web analytics

[Android Code] การสร้าง Android Slices ตอนที่ 1

cover-1

สวัสดีครับ ช่วงนี้ว่างๆก็เลยหาอะไรทำ ศึกษาอะไรสนุกๆ แล้วก็ไปเจอฟีเจอร์ใหม่ใน Android P ซึ่งมีความสามารถใหม่ที่เรียกว่า Slice ก็เลยลองเล่นและนำมาเขียนบล็อกเพื่อทบทวนเป็นความรู้ครับ

 

รู้จักกับ slice

Slice คือ การแสดงส่วนนึงของแอปในแอปอื่น หรือก็คือเราสามารถนำอะไรบางอย่างในแอปอื่นที่เขาเตรียมไว้มาไว้ในแอปเราได้ แบบเดียวกับที่เห็นใน Google Assistance ซึ่งความสามารถนี้มากับ Android P แต่สามารถใช้งานได้ย้อนไปตั้งแต่ Android Kitkat 4.4 เลยนะ

 

เริ่มต้น

โดยบทความนี้ ผมจะลองเล่นเจ้า Slice โดยการทำแอปตัวแรกที่เตรียม Slice เอาไว้ 1 อัน จากนั้นทำแอปตัวที่สอง โดยจะนำเอา Slice ของแอปแรกมาแสดง โดยแอปตัวที่สองจะไม่มีอะไรเลย นอกจาก Slice Viewer
เริ่มกันเลย

 

เพิ่ม Dependencies

ตัว Slice library จะอยู่ใน androidx ซึ่งก็คือ ทาง Google ได้รวมพวก Support library ต่างๆไว้ที่เดียว

dependencies {
    implementation 'androidx.appcompat:appcompat:1.0.0-rc01'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.2'

    // slice
    implementation 'androidx.slice:slice-core:1.0.0-rc01'
    implementation 'androidx.slice:slice-builders:1.0.0-rc01'
}

 

หรือถ้าไม่ใช้ androidx จะใช้แบบ support แบบเดิมปกติก็ได้

    implementation 'com.android.support:appcompat-v7:28.0.0-rc01'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    implementation 'com.android.support:slice-builders:+'

 

สร้าง Provider

สร้าง class ขึ้นมาแล้ว extends SliceProvider ซึ่ง class นี้จะทำงานตอนที่ slice ของเราถูกเรียกใช้งาน
method ที่สำคัญคือ onBindSlice(Uri) เดี๋ยวเราค่อยมาเขียนส่วนนี้ทีหลัง

public class MySlicesProvider extends SliceProvider {
    
    @Override
    public boolean onCreateSliceProvider() {
        return true;
    }

    public Slice onBindSlice(Uri sliceUri) {
        //
        return null;
    }
}

 

สร้าง BroadcastReceiver

สร้าง class BroadcastReceiver ซึ่งคลาสนี้จะทำงานตอนมี action มา slice ของเรา เช่น กดปุ่ม
ตอนนี้ให้กำหนด KEY ของ ACTION ที่เราจะรับเข้ามาว่ามีอะไรบ้าง แล้วเดี๋ยวเราค่อยมาเขียนส่วนนี้ทีหลัง

public class MySliceBroadcastReceiver extends BroadcastReceiver {

    public static String ACTION_CHANGE_TEMP = "com.benzneststudios.myandroidslice.ACTION_CHANGE_TEMP";
    public static String EXTRA_TEMP_VALUE = "ccom.benzneststudios.myandroidslice.EXTRA_TEMP_VALUE";

    @Override
    public void onReceive(Context context, Intent intent) {
          // do something
    }
}

 

กำหนด Provider และ Receiver ใน manifests

กำหนด provider ของเราลงใน Androidmanifests.xml เพื่อให้แอปรู้จักกับ provider ของเรา

    <application>
       ..
        <provider
            android:authorities="com.benzneststudios.myandroidslice"
            android:name=".MySlicesProvider"
            android:exported="true">
        </provider>
        <receiver android:name=".MySliceBroadcastReceiver"/>
    </application>

 

เขียนส่วน Slice Provider

ต่อมาก็มากำหนดว่า Slice ของเราจะมีอะไรแสดงบ้าง
โดยเราจะเริ่มออกแบบ Slice ง่ายๆแบบนี้ก่อน

1

 

