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

Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法についてYuji Otani
 
Project Loom - 限定継続と軽量スレッド -
Project Loom - 限定継続と軽量スレッド - Project Loom - 限定継続と軽量スレッド -
Project Loom - 限定継続と軽量スレッド - Yuichi Sakuraba
 
マイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDay
マイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDayマイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDay
マイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDay都元ダイスケ Miyamoto
 
コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」Masahito Zembutsu
 
OpenID Connect 入門 〜コンシューマーにおけるID連携のトレンド〜
OpenID Connect 入門 〜コンシューマーにおけるID連携のトレンド〜OpenID Connect 入門 〜コンシューマーにおけるID連携のトレンド〜
OpenID Connect 入門 〜コンシューマーにおけるID連携のトレンド〜Masaru Kurahayashi
 
OAuth2.0によるWeb APIの保護
OAuth2.0によるWeb APIの保護OAuth2.0によるWeb APIの保護
OAuth2.0によるWeb APIの保護Naohiro Fujie
 
20180914 security iotlt#1_ほんとうにあった怖い話_aws_iot編
20180914 security iotlt#1_ほんとうにあった怖い話_aws_iot編20180914 security iotlt#1_ほんとうにあった怖い話_aws_iot編
20180914 security iotlt#1_ほんとうにあった怖い話_aws_iot編Tatsuya (達也) Katsuhara (勝原)
 
Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Taku Miyakawa
 
The Usage and Patterns of MagicOnion
The Usage and Patterns of MagicOnionThe Usage and Patterns of MagicOnion
The Usage and Patterns of MagicOnionYoshifumi Kawai
 
Cognitive Complexity でコードの複雑さを定量的に計測しよう
Cognitive Complexity でコードの複雑さを定量的に計測しようCognitive Complexity でコードの複雑さを定量的に計測しよう
Cognitive Complexity でコードの複雑さを定量的に計測しようShuto Suzuki
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!mosa siru
 
これからのネイティブアプリにおけるOpenID Connectの活用
これからのネイティブアプリにおけるOpenID Connectの活用これからのネイティブアプリにおけるOpenID Connectの活用
これからのネイティブアプリにおけるOpenID Connectの活用Masaru Kurahayashi
 
Keycloak & midPoint の紹介
Keycloak & midPoint の紹介Keycloak & midPoint の紹介
Keycloak & midPoint の紹介Hiroyuki Wada
 
MagicOnion~C#でゲームサーバを開発しよう~
MagicOnion~C#でゲームサーバを開発しよう~MagicOnion~C#でゲームサーバを開発しよう~
MagicOnion~C#でゲームサーバを開発しよう~torisoup
 
Twitterのsnowflakeについて
TwitterのsnowflakeについてTwitterのsnowflakeについて
Twitterのsnowflakeについてmoai kids
 
Keycloak拡張入門
Keycloak拡張入門Keycloak拡張入門
Keycloak拡張入門Hiroyuki Wada
 
初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!Tetsutaro Watanabe
 
イベント駆動プログラミングとI/O多重化
イベント駆動プログラミングとI/O多重化イベント駆動プログラミングとI/O多重化
イベント駆動プログラミングとI/O多重化Gosuke Miyashita
 

What's hot (20)

Redisの特徴と活用方法について
Redisの特徴と活用方法についてRedisの特徴と活用方法について
Redisの特徴と活用方法について
 
Project Loom - 限定継続と軽量スレッド -
Project Loom - 限定継続と軽量スレッド - Project Loom - 限定継続と軽量スレッド -
Project Loom - 限定継続と軽量スレッド -
 
AWS CognitoからAuth0への移行パターン4つ
AWS CognitoからAuth0への移行パターン4つAWS CognitoからAuth0への移行パターン4つ
AWS CognitoからAuth0への移行パターン4つ
 
マイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDay
マイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDayマイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDay
マイクロサービス時代の認証と認可 - AWS Dev Day Tokyo 2018 #AWSDevDay
 
コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」コンテナの作り方「Dockerは裏方で何をしているのか?」
コンテナの作り方「Dockerは裏方で何をしているのか?」
 
