SlideShare a Scribd company logo
1 of 103
Download to read offline
Chrome Custom Tabs
の仕組みから学ぶ
プロセス間通信
大前良介(OHMAE Ryosuke)
DroidKaigi 2019
自己紹介
大前良介(OHMAE Ryosuke)
https://github.com/ohmae
ヤフー株式会社
Androidアプリエンジニア
Yahoo! JAPAN Yahoo!ブラウザー buzzHOME
Chrome Custom Tabs
Chrome Custom Tabs
使ったことありますか?
質問
Chrome Custom Tabs
アプリ内ブラウザのような見た目で
Chromeを呼び出すことができる
使い方は?
ライブラリ
•SupportLibrary
com.android.support:customtabs
•Jetpack
androidx.browser:browser
CustomTabsClient.bindCustomTabsService(
context, packageName, this)
...
CustomTabsIntent.Builder(session)
...
.build()
.launchUrl(this, Uri.parse(url))
バインドしてセッション指定
com.android.support:customtabs
val customTabsIntent = CustomTabsIntent.Builder()
...
.build()
customTabsIntent.intent.setPackage(packageName)
customTabsIntent.launchUrl(this, Uri.parse(url))
Intentにパッケージ指定
利用するパッケージの決定方法
https://github.com/
GoogleChrome/custom-tabs-client
/shared/src/main/java/org/chromium/customtabsclient/shared/
CustomTabsHelper.java
String getPackageNameToUse(Context)
Intent activityIntent
= new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.example.com"));
List<ResolveInfo> resolvedActivityList
= pm.queryIntentActivities(activityIntent, 0);
for (ResolveInfo info : resolvedActivityList) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(info.activityInfo.packageName);
if (pm.resolveService(serviceIntent, 0) != null) {
packagesSupportingCustomTabs
.add(info.activityInfo.packageName);
}
}
適当なURLを起動するIntent
Intent activityIntent
= new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.example.com"));
List<ResolveInfo> resolvedActivityList
= pm.queryIntentActivities(activityIntent, 0);
for (ResolveInfo info : resolvedActivityList) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(info.activityInfo.packageName);
if (pm.resolveService(serviceIntent, 0) != null) {
packagesSupportingCustomTabs
.add(info.activityInfo.packageName);
}
}
ブラウザアプリのリスト
Intent activityIntent
= new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.example.com"));
List<ResolveInfo> resolvedActivityList
= pm.queryIntentActivities(activityIntent, 0);
for (ResolveInfo info : resolvedActivityList) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(info.activityInfo.packageName);
if (pm.resolveService(serviceIntent, 0) != null) {
packagesSupportingCustomTabs
.add(info.activityInfo.packageName);
}
}
android.support.customtabs.action
.CustomTabsService
Actionを受け取る
Intent-Filterを持つサービス
使用されるアプリは
ブラウザ
かつ
Custom Tabsサービス
を持つアプリ
Chromeだけが
選ばれるわけじゃない
Firefox Custom Tabs
他にもいっぱい対応ブラウザ
https://github.com/ohmae/
custom-tabs-sample
・・・ってことは
アプリを限定しない
汎用的なプロトコル!?
CustomTabs対応ブラウザ
作ってみました
https://github.com/ohmae/custom-tabs-browser
Android的正攻法な
プロセス間通信の
ノウハウの宝庫
本日の内容
Custom Tabsで使われている
Android的正攻法な
プロセス間通信を紹介
基本中の基本だけど奥深い!
Androidのプロセス間通信
•Intent
• startActivity/startActivityForResult
• startService/sendBroadcast
•AIDL(Android Interface Definition Language)
•Content Provider
•その他(Socket通信・ファイル共有・etc.)
Custom Tabsで使われるプロセス間通信
•Intent
• startActivity/startActivityForResult
• startService/sendBroadcast
•AIDL(Android Interface Definition Language)
•Content Provider
•その他(Socket通信・ファイル共有・etc.)
プロトコルに対応した
アプリを探す
Intent activityIntent
= new Intent(Intent.ACTION_VIEW,
Uri.parse("http://www.example.com"));
List<ResolveInfo> resolvedActivityList
= pm.queryIntentActivities(activityIntent, 0);
for (ResolveInfo info : resolvedActivityList) {
Intent serviceIntent = new Intent();
serviceIntent.setAction(ACTION_CUSTOM_TABS_CONNECTION);
serviceIntent.setPackage(info.activityInfo.packageName);
if (pm.resolveService(serviceIntent, 0) != null) {
packagesSupportingCustomTabs
.add(info.activityInfo.packageName);
}
}
PackageManager
#queryIntentActivities
#queryIntentServices
#resolveActivity
#resolveService
等をつかって探す
AndroidManifest
<activity android:name=".CustomTabsActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
</intent-filter>
</activity>
<service android:name=".CustomTabsConnectionService">
<intent-filter>
<action android:name=
"android.support.customtabs.action.CustomTabsService"/>
</intent-filter>
</service>
プロトコルの宣言は
intent-filter
AndroidManifestは
アプリの仕様宣言
AIDL
class CustomTabsHelper() : CustomTabsServiceConnection() {
override fun onCustomTabsServiceConnected(
name: ComponentName, client: CustomTabsClient) {
client.warmup(0)
val session = client.newSession(CustomTabsCallback())
session.mayLaunchUrl(Uri.parse(url), null, urlList)
}
override fun onServiceDisconnected(name: ComponentName) {}
}
interface ICustomTabsService {
boolean warmup(long flags) = 1;
boolean newSession(in ICustomTabsCallback callback) = 2;
boolean mayLaunchUrl(in ICustomTabsCallback callback, in Uri url,
in Bundle extras, in List<Bundle> otherLikelyBundles) = 3;
Bundle extraCommand(String commandName, in Bundle args) = 4;
boolean updateVisuals(in ICustomTabsCallback callback,
in Bundle bundle) = 5;
boolean requestPostMessageChannel(in ICustomTabsCallback callback,
in Uri postMessageOrigin) = 6;
int postMessage(in ICustomTabsCallback callback, String message,
in Bundle extras) = 7;
boolean validateRelationship(in ICustomTabsCallback callback,
int relation, in Uri origin, in Bundle extras) = 8;
}
AIDL
class CustomTabsConnectionService : CustomTabsService() {
override fun warmup(flags: Long): Boolean = false
override fun newSession(sessionToken: CustomTabsSessionToken?): Boolean = true
override fun mayLaunchUrl(
sessionToken: CustomTabsSessionToken?, url: Uri?, extras: Bundle?,
otherLikelyBundles: MutableList<Bundle>?): Boolean = true
override fun extraCommand(commandName: String?, args: Bundle?): Bundle? = null
override fun requestPostMessageChannel(
sessionToken: CustomTabsSessionToken?, postMessageOrigin: Uri?): Boolean = false
override fun postMessage(
sessionToken: CustomTabsSessionToken?, message: String?, extras: Bundle?
): Int = CustomTabsService.RESULT_FAILURE_DISALLOWED
override fun validateRelationship(
sessionToken: CustomTabsSessionToken?,
relation: Int, origin: Uri?, extras: Bundle?): Boolean = false
override fun updateVisuals(
sessionToken: CustomTabsSessionToken?, bundle: Bundle?): Boolean = false
}
起動先
複雑なので簡単な例で
AIDL
interface IRemoteService {
String sendMessage(String message);
}
起動先 class MainService : Service() {
override fun onBind(intent: Intent): IBinder {
return object : IRemoteService.Stub() {
override fun sendMessage(message: String): String {
return message
}
}
}
}
val intent = Intent(Const.ACTION_AIDL)
intent.setPackage("net.mm2d.aidl.app1")
bindService(intent, object : ServiceConnection {
override fun onServiceConnected(
name: ComponentName?, service: IBinder?) {
remoteService = IRemoteService.Stub.asInterface(service)
remoteService.sendMessage("message")
}
override fun onServiceDisconnected(name: ComponentName?) {
remoteService = null
}
}, Context.BIND_AUTO_CREATE)
別プロセスの
メソッドをコール
AIDL - メリット
•アプリ間でメソッドコールが可能
•引数・戻り値ともに使える
•データ型も柔軟
•プリミティブ型・String・Parcelable
•それらを要素とするList・Map
•比較的大きなデータも送受信可
AIDL - 注意点
•バインド先プロセスが生きている必要あり
•DeadObjectException
•スレッドセーフに作る
•アプリをまたいだメソッドコールはBinderス
レッドで実行される
•密結合にならないようによく考えて
•一度リリースしてしまうと修正ほぼ不可
Intent
CustomTabsIntent
ってどんなIntent?
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.putExtras(bundle);
intent.putExtra(...)
intent.setData(uri);
context.startActivity(intent, startAnimationBundle);
URIを開くIntent
+
Extra
Intent経由で渡せるパラメータ
•Action(String)
•Category(String)
•Data(Uri)
•Extra(Bundle)
Intent経由で渡せるパラメータ
•Action(String)
•Category(String)
•Data(Uri)
•Extra(Bundle)
ツールバー色・クローズアイコン
public Builder setToolbarColor(@ColorInt int color) {
mIntent.putExtra(EXTRA_TOOLBAR_COLOR, color);
return this;
}
val toolbarColor: Int =
intent.getIntExtra(EXTRA_TOOLBAR_COLOR, Color.WHITE)
起動元
起動先
public Builder setToolbarColor(@ColorInt int color) {
mIntent.putExtra(EXTRA_TOOLBAR_COLOR, color);
return this;
}
val toolbarColor: Int =
intent.getIntExtra(EXTRA_TOOLBAR_COLOR, Color.WHITE)
起動元
起動先
public Builder setCloseButtonIcon(@NonNull Bitmap icon) {
mIntent.putExtra(EXTRA_CLOSE_BUTTON_ICON, icon);
return this;
}
val closeIcon: Bitmap? =
intent.getParcelableExtra(EXTRA_CLOSE_BUTTON_ICON)
起動元
起動先
public Builder setCloseButtonIcon(@NonNull Bitmap icon) {
mIntent.putExtra(EXTRA_CLOSE_BUTTON_ICON, icon);
return this;
}
val closeIcon: Bitmap? =
intent.getParcelableExtra(EXTRA_CLOSE_BUTTON_ICON)
起動元
起動先
プリミティブ型
Parcelable
そのまま使える
TransactionTooLargeException
大きなデータを渡すことはできない
注意
コールバック
CustomTabsIntent.Builder(session)
...
.build()
.launchUrl(this, Uri.parse(url))
public Builder(@Nullable CustomTabsSession session) {
if (session != null)
mIntent.setPackage(session.getComponentName()
.getPackageName());
Bundle bundle = new Bundle();
BundleCompat.putBinder(
bundle, EXTRA_SESSION,
session == null ? null : session.getBinder());
mIntent.putExtras(bundle);
}
public Builder(@Nullable CustomTabsSession session) {
if (session != null)
mIntent.setPackage(session.getComponentName()
.getPackageName());
Bundle bundle = new Bundle();
BundleCompat.putBinder(
bundle, EXTRA_SESSION,
session == null ? null : session.getBinder());
mIntent.putExtras(bundle);
}
public final class CustomTabsSession {
...
/* package */ IBinder getBinder() {
return mCallback.asBinder();
}
IBinderをExtraで
渡すことができる
val callback: CustomTabsCallback? = try {
CustomTabsSessionToken
.getSessionTokenFromIntent(intent)?.callback
} catch (e: Exception) {
null
}
単純な例
interface ICallback1 {
void callback(String message);
}
val binder = BundleCompat.getBinder(
intent.extras, Const.EXTRA_CALLBACK1)
ICallback1.Stub.asInterface(binder).callback("message")
val callback = object : ICallback1.Stub() {
override fun callback(message: String?) {...}
}
intent.putExtras(Bundle().also {
BundleCompat.putBinder(it, Const.EXTRA_CALLBACK1, callback)
})
startActivity(intent)
AIDL
起動元
起動先
interface ICallback2 {
Bitmap callback();
}
val binder = BundleCompat.getBinder(
intent.extras, Const.EXTRA_CALLBACK2)
val bitmap = ICallback2.Stub.asInterface(binder).callback()
val callback = object : ICallback2.Stub() {
override fun callback(): Bitmap? {...}
}
intent.putExtras(Bundle().also {
BundleCompat.putBinder(it, Const.EXTRA_CALLBACK2, callback)
})
startActivity(intent)
AIDL
起動元
起動先
1MB以上
OK
ブラウザモード
CustomTabsモード
Activityを分ける
public static Intent setAlwaysUseBrowserUI(Intent intent) {
if (intent == null) intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS, true);
return intent;
}
public static boolean shouldAlwaysUseBrowserUI(Intent intent) {
return intent.getBooleanExtra(
EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS, false)
&& (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0;
}
起動元
起動先
public static Intent setAlwaysUseBrowserUI(Intent intent) {
if (intent == null) intent = new Intent(Intent.ACTION_VIEW);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS, true);
return intent;
}
public static boolean shouldAlwaysUseBrowserUI(Intent intent) {
return intent.getBooleanExtra(
EXTRA_USER_OPT_OUT_FROM_CUSTOM_TABS, false)
&& (intent.getFlags() & Intent.FLAG_ACTIVITY_NEW_TASK) != 0;
}
起動元
起動先
Intent-filterには
Extraの条件をつけられない
<activity android:name=".IntentDispatcher">
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
<data android:scheme="https"/>
</intent-filter>
</activity>
<activity android:name=".BrowserActivity"/>
<activity android:name=".CustomTabsActivity"/>
踏み台
Activity
class IntentDispatcher : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val launchIntent = intent ?: return
if (isCustomTabIntent(launchIntent)) {
launchIntent.setClass(this, CustomTabsActivity::class.java)
} else {
launchIntent.setClass(this, BrowserActivity::class.java)
}
startActivity(launchIntent)
finish()
}
private fun isCustomTabIntent(intent: Intent): Boolean {
if (CustomTabsIntent.shouldAlwaysUseBrowserUI(intent)) return false
if (!intent.hasExtra(CustomTabsIntent.EXTRA_SESSION)) return false
return intent.data != null
}
class IntentDispatcher : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val launchIntent = intent ?: return
if (isCustomTabIntent(launchIntent)) {
launchIntent.setClass(this, CustomTabsActivity::class.java)
} else {
launchIntent.setClass(this, BrowserActivity::class.java)
}
startActivity(launchIntent)
finish()
}
private fun isCustomTabIntent(intent: Intent): Boolean {
if (CustomTabsIntent.shouldAlwaysUseBrowserUI(intent)) return false
if (!intent.hasExtra(CustomTabsIntent.EXTRA_SESSION)) return false
return intent.data != null
}
class IntentDispatcher : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val launchIntent = intent ?: return
if (isCustomTabIntent(launchIntent)) {
launchIntent.setClass(this, CustomTabsActivity::class.java)
} else {
launchIntent.setClass(this, BrowserActivity::class.java)
}
startActivity(launchIntent)
finish()
}
private fun isCustomTabIntent(intent: Intent): Boolean {
if (CustomTabsIntent.shouldAlwaysUseBrowserUI(intent)) return false
if (!intent.hasExtra(CustomTabsIntent.EXTRA_SESSION)) return false
return intent.data != null
}
アニメーション
CustomTabsIntent.Builder(session)
.setStartAnimations(this,
R.anim.slide_in_right, R.anim.slide_out_left)
.setExitAnimations(this,
R.anim.slide_in_left, R.anim.slide_out_right)
.build()
.launchUrl(this, Uri.parse(url))
public Builder setStartAnimations(
@NonNull Context context,
@AnimRes int enterResId,
@AnimRes int exitResId) {
mStartAnimationBundle =
ActivityOptionsCompat.makeCustomAnimation(
context, enterResId, exitResId).toBundle();
return this;
}
public void launchUrl(Context context, Uri url) {
intent.setData(url);
ContextCompat.startActivity(context,
intent, startAnimationBundle);
}
public Builder setExitAnimations(
@NonNull Context context,
@AnimRes int enterResId,
@AnimRes int exitResId) {
Bundle bundle = ActivityOptionsCompat.makeCustomAnimation(
context, enterResId, exitResId).toBundle();
mIntent.putExtra(EXTRA_EXIT_ANIMATION_BUNDLE, bundle);
return this;
}
b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
リソースID
val animationBundle = intent
.getBundleExtra(EXTRA_EXIT_ANIMATION_BUNDLE)
enterAnimationRes = animationBundle
.getInt(BUNDLE_ENTER_ANIMATION_RESOURCE)
exitAnimationRes = animationBundle
.getInt(BUNDLE_EXIT_ANIMATION_RESOURCE)
起動先
別アプリの
リソースID
val resources = packageManager
.getResourcesForApplication(packageName)
val icon = resources.getDrawable(id, theme)
override fun finish() {
super.finish()
overridePackageName = true
overridePendingTransition(
reader.enterAnimationRes, reader.exitAnimationRes)
overridePackageName = false
}
override fun getPackageName(): String {
if (overridePackageName) return reader.clientPackageName
?: super.getPackageName()
return super.getPackageName()
}
起動先
getPackage()を書き換えると
他アプリのリソースで
終了アニメーションが実現
任意のUI(View)
public Builder setSecondaryToolbarViews(
@NonNull RemoteViews remoteViews,
@Nullable int[] clickableIDs,
@Nullable PendingIntent pendingIntent) {
mIntent.putExtra(EXTRA_REMOTEVIEWS, remoteViews);
mIntent.putExtra(EXTRA_REMOTEVIEWS_VIEW_IDS, clickableIDs);
mIntent.putExtra(EXTRA_REMOTEVIEWS_PENDINGINTENT, pendingIntent);
return this;
}
public Builder setSecondaryToolbarViews(
@NonNull RemoteViews remoteViews,
@Nullable int[] clickableIDs,
@Nullable PendingIntent pendingIntent) {
mIntent.putExtra(EXTRA_REMOTEVIEWS, remoteViews);
mIntent.putExtra(EXTRA_REMOTEVIEWS_VIEW_IDS, clickableIDs);
mIntent.putExtra(EXTRA_REMOTEVIEWS_PENDINGINTENT, pendingIntent);
return this;
}
val remoteViews: RemoteViews? =
intent.getParcelableExtra(EXTRA_REMOTEVIEWS)
val remoteViewsClickableIDs: IntArray? =
intent.getIntArrayExtra(EXTRA_REMOTEVIEWS_VIEW_IDS)
val remoteViewsPendingIntent: PendingIntent? =
intent.getParcelableExtra(EXTRA_REMOTEVIEWS_PENDINGINTENT)
起動先
RemoteViews
val views = remoteViews.apply(applicationContext, toolbar)
toolbar.addView(views)
reader.remoteViewsClickableIDs?.forEach {
views.findViewById<View>(it)?.setOnClickListener { v ->
sendPendingIntentOnClick(pendingIntent, v.id)
}
}
起動先
val views = remoteViews.apply(applicationContext, toolbar)
toolbar.addView(views)
reader.remoteViewsClickableIDs?.forEach {
views.findViewById<View>(it)?.setOnClickListener { v ->
sendPendingIntentOnClick(pendingIntent, v.id)
}
}
起動先
RemoteViews
•Viewの構築情報をParcelableにしたもの
•applyしてしまえば、ただのView
•標準で使える仕組みは強い
•表現力が物足りないなどであれば独自の
仕組みを作るのもありかも
オプションメニュー
public Builder addMenuItem(
@NonNull String label,
@NonNull PendingIntent pendingIntent) {
if (mMenuItems == null) mMenuItems = new ArrayList<>();
Bundle bundle = new Bundle();
bundle.putString(KEY_MENU_ITEM_TITLE, label);
bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
mMenuItems.add(bundle);
return this;
}
public CustomTabsIntent build() {
if (mMenuItems != null) {
mIntent.putParcelableArrayListExtra(
CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems);
}
...
}
public Builder addMenuItem(
@NonNull String label,
@NonNull PendingIntent pendingIntent) {
if (mMenuItems == null) mMenuItems = new ArrayList<>();
Bundle bundle = new Bundle();
bundle.putString(KEY_MENU_ITEM_TITLE, label);
bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
mMenuItems.add(bundle);
return this;
}
public CustomTabsIntent build() {
if (mMenuItems != null) {
mIntent.putParcelableArrayListExtra(
CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems);
}
...
}
public Builder addMenuItem(
@NonNull String label,
@NonNull PendingIntent pendingIntent) {
if (mMenuItems == null) mMenuItems = new ArrayList<>();
Bundle bundle = new Bundle();
bundle.putString(KEY_MENU_ITEM_TITLE, label);
bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
mMenuItems.add(bundle);
return this;
}
public CustomTabsIntent build() {
if (mMenuItems != null) {
mIntent.putParcelableArrayListExtra(
CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems);
}
...
}
fun makeMenuParamsList(intent: Intent): List<MenuParams> {
return intent
.getParcelableArrayListExtra<Bundle>(EXTRA_MENU_ITEMS)
?.map { makeMenuParams(it) } ?: emptyList()
}
fun makeMenuParams(bundle: Bundle): MenuParams? {
return MenuParams(
bundle.getString(KEY_MENU_ITEM_TITLE),
bundle.getParcelable(KEY_PENDING_INTENT))
}
起動先
fun makeMenuParamsList(intent: Intent): List<MenuParams> {
return intent
.getParcelableArrayListExtra<Bundle>(EXTRA_MENU_ITEMS)
?.map { makeMenuParams(it) } ?: emptyList()
}
fun makeMenuParams(bundle: Bundle): MenuParams? {
return MenuParams(
bundle.getString(KEY_MENU_ITEM_TITLE),
bundle.getParcelable(KEY_PENDING_INTENT))
}
起動先
fun makeMenuParamsList(intent: Intent): List<MenuParams> {
return intent
.getParcelableArrayListExtra<Bundle>(EXTRA_MENU_ITEMS)
?.map { makeMenuParams(it) } ?: emptyList()
}
fun makeMenuParams(bundle: Bundle): MenuParams? {
return MenuParams(
bundle.getString(KEY_MENU_ITEM_TITLE),
bundle.getParcelable(KEY_PENDING_INTENT))
}
起動先
PendingIntent
fun onSelectCustomMenu(index: Int) {
reader.menuParamsList[index]
.pendingIntent?.send()
}
fun sendPendingIntentWithUrl(pendingIntent: PendingIntent) {
val addedIntent = Intent().also {
it.data = Uri.parse(web_view.url)
}
pendingIntent.send(this, 0, addedIntent)
}
fun onSelectCustomMenu(index: Int) {
reader.menuParamsList[index]
.pendingIntent?.send()
}
fun sendPendingIntentWithUrl(pendingIntent: PendingIntent) {
val addedIntent = Intent().also {
it.data = Uri.parse(web_view.url)
}
pendingIntent.send(this, 0, addedIntent)
}
fun onSelectCustomMenu(index: Int) {
reader.menuParamsList[index]
.pendingIntent?.send()
}
fun sendPendingIntentWithUrl(pendingIntent: PendingIntent) {
val addedIntent = Intent().also {
it.data = Uri.parse(web_view.url)
}
pendingIntent.send(this, 0, addedIntent)
}
Intent ≠ Parcelable
PendingIntent = Parcelable
まとめ
•プロトコルの宣言は intent-filter
• Intentは基本中の基本、使い方を極めよう
• AIDLは非常に強力だが利用は慎重に
• Parcelable/Bundle超重要、使い方を極めよう
Thank you

