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. slice { slice (horizontal, list_item) { slice (list_item) { Icon(...)

    (no_tint), “Mon” (title), “62°”, }, slice (list_item) { … }, ... } } UIを構造化されたデータで表現
  2. 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() } } ... }
  3. 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() } } ... }
  4. 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() } } ... }
  5. 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> メタデータ追加
  6. 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を追加 メタデータ追加
  7. 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追加
  8. 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を オーバーライド
  9. 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を
  10. 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に変換
  11. 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開始
  12. 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 生成
  13. 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 -> … } 値を監視
  14. 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 取得
  15. 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() を呼ぶ
  16. 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); } } … }
  17. 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以降
  18. 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以降
  19. 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未満
  20. 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未満 今日はこっち
  21. class SliceManagerCompat extends SliceManagerBase { … @Override public Slice bindSlice(@NonNull

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

    Intent intent) { return SliceProviderCompat.bindSlice(mContext, intent, SUPPORTED_SPECS); } … } ※Sliceを返す側も、  SliceProviderCompatを使います
  23. 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); } … } … }
  24. 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); } … }
  25. 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 取得
  26. 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() を呼び
  27. 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に変換
  28. 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)) { … } … }
  29. 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を生成
  30. 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に変換
  31. 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に積む
  32. public class SliceProviderCompat { … private Slice handleBindSlice(final Uri sliceUri,

    final Set<SliceSpec> specs, final String callingPkg) { ... return onBindSliceStrict(sliceUri, specs); } … }
  33. 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); } ...
  34. 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秒
  35. 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); } ... 重たい処理 マジおこ
  36. 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()
  37. 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); } }; ... }
  38. fun SliceView.bind( context: Context, lifecycleOwner: LifecycleOwner, uri: Uri, ... )

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

    public void setSlice(@Nullable Slice slice) { ... mCurrentSlice = slice; reinflate(); } … } SliceからViewを生成 ※省略