OpenID Connect 入門 〜コンシューマーにおけるID連携のトレンド〜
OpenID Connect 入門 〜コンシューマーにおけるID連携のトレンド〜OpenID Connect 入門 〜コンシューマーにおけるID連携のトレンド〜
OpenID Connect 入門 〜コンシューマーにおけるID連携のトレンド〜
 
Docker Compose 徹底解説
Docker Compose 徹底解説Docker Compose 徹底解説
Docker Compose 徹底解説
 
OAuth2.0によるWeb APIの保護
OAuth2.0によるWeb APIの保護OAuth2.0によるWeb APIの保護
OAuth2.0によるWeb APIの保護
 
20180914 security iotlt#1_ほんとうにあった怖い話_aws_iot編
20180914 security iotlt#1_ほんとうにあった怖い話_aws_iot編20180914 security iotlt#1_ほんとうにあった怖い話_aws_iot編
20180914 security iotlt#1_ほんとうにあった怖い話_aws_iot編
 
Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方Javaのログ出力: 道具と考え方
Javaのログ出力: 道具と考え方
 
The Usage and Patterns of MagicOnion
The Usage and Patterns of MagicOnionThe Usage and Patterns of MagicOnion
The Usage and Patterns of MagicOnion
 
Cognitive Complexity でコードの複雑さを定量的に計測しよう
Cognitive Complexity でコードの複雑さを定量的に計測しようCognitive Complexity でコードの複雑さを定量的に計測しよう
Cognitive Complexity でコードの複雑さを定量的に計測しよう
 
マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!マイクロにしすぎた結果がこれだよ!
マイクロにしすぎた結果がこれだよ!
 
これからのネイティブアプリにおけるOpenID Connectの活用
これからのネイティブアプリにおけるOpenID Connectの活用これからのネイティブアプリにおけるOpenID Connectの活用
これからのネイティブアプリにおけるOpenID Connectの活用
 
Keycloak & midPoint の紹介
Keycloak & midPoint の紹介Keycloak & midPoint の紹介
Keycloak & midPoint の紹介
 
MagicOnion~C#でゲームサーバを開発しよう~
MagicOnion~C#でゲームサーバを開発しよう~MagicOnion~C#でゲームサーバを開発しよう~
MagicOnion~C#でゲームサーバを開発しよう~
 
Twitterのsnowflakeについて
TwitterのsnowflakeについてTwitterのsnowflakeについて
Twitterのsnowflakeについて
 
Keycloak拡張入門
Keycloak拡張入門Keycloak拡張入門
Keycloak拡張入門
 
初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!初心者向けMongoDBのキホン!
初心者向けMongoDBのキホン!
 
イベント駆動プログラミングとI/O多重化
イベント駆動プログラミングとI/O多重化イベント駆動プログラミングとI/O多重化
イベント駆動プログラミングとI/O多重化
 

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

TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案sugiuralab
 
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...博三 太田
 
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineerYuki Kikuchi
 
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?akihisamiyanaga1
 
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdfクラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdfFumieNakayama
 
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)Hiroshi Tomioka
 
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdfAWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdfFumieNakayama
 
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)UEHARA, Tetsutaro
 

Recently uploaded (8)

TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
TataPixel: 畳の異方性を利用した切り替え可能なディスプレイの提案
 
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察  ~Text-to-MusicとText-To-ImageかつImage-to-Music...
モーダル間の変換後の一致性とジャンル表を用いた解釈可能性の考察 ~Text-to-MusicとText-To-ImageかつImage-to-Music...
 
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
自分史上一番早い2024振り返り〜コロナ後、仕事は通常ペースに戻ったか〜 by IoT fullstack engineer
 
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
CTO, VPoE, テックリードなどリーダーポジションに登用したくなるのはどんな人材か?
 
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdfクラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
クラウドネイティブなサーバー仮想化基盤 - OpenShift Virtualization.pdf
 
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
業務で生成AIを活用したい人のための生成AI入門講座(社外公開版:キンドリルジャパン社内勉強会:2024年4月発表)
 
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdfAWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
AWS の OpenShift サービス (ROSA) を使った OpenShift Virtualizationの始め方.pdf
 
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
デジタル・フォレンジックの最新動向(2024年4月27日情洛会総会特別講演スライド)
 

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