More Related Content

What's hot

Apache Arrow - データ処理ツールの次世代プラットフォーム
Apache Arrow - データ処理ツールの次世代プラットフォームApache Arrow - データ処理ツールの次世代プラットフォーム
Apache Arrow - データ処理ツールの次世代プラットフォームKouhei Sutou
 
Ingressの概要とLoadBalancerとの比較
Ingressの概要とLoadBalancerとの比較Ingressの概要とLoadBalancerとの比較
Ingressの概要とLoadBalancerとの比較Mei Nakamura
 
Spring Boot ユーザの方のための Quarkus 入門
Spring Boot ユーザの方のための Quarkus 入門Spring Boot ユーザの方のための Quarkus 入門
Spring Boot ユーザの方のための Quarkus 入門tsukasamannen
 
今なら間に合う分散型IDとEntra Verified ID
今なら間に合う分散型IDとEntra Verified ID今なら間に合う分散型IDとEntra Verified ID
今なら間に合う分散型IDとEntra Verified IDNaohiro Fujie
 
20180704 AWS Black Belt Online Seminar Amazon Elastic File System (Amazon EFS...
20180704 AWS Black Belt Online Seminar Amazon Elastic File System (Amazon EFS...20180704 AWS Black Belt Online Seminar Amazon Elastic File System (Amazon EFS...
20180704 AWS Black Belt Online Seminar Amazon Elastic File System (Amazon EFS...Amazon Web Services Japan
 
[AKIBA.AWS] AWS Elemental MediaConvertから学ぶコーデック入門
[AKIBA.AWS] AWS Elemental MediaConvertから学ぶコーデック入門[AKIBA.AWS] AWS Elemental MediaConvertから学ぶコーデック入門
[AKIBA.AWS] AWS Elemental MediaConvertから学ぶコーデック入門Shuji Kikuchi
 
AngularとSpring Bootで作るSPA + RESTful Web Serviceアプリケーション
AngularとSpring Bootで作るSPA + RESTful Web ServiceアプリケーションAngularとSpring Bootで作るSPA + RESTful Web Serviceアプリケーション
AngularとSpring Bootで作るSPA + RESTful Web Serviceアプリケーションssuser070fa9
 
Jmespathをもっと広めたい
Jmespathをもっと広めたいJmespathをもっと広めたい
Jmespathをもっと広めたいTetsunori Nishizawa
 
[社内勉強会]ELBとALBと数万スパイク負荷テスト
[社内勉強会]ELBとALBと数万スパイク負荷テスト[社内勉強会]ELBとALBと数万スパイク負荷テスト
[社内勉強会]ELBとALBと数万スパイク負荷テストTakahiro Moteki
 
Keycloak拡張入門
Keycloak拡張入門Keycloak拡張入門
Keycloak拡張入門Hiroyuki Wada
 
長期運用タイトルの GCP 移行実例とグレンジのこれから | Google Cloud INSIDE Games & Apps
長期運用タイトルの GCP 移行実例とグレンジのこれから | Google Cloud INSIDE Games & Apps 長期運用タイトルの GCP 移行実例とグレンジのこれから | Google Cloud INSIDE Games & Apps
長期運用タイトルの GCP 移行実例とグレンジのこれから | Google Cloud INSIDE Games & Apps Google Cloud Platform - Japan
 
パスワード氾濫時代のID管理とは? ~最新のOpenIDが目指すユーザー認証の効率的な強化~
パスワード氾濫時代のID管理とは? ~最新のOpenIDが目指すユーザー認証の効率的な強化~パスワード氾濫時代のID管理とは? ~最新のOpenIDが目指すユーザー認証の効率的な強化~
パスワード氾濫時代のID管理とは? ~最新のOpenIDが目指すユーザー認証の効率的な強化~Tatsuo Kudo
 
Lambda layerをDeployする方法を調べる
Lambda layerをDeployする方法を調べるLambda layerをDeployする方法を調べる
Lambda layerをDeployする方法を調べるshotaueda3
 
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くしたNginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くしたtoshi_pp
 
インフラCICDの勘所
インフラCICDの勘所インフラCICDの勘所
インフラCICDの勘所Toru Makabe
 
DockerとPodmanの比較
DockerとPodmanの比較DockerとPodmanの比較
DockerとPodmanの比較Akihiro Suda
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪Takuto Wada
 
AWS Black Belt Online Seminar 2017 Amazon S3
AWS Black Belt Online Seminar 2017 Amazon S3AWS Black Belt Online Seminar 2017 Amazon S3
AWS Black Belt Online Seminar 2017 Amazon S3Amazon Web Services Japan
 

What's hot (20)

Apache Arrow - データ処理ツールの次世代プラットフォーム
Apache Arrow - データ処理ツールの次世代プラットフォームApache Arrow - データ処理ツールの次世代プラットフォーム
Apache Arrow - データ処理ツールの次世代プラットフォーム
 
分散トレーシング技術について(Open tracingやjaeger)
分散トレーシング技術について(Open tracingやjaeger)分散トレーシング技術について(Open tracingやjaeger)
分散トレーシング技術について(Open tracingやjaeger)
 
Ingressの概要とLoadBalancerとの比較
Ingressの概要とLoadBalancerとの比較Ingressの概要とLoadBalancerとの比較
Ingressの概要とLoadBalancerとの比較
 
Spring Boot ユーザの方のための Quarkus 入門
Spring Boot ユーザの方のための Quarkus 入門Spring Boot ユーザの方のための Quarkus 入門
Spring Boot ユーザの方のための Quarkus 入門
 
今なら間に合う分散型IDとEntra Verified ID
今なら間に合う分散型IDとEntra Verified ID今なら間に合う分散型IDとEntra Verified ID
今なら間に合う分散型IDとEntra Verified ID
 
20180704 AWS Black Belt Online Seminar Amazon Elastic File System (Amazon EFS...
20180704 AWS Black Belt Online Seminar Amazon Elastic File System (Amazon EFS...20180704 AWS Black Belt Online Seminar Amazon Elastic File System (Amazon EFS...
20180704 AWS Black Belt Online Seminar Amazon Elastic File System (Amazon EFS...
 
[AKIBA.AWS] AWS Elemental MediaConvertから学ぶコーデック入門
[AKIBA.AWS] AWS Elemental MediaConvertから学ぶコーデック入門[AKIBA.AWS] AWS Elemental MediaConvertから学ぶコーデック入門
[AKIBA.AWS] AWS Elemental MediaConvertから学ぶコーデック入門
 
AngularとSpring Bootで作るSPA + RESTful Web Serviceアプリケーション
AngularとSpring Bootで作るSPA + RESTful Web ServiceアプリケーションAngularとSpring Bootで作るSPA + RESTful Web Serviceアプリケーション
AngularとSpring Bootで作るSPA + RESTful Web Serviceアプリケーション
 
Jmespathをもっと広めたい
Jmespathをもっと広めたいJmespathをもっと広めたい
Jmespathをもっと広めたい
 
[社内勉強会]ELBとALBと数万スパイク負荷テスト
[社内勉強会]ELBとALBと数万スパイク負荷テスト[社内勉強会]ELBとALBと数万スパイク負荷テスト
[社内勉強会]ELBとALBと数万スパイク負荷テスト
 
Keycloak拡張入門
Keycloak拡張入門Keycloak拡張入門
Keycloak拡張入門
 
長期運用タイトルの GCP 移行実例とグレンジのこれから | Google Cloud INSIDE Games & Apps
長期運用タイトルの GCP 移行実例とグレンジのこれから | Google Cloud INSIDE Games & Apps 長期運用タイトルの GCP 移行実例とグレンジのこれから | Google Cloud INSIDE Games & Apps
長期運用タイトルの GCP 移行実例とグレンジのこれから | Google Cloud INSIDE Games & Apps
 
パスワード氾濫時代のID管理とは? ~最新のOpenIDが目指すユーザー認証の効率的な強化~
パスワード氾濫時代のID管理とは? ~最新のOpenIDが目指すユーザー認証の効率的な強化~パスワード氾濫時代のID管理とは? ~最新のOpenIDが目指すユーザー認証の効率的な強化~
パスワード氾濫時代のID管理とは? ~最新のOpenIDが目指すユーザー認証の効率的な強化~
 
Lambda layerをDeployする方法を調べる
Lambda layerをDeployする方法を調べるLambda layerをDeployする方法を調べる
Lambda layerをDeployする方法を調べる
 
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くしたNginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
NginxとLuaを用いた動的なリバースプロキシでデプロイを 100 倍速くした
 
インフラCICDの勘所
インフラCICDの勘所インフラCICDの勘所
インフラCICDの勘所
 
DockerとPodmanの比較
DockerとPodmanの比較DockerとPodmanの比較
DockerとPodmanの比較
 
Keycloak入門
Keycloak入門Keycloak入門
Keycloak入門
 
例外設計における大罪
例外設計における大罪例外設計における大罪
例外設計における大罪
 
AWS Black Belt Online Seminar 2017 Amazon S3
AWS Black Belt Online Seminar 2017 Amazon S3AWS Black Belt Online Seminar 2017 Amazon S3
AWS Black Belt Online Seminar 2017 Amazon S3
 

Similar to DroidKaigi 2019 Chrome Custom Tabsの仕組みから学ぶプロセス間通信

3分でサーバオペレーションコマンドを作る技術
3分でサーバオペレーションコマンドを作る技術3分でサーバオペレーションコマンドを作る技術
3分でサーバオペレーションコマンドを作る技術Kei IWASAKI
 
Go言語によるwebアプリの作り方
Go言語によるwebアプリの作り方Go言語によるwebアプリの作り方
Go言語によるwebアプリの作り方Yasutaka Kawamoto
 
TypeScript と Visual Studio Code
TypeScript と Visual Studio CodeTypeScript と Visual Studio Code
TypeScript と Visual Studio CodeAkira Inoue
 
DEV-010_エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法
DEV-010_エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法DEV-010_エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法
DEV-010_エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法decode2016
 
【18-C-4】Google App Engine - 無限の彼方へ
【18-C-4】Google App Engine - 無限の彼方へ【18-C-4】Google App Engine - 無限の彼方へ
【18-C-4】Google App Engine - 無限の彼方へDevelopers Summit
 
ヒカルのGo 資料 Webアプリケーションの作り方
ヒカルのGo 資料 Webアプリケーションの作り方ヒカルのGo 資料 Webアプリケーションの作り方
ヒカルのGo 資料 Webアプリケーションの作り方Yosuke Furukawa
 
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyotoGo言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyotoShoot Morii
 
Chrome Extensionsの基本とデザインパターン
Chrome Extensionsの基本とデザインパターンChrome Extensionsの基本とデザインパターン
Chrome Extensionsの基本とデザインパターンYoichiro Tanaka
 
Couchbase MeetUP Tokyo - #11 Omoidenote
Couchbase MeetUP Tokyo - #11 OmoidenoteCouchbase MeetUP Tokyo - #11 Omoidenote
Couchbase MeetUP Tokyo - #11 Omoidenotekitsugi
 

Similar to DroidKaigi 2019 Chrome Custom Tabsの仕組みから学ぶプロセス間通信 (10)

3分でサーバオペレーションコマンドを作る技術
3分でサーバオペレーションコマンドを作る技術3分でサーバオペレーションコマンドを作る技術
3分でサーバオペレーションコマンドを作る技術
 
Go言語によるwebアプリの作り方
Go言語によるwebアプリの作り方Go言語によるwebアプリの作り方
Go言語によるwebアプリの作り方
 
Boost Tour 1.50.0
Boost Tour 1.50.0Boost Tour 1.50.0
Boost Tour 1.50.0
 
TypeScript と Visual Studio Code
TypeScript と Visual Studio CodeTypeScript と Visual Studio Code
TypeScript と Visual Studio Code
 
DEV-010_エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法
DEV-010_エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法DEV-010_エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法
DEV-010_エンプラ系業務 Web アプリ開発に効く! 実践的 SPA 型モダン Web アプリ開発の選択手法
 
【18-C-4】Google App Engine - 無限の彼方へ
【18-C-4】Google App Engine - 無限の彼方へ【18-C-4】Google App Engine - 無限の彼方へ
【18-C-4】Google App Engine - 無限の彼方へ
 
ヒカルのGo 資料 Webアプリケーションの作り方
ヒカルのGo 資料 Webアプリケーションの作り方ヒカルのGo 資料 Webアプリケーションの作り方
ヒカルのGo 資料 Webアプリケーションの作り方
 
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyotoGo言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
Go言語入門者が Webアプリケーション を作ってみた話 #devfest #gdgkyoto
 
Chrome Extensionsの基本とデザインパターン
Chrome Extensionsの基本とデザインパターンChrome Extensionsの基本とデザインパターン
Chrome Extensionsの基本とデザインパターン
 
Couchbase MeetUP Tokyo - #11 Omoidenote
Couchbase MeetUP Tokyo - #11 OmoidenoteCouchbase MeetUP Tokyo - #11 Omoidenote
Couchbase MeetUP Tokyo - #11 Omoidenote
 

Recently uploaded

論文紹介:Video-GroundingDINO: Towards Open-Vocabulary Spatio-Temporal Video Groun...
論文紹介:Video-GroundingDINO: Towards Open-Vocabulary Spatio-Temporal Video Groun...論文紹介:Video-GroundingDINO: Towards Open-Vocabulary Spatio-Temporal Video Groun...
論文紹介:Video-GroundingDINO: Towards Open-Vocabulary Spatio-Temporal Video Groun...Toru Tamaki
 
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)Hiroshi Tomioka
 
新人研修 後半 2024/04/26の勉強会で発表されたものです。
新人研修 後半        2024/04/26の勉強会で発表されたものです。新人研修 後半        2024/04/26の勉強会で発表されたものです。
新人研修 後半 2024/04/26の勉強会で発表されたものです。iPride Co., Ltd.
 
LoRaWAN スマート距離検出デバイスDS20L日本語マニュアル
LoRaWAN スマート距離検出デバイスDS20L日本語マニュアルLoRaWAN スマート距離検出デバイスDS20L日本語マニュアル
LoRaWAN スマート距離検出デバイスDS20L日本語マニュアルCRI Japan, Inc.
 
Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。iPride Co., Ltd.
 
論文紹介: The Surprising Effectiveness of PPO in Cooperative Multi-Agent Games
論文紹介: The Surprising Effectiveness of PPO in Cooperative Multi-Agent Games論文紹介: The Surprising Effectiveness of PPO in Cooperative Multi-Agent Games
論文紹介: The Surprising Effectiveness of PPO in Cooperative Multi-Agent Gamesatsushi061452
 
LoRaWANスマート距離検出センサー DS20L カタログ LiDARデバイス
LoRaWANスマート距離検出センサー  DS20L  カタログ  LiDARデバイスLoRaWANスマート距離検出センサー  DS20L  カタログ  LiDARデバイス
LoRaWANスマート距離検出センサー DS20L カタログ LiDARデバイスCRI Japan, Inc.
 
Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。iPride Co., Ltd.
 
論文紹介:Selective Structured State-Spaces for Long-Form Video Understanding
論文紹介:Selective Structured State-Spaces for Long-Form Video Understanding論文紹介:Selective Structured State-Spaces for Long-Form Video Understanding
論文紹介:Selective Structured State-Spaces for Long-Form Video UnderstandingToru Tamaki
 
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NTT DATA Technology & Innovation
 
Observabilityは従来型の監視と何が違うのか(キンドリルジャパン社内勉強会:2022年10月27日発表)
Observabilityは従来型の監視と何が違うのか(キンドリルジャパン社内勉強会:2022年10月27日発表)Observabilityは従来型の監視と何が違うのか(キンドリルジャパン社内勉強会:2022年10月27日発表)
Observabilityは従来型の監視と何が違うのか(キンドリルジャパン社内勉強会:2022年10月27日発表)Hiroshi Tomioka
 

Recently uploaded (11)

論文紹介:Video-GroundingDINO: Towards Open-Vocabulary Spatio-Temporal Video Groun...
論文紹介:Video-GroundingDINO: Towards Open-Vocabulary Spatio-Temporal Video Groun...論文紹介:Video-GroundingDINO: Towards Open-Vocabulary Spatio-Temporal Video Groun...
論文紹介:Video-GroundingDINO: Towards Open-Vocabulary Spatio-Temporal Video Groun...
 
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
 
新人研修 後半 2024/04/26の勉強会で発表されたものです。
新人研修 後半        2024/04/26の勉強会で発表されたものです。新人研修 後半        2024/04/26の勉強会で発表されたものです。
新人研修 後半 2024/04/26の勉強会で発表されたものです。
 
LoRaWAN スマート距離検出デバイスDS20L日本語マニュアル
LoRaWAN スマート距離検出デバイスDS20L日本語マニュアルLoRaWAN スマート距離検出デバイスDS20L日本語マニュアル
LoRaWAN スマート距離検出デバイスDS20L日本語マニュアル
 
Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その32024/04/26の勉強会で発表されたものです。
 
論文紹介: The Surprising Effectiveness of PPO in Cooperative Multi-Agent Games
論文紹介: The Surprising Effectiveness of PPO in Cooperative Multi-Agent Games論文紹介: The Surprising Effectiveness of PPO in Cooperative Multi-Agent Games
論文紹介: The Surprising Effectiveness of PPO in Cooperative Multi-Agent Games
 
LoRaWANスマート距離検出センサー DS20L カタログ LiDARデバイス
LoRaWANスマート距離検出センサー  DS20L  カタログ  LiDARデバイスLoRaWANスマート距離検出センサー  DS20L  カタログ  LiDARデバイス
LoRaWANスマート距離検出センサー DS20L カタログ LiDARデバイス
 
Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。
Amazon SES を勉強してみる その22024/04/26の勉強会で発表されたものです。
 
論文紹介:Selective Structured State-Spaces for Long-Form Video Understanding
論文紹介:Selective Structured State-Spaces for Long-Form Video Understanding論文紹介:Selective Structured State-Spaces for Long-Form Video Understanding
論文紹介:Selective Structured State-Spaces for Long-Form Video Understanding
 
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
NewSQLの可用性構成パターン(OCHaCafe Season 8 #4 発表資料)
 
Observabilityは従来型の監視と何が違うのか(キンドリルジャパン社内勉強会:2022年10月27日発表)
Observabilityは従来型の監視と何が違うのか(キンドリルジャパン社内勉強会:2022年10月27日発表)Observabilityは従来型の監視と何が違うのか(キンドリルジャパン社内勉強会:2022年10月27日発表)
Observabilityは従来型の監視と何が違うのか(キンドリルジャパン社内勉強会:2022年10月27日発表)
 

DroidKaigi 2019 Chrome Custom Tabsの仕組みから学ぶプロセス間通信