เริ่มจาก Slice ของแอปนั้นจะใช้ URI ในการระบุตัวตนของ slice ซึ่งก็คือ content://<packagename>/<PATH SOMETHING> เช่น

content://com.benzneststudios.myandroidslice/volume

ดังนั้น ถ้ามีแอปไหนเรียกใช้ slice ของเรา จะมาเข้าที่ onBindSlice(uri) ให้เช็ค URI ที่ได้รับเข้ามา
หากตรงกับของเรา ก็ให้สร้าง slice แล้ว return ค่ากลับไป

public class MySlicesProvider extends SliceProvider {

    @Override
    public boolean onCreateSliceProvider() {
        return true;
    }

    public Slice onBindSlice(Uri sliceUri) {
        switch (sliceUri.getPath()) {
            case "/volume":
                return createVolumeSlice(sliceUri);
        }
        return null;
    }

    ...

 

ต่อมาก็มาเขียนส่วนของการสร้าง slice
วิธีการคือใช้คลาสที่ชื่อว่า ListBuider จะสร้าง view ใน slice แบบลิส ดังนั้นให้เราสร้างแถว เพิ่ม action
ที่สำคัญต้องใส่ setPrimaryAction(action) เสมอ ซึ่งก็คือเมื่อผู้ใช้กดทีื่ slice ของเรา

public class MySlicesProvider extends SliceProvider {

    ...

    private Slice createVolumeSlice(Uri sliceUri) {
        // Construct our parent builder
        ListBuilder listBuilder = new ListBuilder(getContext(), sliceUri, ListBuilder.INFINITY);

        // Add the row to the parent builder
        listBuilder.addRow(new ListBuilder.RowBuilder()
                .setTitle("Volume = " + MyData.volume)
                .setPrimaryAction(getSliceActionOpenActivity()));

        listBuilder.addAction(getSliceActionIncreaseVolume());
        listBuilder.addAction(getSliceActionDecreaseVolume());
        // Build the slice
        return listBuilder.build();
    }

    ...
}

 

แล้วก็ในที่นี้จะต้องมีที่เก็บค่า ของ volume จึงขอสมมุติโดยการสร้าง class ตัวแปรแบบ static ไว้

public class MyData {
    public static int volume= 25;
}

 

ส่วน SliceAction จะกำหนดไว้ 3 อย่าง คือปุ่ม เพิ่ม-ลดด้านขวา กดแล้วตัวเลขจะเพิ่มขึ้น-ลดลง แล้วก็เมื่อกดที่ตัว slice จะให้ทำการเปิดแอปของเราขึ้นมา
ซึ่ง SliceAction จะต้องกำหนด PendingIntent เสมอ ซึ่งมันก็การการกำหนดว่าให้ action นั้นๆทำอะไร และ PendingIntent ก็จะกำหนด Action เป็น KEY ใน receiver ของเรา
แล้วก็ reqCode ของ PendingIntent ของ action เพิ่ม กับ action ลด ห้ามซ้ำกันนะ

 public class MySlicesProvider extends SliceProvider {

    ...

    private PendingIntent createPendingIntentWithData(int reqCode, int value) {
        Intent intent = new Intent(MySliceBroadcastReceiver.ACTION_CHANGE_TEMP);
        intent.setClass(getContext(), MySliceBroadcastReceiver.class);
        intent.putExtra(MySliceBroadcastReceiver.EXTRA_TEMP_VALUE, value);
        return PendingIntent.getBroadcast(getContext(), reqCode, intent,
                PendingIntent.FLAG_UPDATE_CURRENT);
    }

    private SliceAction getSliceActionIncreaseVolume() {
        Log.d("Slice", "Increase");
        SliceAction tempUp = new SliceAction(createPendingIntentWithData(1111, MyData.volume + 1), IconCompat.createWithResource(getContext(), R.drawable.ic_up), "+");
        return tempUp;
    }

    private SliceAction getSliceActionDecreaseVolume() {
        Log.d("Slice", "Decrease");
        SliceAction tempDown = new SliceAction(createPendingIntentWithData(2222, MyData.volume - 1), IconCompat.createWithResource(getContext(), R.drawable.ic_down), "-");
        return tempDown;
    }

