Upgrade to Pro — share decks privately, control downloads, hide ads and more …

【shibuya.apk】Deep Dive into Slices

【shibuya.apk】Deep Dive into Slices

SlicesはContentProviderをどう使っているか

kobito-kaba

June 26, 2018
Tweet

More Decks by kobito-kaba

Other Decks in Programming

Transcript

  1. Deep Dive into Slices
    森 洋之@Yahoo! JAPAN

    View Slide

  2. 森 洋之 @moridroid

    View Slide

  3. 森 洋之 @moridroid
    Yahoo! JAPAN Androidアプリ黒帯
     (こういうところで話す仕事)

    View Slide

  4. 森 洋之 @moridroid
    Yahoo! JAPAN Androidアプリ黒帯
     (こういうところで話す仕事)
    ヤフオク!のアプリ担当

    View Slide

  5. goo.gl/fW2YPh

    View Slide

  6. View Slide

  7. いまここ

    View Slide

  8. いまここ
    啓文堂書店

    View Slide

  9. はじめます

    View Slide

  10. Slice

    View Slide

  11. dependencies {
    // ...
    implementation "androidx.slice:slice-core:1.0.0-alpha1"
    implementation "androidx.slice:slice-builders:1.0.0-alpha1"
    // ...
    }
    Jetpackに含まれるコンポーネント

    View Slide

  12. テンプレート化されたUI

    View Slide

  13. インタラクティブ

    View Slide

  14. アップデート可能

    View Slide

  15. Sliceの仕組み

    View Slide

  16. これを作る

    View Slide

  17. ContentProvider

    View Slide

  18. URIをもとにSliceを要求
    ContentProvider

    View Slide

  19. slice {
    slice (horizontal, list_item) {
    slice (list_item) {
    Icon(...) (no_tint),
    “Mon” (title),
    “62°”,
    },
    slice (list_item) {

    },
    ...
    }
    }
    UIを構造化されたデータで表現

    View Slide

  20. 実装方法

    View Slide

  21. View Slide

  22. View Slide

  23. View Slide

  24. View Slide

  25. 一発です!

    View Slide

  26. もうちょっと

    View Slide

  27. Step1: SliceProviderをつくる
    class MySliceProvider : SliceProvider() {
    ...
    override fun onBindSlice(sliceUri: Uri): Slice? {
    val context = context ?: return null
    return if (sliceUri.path == "/") {
    return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
    .addRow { it.setTitle("URI found.") }
    .build()
    } else {
    ListBuilder(context, sliceUri, ListBuilder.INFINITY)
    .addRow { it.setTitle("URI not found.") }
    .build()
    }
    }
    ...
    }

    View Slide

  28. Step1: SliceProviderをつくる
    class MySliceProvider : SliceProvider() {
    ...
    override fun onBindSlice(sliceUri: Uri): Slice? {
    val context = context ?: return null
    return if (sliceUri.path == "/") {
    return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
    .addRow { it.setTitle("URI found.") }
    .build()
    } else {
    ListBuilder(context, sliceUri, ListBuilder.INFINITY)
    .addRow { it.setTitle("URI not found.") }
    .build()
    }
    }
    ...
    }

    View Slide

  29. Step1: SliceProviderをつくる
    class MySliceProvider : SliceProvider() {
    ...
    override fun onBindSlice(sliceUri: Uri): Slice? {
    val context = context ?: return null
    return if (sliceUri.path == "/") {
    return ListBuilder(context, sliceUri, ListBuilder.INFINITY)
    .addRow { it.setTitle("URI found.") }
    .build()
    } else {
    ListBuilder(context, sliceUri, ListBuilder.INFINITY)
    .addRow { it.setTitle("URI not found.") }
    .build()
    }
    }
    ...
    }

    View Slide

  30. Step2: マニフェストに追加する


    ...
    android:name=".MySliceProvider"
    android:authorities="jp.co.yahoo.android.slicesample"
    android:exported="true"/>


    View Slide

  31. どこで使われるか

    View Slide

  32. Google Search

    View Slide

  33. 1. by App Name

    View Slide

  34. Step1: meta-dataを追加

    ...

    ...
    android:label="Slices Test">
    android:name="android.metadata.SLICE_URI"
    android:value="content://com.example.some/mydefault"/>








    メタデータ追加

    View Slide

  35. Step2: パーミッションを追加
    val manager = SliceManager.getInstance(this)
    manager.grantSlicePermission(
    “com.google.android.googlequicksearchbox”, mainUri)
    manager.grantSlicePermission(
    “com.google.android.gms”, mainUri)
    パーミッション要求

    View Slide

  36. 2. by General Terms

    View Slide

  37. val appIndex = FirebaseAppIndex.getInstance()
    appIndex.update(Indexable.Builder()
    .setUrl(contentUrl)
    .setName(“Temperature”)
    .setKeywords(“living room”, “kitchen”, “bedroom”)
    .setMetadata(Indexable.Metadata.Builder()
    .setSliceUri(sliceUri))
    .build())
    Step1: Indexを追加
    メタデータ追加

    View Slide

  38. Step2: intent-filterを追加

    ...

    ...
    android:authority=”com.example.slicesample”
    android:exported=”true”>



    android:host=”example.com” />


    ...
    intent-filter追加

    View Slide

  39. class MySliceProvider : SliceProvider() {

    override fun onMapIntentToUri(intent: Intent?): Uri {
    val path = intent.data.path.replace(“/slice”, “”)
    return Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    .authority(“com.example.slicesample”)
    .path(path)
    .build()
    }

    }
    Step3: App IndexingのURIを
    SliceProviderのURIに変換する
    onMapIntentToUriを
    オーバーライド

    View Slide

  40. class MySliceProvider : SliceProvider() {

    override fun onMapIntentToUri(intent: Intent?): Uri {
    val path = intent.data.path.replace(“/slice”, “”)
    return Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    .authority(“com.example.slicesample”)
    .path(path)
    .build()
    }

    }
    Step3: App IndexingのURIを
    SliceProviderのURIに変換する
    AppIndexingのURIを

    View Slide

  41. class MySliceProvider : SliceProvider() {

    override fun onMapIntentToUri(intent: Intent?): Uri {
    val path = intent.data.path.replace(“/slice”, “”)
    return Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
    .authority(“com.example.slicesample”)
    .path(path)
    .build()
    }

    }
    Step3: App IndexingのURIを
    SliceProviderのURIに変換する
    Slice用のURIに変換

    View Slide

  42. 3rd partyは?

    View Slide

  43. 3rd party to 3rd party

    View Slide

  44. View Slide

  45. View Slide

  46. Step1: 続報を待つ

    View Slide

  47. 開放されなかったら?

    View Slide

  48. 自分で作る

    View Slide

  49. Sliceの具体的な仕組み

    View Slide

  50. goo.gl/pfiqiv
    SliceViewer

    View Slide

  51. スライスの要求

    View Slide

  52. class SingleSliceViewerActivity : AppCompatActivity() {

    private fun bindSlice(uri: Uri) {
    sliceView.bind(
    context = this,
    lifecycleOwner = this,
    uri = uri,
    scrollable = true
    )
    viewModel.selectedMode.observe(this, Observer {
    sliceView.mode = it ?: SliceView.MODE_LARGE
    })
    uriValue.text = uri.toString()
    }

    }
    bind開始

    View Slide

  53. fun SliceView.bind(
    context: Context,
    lifecycleOwner: LifecycleOwner,
    uri: Uri,
    ...
    ) {
    ...
    val intent = Intent(Intent.ACTION_VIEW, uri)
    val sliceLiveData = SliceLiveData.fromIntent(context, intent)
    sliceLiveData?.removeObservers(lifecycleOwner)
    try {
    sliceLiveData?.observe(lifecycleOwner, Observer { updatedSlice ->

    }
    SliceLiveData
    生成

    View Slide

  54. fun SliceView.bind(
    context: Context,
    lifecycleOwner: LifecycleOwner,
    uri: Uri,
    ...
    ) {
    ...
    val intent = Intent(Intent.ACTION_VIEW, uri)
    val sliceLiveData = SliceLiveData.fromIntent(context, intent)
    sliceLiveData?.removeObservers(lifecycleOwner)
    try {
    sliceLiveData?.observe(lifecycleOwner, Observer { updatedSlice ->

    }
    値を監視

    View Slide

  55. public final class SliceLiveData {
    ...
    private static class SliceLiveDataImpl extends LiveData {
    private SliceLiveDataImpl(Context context, Uri uri) {
    super();
    mSliceManager = SliceManager.getInstance(context);
    ...
    }
    @Override
    protected void onActive() {
    AsyncTask.execute(mUpdateSlice);
    ...
    }
    ...
    SliceManager
    取得

    View Slide

  56. private final Runnable mUpdateSlice = new Runnable() {
    @Override
    public void run() {
    Slice s = mUri != null ? mSliceManager.bindSlice(mUri)
    : mSliceManager.bindSlice(mIntent);
    ...
    }
    };
    ...
    }
    public final class SliceLiveData {
    ...
    private static class SliceLiveDataImpl extends LiveData {
    private SliceLiveDataImpl(Context context, Uri uri) {
    super();
    mSliceManager = SliceManager.getInstance(context);
    ...
    }
    @Override
    protected void onActive() {
    AsyncTask.execute(mUpdateSlice);
    ...
    }
    ...
    bindSlice()
    を呼ぶ

    View Slide

  57. public abstract class SliceManager {
    /**
    * Get a {@link SliceManager}.
    */
    @SuppressWarnings("NewApi")
    public static @NonNull SliceManager getInstance(@NonNull Context context) {
    if (BuildCompat.isAtLeastP()) {
    return new SliceManagerWrapper(context);
    } else {
    return new SliceManagerCompat(context);
    }
    }

    }

    View Slide

  58. public abstract class SliceManager {
    /**
    * Get a {@link SliceManager}.
    */
    @SuppressWarnings("NewApi")
    public static @NonNull SliceManager getInstance(@NonNull Context context) {
    if (BuildCompat.isAtLeastP()) {
    return new SliceManagerWrapper(context);
    } else {
    return new SliceManagerCompat(context);
    }
    }

    }
    P以降

    View Slide

  59. public abstract class SliceManager {
    /**
    * Get a {@link SliceManager}.
    */
    @SuppressWarnings("NewApi")
    public static @NonNull SliceManager getInstance(@NonNull Context context) {
    if (BuildCompat.isAtLeastP()) {
    return new SliceManagerWrapper(context);
    } else {
    return new SliceManagerCompat(context);
    }
    }

    } なかでSystemService
    取得してる
    P以降

    View Slide

  60. public abstract class SliceManager {
    /**
    * Get a {@link SliceManager}.
    */
    @SuppressWarnings("NewApi")
    public static @NonNull SliceManager getInstance(@NonNull Context context) {
    if (BuildCompat.isAtLeastP()) {
    return new SliceManagerWrapper(context);
    } else {
    return new SliceManagerCompat(context);
    }
    }

    }
    P未満

    View Slide

  61. public abstract class SliceManager {
    /**
    * Get a {@link SliceManager}.
    */
    @SuppressWarnings("NewApi")
    public static @NonNull SliceManager getInstance(@NonNull Context context) {
    if (BuildCompat.isAtLeastP()) {
    return new SliceManagerWrapper(context);
    } else {
    return new SliceManagerCompat(context);
    }
    }

    }
    P未満
    今日はこっち

    View Slide

  62. class SliceManagerCompat extends SliceManagerBase {

    @Override
    public Slice bindSlice(@NonNull Intent intent) {
    return SliceProviderCompat.bindSlice(mContext, intent, SUPPORTED_SPECS);
    }

    }
    SliceProviderCompat呼ぶだけ

    View Slide

  63. class SliceManagerCompat extends SliceManagerBase {

    @Override
    public Slice bindSlice(@NonNull Intent intent) {
    return SliceProviderCompat.bindSlice(mContext, intent, SUPPORTED_SPECS);
    }

    }
    ※Sliceを返す側も、
     SliceProviderCompatを使います

    View Slide

  64. つまり

    View Slide

  65. View Slide

  66. View Slide

  67. public class SliceProviderCompat {

    public static Slice bindSlice(Context context, Intent intent,
    Set supportedSpecs) {
    ...
    ContentResolver resolver = context.getContentResolver();
    ...
    // Check if the intent has data for the slice uri on it and use that
    final Uri intentData = intent.getData();
    if (intentData != null && SLICE_TYPE.equals(resolver.getType(intentData))) {
    return bindSlice(context, intentData, supportedSpecs);
    }

    }

    }

    View Slide

  68. public class SliceProviderCompat {

    public static Slice bindSlice(Context context, Uri uri,
    Set supportedSpecs) {
    ProviderHolder holder = acquireClient(context.getContentResolver(), uri);
    ...
    try {
    ...
    final Bundle res = holder.mProvider.call(METHOD_SLICE, null, extras);
    ...
    return new Slice((Bundle) bundle);
    }

    }

    View Slide

  69. public class SliceProviderCompat {

    public static Slice bindSlice(Context context, Uri uri,
    Set supportedSpecs) {
    ProviderHolder holder = acquireClient(context.getContentResolver(), uri);
    ...
    try {
    ...
    final Bundle res = holder.mProvider.call(METHOD_SLICE, null, extras);
    ...
    return new Slice((Bundle) bundle);
    }

    }
    ContentProviderClient
    取得

    View Slide

  70. public class SliceProviderCompat {

    public static Slice bindSlice(Context context, Uri uri,
    Set supportedSpecs) {
    ProviderHolder holder = acquireClient(context.getContentResolver(), uri);
    ...
    try {
    ...
    final Bundle res = holder.mProvider.call(METHOD_SLICE, null, extras);
    ...
    return new Slice((Bundle) bundle);
    }

    }
    ContentProviderClient#call()
    を呼び

    View Slide

  71. public class SliceProviderCompat {

    public static Slice bindSlice(Context context, Uri uri,
    Set supportedSpecs) {
    ProviderHolder holder = acquireClient(context.getContentResolver(), uri);
    ...
    try {
    ...
    final Bundle res = holder.mProvider.call(METHOD_SLICE, null, extras);
    ...
    return new Slice((Bundle) bundle);
    }

    } Sliceに変換

    View Slide

  72. ContentProvider

    View Slide

  73. ContentProviderClient

    View Slide

  74. ContentProviderClient
    #call( )

    View Slide

  75. ContentProviderClient

    View Slide

  76. ContentProviderClient#call

    View Slide

  77. ContentProviderClient#call

    View Slide

  78. ContentProviderClient#call
    ・ContentProviderの任意のメソッドを呼ぶ
     のではなく
     ContentProvider#call()を呼ぶ

    View Slide

  79. ContentProviderClient#call
    ・ContentProviderの任意のメソッドを呼ぶ
     のではなく
     ContentProvider#call()を呼ぶ
    ・ContentProviderは、
     引数のmethodで判定して、なんかして、
     結果はBundleで返す

    View Slide

  80. スライスを返す

    View Slide

  81. public class SliceProviderCompat {

    public Bundle call(String method, String arg, Bundle extras) {
    if (method.equals(METHOD_SLICE)) {
    ...
    Slice s = handleBindSlice(uri, specs, getCallingPackage());
    Bundle b = new Bundle();
    b.putParcelable(EXTRA_SLICE, s != null ? s.toBundle() : null);
    return b;
    } else if (method.equals(METHOD_MAP_INTENT)) {

    }

    }

    View Slide

  82. public class SliceProviderCompat {

    public Bundle call(String method, String arg, Bundle extras) {
    if (method.equals(METHOD_SLICE)) {
    ...
    Slice s = handleBindSlice(uri, specs, getCallingPackage());
    Bundle b = new Bundle();
    b.putParcelable(EXTRA_SLICE, s != null ? s.toBundle() : null);
    return b;
    } else if (method.equals(METHOD_MAP_INTENT)) {

    }

    }
    Sliceを生成

    View Slide

  83. public class SliceProviderCompat {

    public Bundle call(String method, String arg, Bundle extras) {
    if (method.equals(METHOD_SLICE)) {
    ...
    Slice s = handleBindSlice(uri, specs, getCallingPackage());
    Bundle b = new Bundle();
    b.putParcelable(EXTRA_SLICE, s != null ? s.toBundle() : null);
    return b;
    } else if (method.equals(METHOD_MAP_INTENT)) {

    }

    }
    Bundleに変換

    View Slide

  84. public class SliceProviderCompat {

    public Bundle call(String method, String arg, Bundle extras) {
    if (method.equals(METHOD_SLICE)) {
    ...
    Slice s = handleBindSlice(uri, specs, getCallingPackage());
    Bundle b = new Bundle();
    b.putParcelable(EXTRA_SLICE, s != null ? s.toBundle() : null);
    return b;
    } else if (method.equals(METHOD_MAP_INTENT)) {

    }

    }
    Bundleに積む

    View Slide

  85. public class SliceProviderCompat {

    private Slice handleBindSlice(final Uri sliceUri, final Set specs,
    final String callingPkg) {
    ...
    return onBindSliceStrict(sliceUri, specs);
    }

    }

    View Slide

  86. public class SliceProviderCompat {

    private Slice onBindSliceStrict(Uri sliceUri, Set specs) {

    mHandler.postDelayed(mAnr, SLICE_BIND_ANR);
    try {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    .detectAll()
    .penaltyDeath()
    .build());
    ...
    try {
    return mProvider.onBindSlice(sliceUri);
    }
    ...

    View Slide

  87. public class SliceProviderCompat {

    private Slice onBindSliceStrict(Uri sliceUri, Set specs) {

    mHandler.postDelayed(mAnr, SLICE_BIND_ANR);
    try {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    .detectAll()
    .penaltyDeath()
    .build());
    ...
    try {
    return mProvider.onBindSlice(sliceUri);
    }
    ...
    ANR 2秒

    View Slide

  88. public class SliceProviderCompat {

    private Slice onBindSliceStrict(Uri sliceUri, Set specs) {

    mHandler.postDelayed(mAnr, SLICE_BIND_ANR);
    try {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    .detectAll()
    .penaltyDeath()
    .build());
    ...
    try {
    return mProvider.onBindSlice(sliceUri);
    }
    ...
    重たい処理
    マジおこ

    View Slide

  89. public class SliceProviderCompat {

    private Slice onBindSliceStrict(Uri sliceUri, Set specs) {

    mHandler.postDelayed(mAnr, SLICE_BIND_ANR);
    try {
    StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
    .detectAll()
    .penaltyDeath()
    .build());
    ...
    try {
    return mProvider.onBindSlice(sliceUri);
    }
    ...
    SliceProvider
    #onBindSlice()

    View Slide

  90. スライスの表示

    View Slide

  91. public final class SliceLiveData {
    ...
    private static class SliceLiveDataImpl extends LiveData {

    private final Runnable mUpdateSlice = new Runnable() {
    @Override
    public void run() {
    Slice s = mUri != null ? mSliceManager.bindSlice(mUri)
    : mSliceManager.bindSlice(mIntent);
    ...
    postValue(s);
    }
    };
    ...
    }

    View Slide

  92. fun SliceView.bind(
    context: Context,
    lifecycleOwner: LifecycleOwner,
    uri: Uri,
    ...
    ) {
    ...
    try {
    sliceLiveData?.observe(lifecycleOwner, Observer { updatedSlice ->
    if (updatedSlice == null) [email protected]
    slice = updatedSlice
    ...
    })

    View Slide

  93. public class SliceView extends ViewGroup implements Observer, View.OnClickListener {

    public void setSlice(@Nullable Slice slice) {
    ...
    mCurrentSlice = slice;
    reinflate();
    }

    }
    SliceからViewを生成
    ※省略

    View Slide

  94. Sliceめいたものを
    つくるなら

    View Slide

  95. ・Bundleと相互変換できるクラスHogeをつくる
     ※めんどくさい
    ・ContentProviderClientを使って、
     高速にBundleをやりとりする
    ・Hoge→Viewのロジックを書く※めんどくさい

    View Slide

  96. ・Bundleと相互変換できるクラスHogeをつくる
     ※めんどくさい
    ・ContentProviderClientを使って、
     高速にBundleをやりとりする
    ・Hoge→Viewのロジックを書く※めんどくさい

    View Slide

  97. ・Bundleと相互変換できるクラスHogeをつくる
     ※めんどくさい
    ・ContentProviderClientを使って、
     高速にBundleをやりとりする
    ・Hoge→Viewのロジックを書く※めんどくさい

    View Slide

  98. まとめ

    View Slide

  99. ・Sliceは古典的なAPIを上手に使っている
    ・最初はGoogle検索アプリで使われる
    ・サードパーティでも表示できるかは不透明
    ・でも似たようなことは自前でできるね!

    View Slide

  100. ・Sliceは古典的な技術を上手に使っている
    ・最初はGoogle検索アプリで使われる
    ・サードパーティでも表示できるかは不透明
    ・でも似たようなことは自前でできるね!

    View Slide

  101. ・Sliceは古典的な技術を上手に使っている
    ・最初はGoogle検索アプリで使われる
    ・サードパーティでも表示できるかは不透明
    ・でも似たようなことは自前でできるね!

    View Slide

  102. ・Sliceは古典的な技術を上手に使っている
    ・最初はGoogle検索アプリで使われる
    ・サードパーティでも表示できるかは不透明
    ・でも似たようなことは自前でできるね!

    View Slide

  103. ・Sliceは古典的な技術を上手に使っている
    ・最初はGoogle検索アプリで使われる
    ・サードパーティでも表示できるかは不透明
    ・でも似たようなことは自前でできるね!
    ・本を書いたのでよろしくお願いします

    View Slide

  104. おしまい

    View Slide