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をどう使っているか

8766b19fe1c1f8475e233c910ed6440f?s=128

kobito-kaba

June 26, 2018
Tweet

Transcript

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

  2. 森 洋之 @moridroid

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

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

  5. goo.gl/fW2YPh

  6. None
  7. いまここ

  8. いまここ 啓文堂書店

  9. はじめます

  10. Slice

  11. dependencies { // ... implementation "androidx.slice:slice-core:1.0.0-alpha1" implementation "androidx.slice:slice-builders:1.0.0-alpha1" // ...

    } Jetpackに含まれるコンポーネント
  12. テンプレート化されたUI

  13. インタラクティブ

  14. アップデート可能

  15. Sliceの仕組み

  16. これを作る

  17. ContentProvider

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

  19. slice { slice (horizontal, list_item) { slice (list_item) { Icon(...)

    (no_tint), “Mon” (title), “62°”, }, slice (list_item) { … }, ... } } UIを構造化されたデータで表現
  20. 実装方法

  21. None
  22. None
  23. None
  24. None
  25. 一発です!

  26. もうちょっと

  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() } } ... }
  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() } } ... }
  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() } } ... }
  30. Step2: マニフェストに追加する <manifest> <application> ... <provider android:name=".MySliceProvider" android:authorities="jp.co.yahoo.android.slicesample" android:exported="true"/> </application>

    </manifest>
  31. どこで使われるか

  32. Google Search

  33. 1. by App Name

  34. Step1: meta-dataを追加 <manifest> ... <application> ... <activity android:name="com.example.some.MainActivity" android:label="Slices Test">

    <meta-data android:name="android.metadata.SLICE_URI" android:value="content://com.example.some/mydefault"/> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> … </application> </manifest> メタデータ追加
  35. Step2: パーミッションを追加 val manager = SliceManager.getInstance(this) manager.grantSlicePermission( “com.google.android.googlequicksearchbox”, mainUri) manager.grantSlicePermission(

    “com.google.android.gms”, mainUri) パーミッション要求
  36. 2. by General Terms

  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を追加 メタデータ追加
  38. Step2: intent-filterを追加 <manifest> ... <application> ... <provider android:name=”.MySliceProvider” android:authority=”com.example.slicesample” android:exported=”true”>

    <intent-filter> <action android:name=”android.intent.action.VIEW” /> <category android:name=”android.intent.category.SLICE” /> <data android:scheme=”https” android:host=”example.com” /> </intent-filter> </provider> ... intent-filter追加
  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を オーバーライド
  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を
  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に変換
  42. 3rd partyは?

  43. 3rd party to 3rd party

  44. None
  45. None
  46. Step1: 続報を待つ

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

  48. 自分で作る

  49. Sliceの具体的な仕組み

  50. goo.gl/pfiqiv SliceViewer

  51. スライスの要求

  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開始
  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 生成
  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 -> … } 値を監視
  55. public final class SliceLiveData { ... private static class SliceLiveDataImpl

    extends LiveData<Slice> { private SliceLiveDataImpl(Context context, Uri uri) { super(); mSliceManager = SliceManager.getInstance(context); ... } @Override protected void onActive() { AsyncTask.execute(mUpdateSlice); ... } ... SliceManager 取得
  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<Slice> { private SliceLiveDataImpl(Context context, Uri uri) { super(); mSliceManager = SliceManager.getInstance(context); ... } @Override protected void onActive() { AsyncTask.execute(mUpdateSlice); ... } ... bindSlice() を呼ぶ
  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); } } … }
  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以降
  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以降
  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未満
  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未満 今日はこっち
  62. class SliceManagerCompat extends SliceManagerBase { … @Override public Slice bindSlice(@NonNull

    Intent intent) { return SliceProviderCompat.bindSlice(mContext, intent, SUPPORTED_SPECS); } … } SliceProviderCompat呼ぶだけ
  63. class SliceManagerCompat extends SliceManagerBase { … @Override public Slice bindSlice(@NonNull

    Intent intent) { return SliceProviderCompat.bindSlice(mContext, intent, SUPPORTED_SPECS); } … } ※Sliceを返す側も、  SliceProviderCompatを使います
  64. つまり

  65. None
  66. None
  67. public class SliceProviderCompat { … public static Slice bindSlice(Context context,

    Intent intent, Set<SliceSpec> 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); } … } … }
  68. public class SliceProviderCompat { … public static Slice bindSlice(Context context,

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

    Uri uri, Set<SliceSpec> supportedSpecs) { ProviderHolder holder = acquireClient(context.getContentResolver(), uri); ... try { ... final Bundle res = holder.mProvider.call(METHOD_SLICE, null, extras); ... return new Slice((Bundle) bundle); } … } ContentProviderClient 取得
  70. public class SliceProviderCompat { … public static Slice bindSlice(Context context,

    Uri uri, Set<SliceSpec> 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() を呼び
  71. public class SliceProviderCompat { … public static Slice bindSlice(Context context,

    Uri uri, Set<SliceSpec> supportedSpecs) { ProviderHolder holder = acquireClient(context.getContentResolver(), uri); ... try { ... final Bundle res = holder.mProvider.call(METHOD_SLICE, null, extras); ... return new Slice((Bundle) bundle); } … } Sliceに変換
  72. ContentProvider

  73. ContentProviderClient

  74. ContentProviderClient #call( )

  75. ContentProviderClient

  76. ContentProviderClient#call

  77. ContentProviderClient#call

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

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

  80. スライスを返す

  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)) { … } … }
  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を生成
  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に変換
  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に積む
  85. public class SliceProviderCompat { … private Slice handleBindSlice(final Uri sliceUri,

    final Set<SliceSpec> specs, final String callingPkg) { ... return onBindSliceStrict(sliceUri, specs); } … }
  86. public class SliceProviderCompat { … private Slice onBindSliceStrict(Uri sliceUri, Set<SliceSpec>

    specs) { … mHandler.postDelayed(mAnr, SLICE_BIND_ANR); try { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build()); ... try { return mProvider.onBindSlice(sliceUri); } ...
  87. public class SliceProviderCompat { … private Slice onBindSliceStrict(Uri sliceUri, Set<SliceSpec>

    specs) { … mHandler.postDelayed(mAnr, SLICE_BIND_ANR); try { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build()); ... try { return mProvider.onBindSlice(sliceUri); } ... ANR 2秒
  88. public class SliceProviderCompat { … private Slice onBindSliceStrict(Uri sliceUri, Set<SliceSpec>

    specs) { … mHandler.postDelayed(mAnr, SLICE_BIND_ANR); try { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build()); ... try { return mProvider.onBindSlice(sliceUri); } ... 重たい処理 マジおこ
  89. public class SliceProviderCompat { … private Slice onBindSliceStrict(Uri sliceUri, Set<SliceSpec>

    specs) { … mHandler.postDelayed(mAnr, SLICE_BIND_ANR); try { StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build()); ... try { return mProvider.onBindSlice(sliceUri); } ... SliceProvider #onBindSlice()
  90. スライスの表示

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

    extends LiveData<Slice> { … private final Runnable mUpdateSlice = new Runnable() { @Override public void run() { Slice s = mUri != null ? mSliceManager.bindSlice(mUri) : mSliceManager.bindSlice(mIntent); ... postValue(s); } }; ... }
  92. fun SliceView.bind( context: Context, lifecycleOwner: LifecycleOwner, uri: Uri, ... )

    { ... try { sliceLiveData?.observe(lifecycleOwner, Observer { updatedSlice -> if (updatedSlice == null) return@Observer slice = updatedSlice ... }) …
  93. public class SliceView extends ViewGroup implements Observer<Slice>, View.OnClickListener { …

    public void setSlice(@Nullable Slice slice) { ... mCurrentSlice = slice; reinflate(); } … } SliceからViewを生成 ※省略
  94. Sliceめいたものを つくるなら

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

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

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

  98. まとめ

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

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

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

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

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

  104. おしまい