    private SliceAction getSliceActionOpenActivity() {

        // The primary action; this will activate when the row is tapped
        Intent intent = new Intent(getContext(), MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(getContext(), 0, intent, 0);
        SliceAction openTempActivity = new SliceAction(pendingIntent,
                IconCompat.createWithResource(getContext(),
                        R.drawable.ic_launcher_foreground), "controls");
        return openTempActivity;
    }
}

 

เขียนส่วน Slice Receiver

ต่อมาเขียน onReceive() ให้เช็ค Action ของ Intent
แล้วก็ update ค่า volume ใหม่ จากนั้นก็ให้ update Slice ใหม่อีกครั้ง ใช้คำสั่ง notifyChange(uri)

public class MySliceBroadcastReceiver extends BroadcastReceiver {

    public static String ACTION_CHANGE_TEMP = "com.benzneststudios.myandroidslice.ACTION_CHANGE_TEMP";
    public static String EXTRA_TEMP_VALUE = "ccom.benzneststudios.myandroidslice.EXTRA_TEMP_VALUE";

    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();

        if (ACTION_CHANGE_TEMP.equals(action) && intent.getExtras() != null) {
            int newValue = intent.getExtras().getInt(EXTRA_TEMP_VALUE, 25);
            updateVolume(context, newValue);
        }
    }

    private void updateVolume(Context context, int newValue) {

        MyData.volume = newValue;

        Uri uri = Uri.parse("content://com.benzneststudios.myandroidslice/volume");
        context.getContentResolver().notifyChange(uri, null);
    }
}

 

สร้างแอปที่สองสำหรับแสดง Slice

ให้สร้างโปรเจคอีกตัว โดยแอปนี้จะเอาไว้แสดง slice ของแอปแรก
โดยกำหนด packagename ต่างจากแอปแรก

 

เพิ่ม Dependencies

เพิ่ม dependencies ของ slice-view

dependencies {
    ...
    implementation 'androidx.appcompat:appcompat:1.0.0-rc01'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.2'

    // slice view
    implementation 'androidx.slice:slice-view:1.0.0-rc01'
}

 

กำหนด SliceView ใน layout

ที่ layout ของ activity หรือ fragment ให้กำหนด SliceView ลงไป

<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:padding="16dp">

    <androidx.slice.widget.SliceView
        android:id="@+id/sliceView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

</FrameLayout>

 

ใช้งาน SliceView ใน Activity

ในที่นี้ผมจะใช้ sliceView ใน MainActivity
วิธีการใช้ คือใช้คลาสที่ชื่อว่า LiveData โดยแปลงมาจาก URI ในที่นี้คือ URI ของแอปตัวแรกที่เราทำไว้
แล้วก็เรียก observe

public class MainActivity extends AppCompatActivity {

    private SliceView sliceView;

    @SuppressLint("RestrictedApi")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        sliceView = findViewById(R.id.sliceView);
        sliceView.setMode(SliceView.MODE_LARGE);

        LiveData liveData = SliceLiveData.fromUri(this, Uri.parse("content://com.benzneststudios.myandroidslice/volume"));
        liveData.observe(this, sliceView);
    }
}

 

อธิบายเพิ่มเติมอีกนิด ซึ่งพารามิเตอร์ตัวแรกของ observe คือ LifecycleOwner มันคือ คลาสที่รวมพวก lifecycle ของ activity , fragment เช่น onStart() , onStop() , onResume() เอาไว้ ดังนั้นเราก็แค่โยน activity ให้มัน แล้วที่เหลือเดี๋ยวมันจัดการเอง

 

ลองรัน

ความสามารถนี้สามารถใช้งานย้อนไปถึง Android 4.4 เลย
รันแอปที่สอง (Slice view) ดูผลลัพธ์ แอปจะถามหา permission

screenshot_1534751742

ก็ให้เรากด อนุญาตซะ

screenshot_1534751746

 

screenshot_1534755304

 

เราจะสามารถมี action กับ slice ได้

g1

 

บทความนี้ขอหยุดเพียงเท่านี้ก่อน บทความนี้รวมๆก็คือเป็นการนำ slice ไปใช้ ในตอนหน้าจะลองทำ Slice แบบ advance ขึ้น

 

ตัวอย่างโปรเจค

https://github.com/benznest/my-android-slices/tree/master

 

Reference

https://developer.android.com/guide/slices/
https://codelabs.developers.google.com/codelabs/android-slices-basic/index.html