Codelabsの Direct Share to an Android app (eventタグ:Android Dev Summit 2019) を元にDirect Share(ダイレクトシェア)について説明します。
Direct Share とは
Android 6.0 から導入された機能で、アプリからコンテンツを共有する時に直接共有する相手を選択することができるというものです。この機能を使わない場合は、まず共有するアプリを選択した上で連絡先を指定する必要があります。
Android 6.0 (APIレベル23) では ChooserTargetService
を利用する手法でしたが、 Android 10 (APIレベル20) では ChooserTargetService DirectShare API が Sharing Shortcuts API に置き換えられました。こちらではターゲットの読み込み時間を短縮することができます。
ChooserTargetService DirectShare API と Sharing Shortcuts API
旧式の DirectShare API ではプルモデルが使用されていたのに対し、新式の Sharing Shortcuts API ではプッシュモデルが使用されています。これによりシェアターゲットを取得するプロセスが大幅に高速化されています。新式の API を使う場合は、シェアターゲットのリストを事前に用意し、例えば新しい連絡先が追加された時などにそのリストを更新する必要があります。
新式 API に書き換えないといけないわけではありません。が、Android 10 以降の Android Sharesheet では新式 API で提供された共有ターゲットの方が優先されるようになるので、旧式 API だと他の共有ターゲットに埋もれてしまう可能性はあります。
後方互換性を保ちたいからと言って旧式 API と新式 API を併用すると望ましくない挙動が発生する恐れがあります。なので旧式 API の代わりに ShortcutManagerCompat
を使用するのが望ましいです。
今回作るもの
コードは Codelabs の Direct Share to an Android app を使います。
・プレーンなテキストが送信できる
・他アプリからテキストを共有しようとした時に、今回作ったアプリが選択肢として出てくる
というものです。
https://github.com/googlecodelabs/android-direct-share
この Codelabs ではすでに Direct Share 以外の部分は実装されていて、自分でコードを書く部分はこれくらいでした。
- 共有ショートカットを公開する
- 古いAndroidバージョンとの後方互換性を保ちつつ Direct Share 機能を使えるようにする
- コンテンツプレビューにタイトルとサムネイルを追加する
実装
shortcuts.xml
ここでは以下のことを宣言します。
- シェアするデータのタイプ
- 共有ショートカットのカテゴリ
- 共有インテントを扱うActivity
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
<share-target android:targetClass="com.example.android.directshare.SendMessageActivity">
<data android:mimeType="text/plain" />
<category android:name="com.example.android.directshare.category.TEXT_SHARE_TARGET" />
</share-target>
</shortcuts>
<category>
は公開済みショートカットと共有ターゲット定義を一致させるために使用するものです。この値は自由に決めて良く、また、1つの要素に複数のカテゴリを含めることができます。
AndroidManifest.xml
shortcuts.xml
を android.intent.action.MAIN
アクションと android.intent.category.LAUNCHER
カテゴリを intent-filter に持つアクティビティ内で宣言する必要があります。
<activity
android:name=".MainActivity"
android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<!-- Reference resource file where the app's shortcuts are defined -->
<meta-data
android:name="android.app.shortcuts"
android:resource="@xml/shortcuts" />
</activity>
SendMessageActivity
では <intent-filter>
タグ内で、共有する時に扱うタイプを定義する必要があります。 SendMessageActivity
は起動した時にインテントの内容を確認します。連絡先に関する情報がないときは SelectContactActivity
を起動して連絡先を選択し、その結果を onActivityResult
で取得します。
<activity
android:name=".SendMessageActivity"
android:label="@string/app_name"
android:theme="@style/DirectShareDialogTheme">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<!-- 後方互換性を保つための記述 -->
<meta-data
android:name="android.service.chooser.chooser_target_service"
android:value="androidx.sharetarget.ChooserTargetServiceCompat" />
</activity>
ファイルコンテンツURIを安全に生成、共有するために FileProvider
の定義も記述します。
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.example.android.directshare.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
build.gradle
ShortcutManager API を使う準備をします。この API にはショートカットを更新、削除、追加するメソッドがあります。
※ShortcutManager とのやり取りはバックグラウンドスレッドで行う必要があります。
Codelabs では ShortcutManagerCompat
を使用しています。ShortcutManagerCompat
は旧式の ChooserTargetService DirectShare API との後方互換性を備えた共有ショートカットを提供する AndroidX の API です。プロジェクトで使うときは core
を gradle に追加します。sharetarget
には古いAndroidバージョンでも動作する ChooserTargetServiceCompat
が入っています。
implementation "androidx.core:core:${versions.androidxCore}"
implementation "androidx.sharetarget:sharetarget:${versions.shareTarget}"
MainActivity
共有するテキストを入力、送信する画面です。
今回の例では、ユーザがアプリを開くたび(= MainActivity が立ち上がるたび)に共有ショートカットをプッシュします。この処理の詳細は SharingShortcutsManager
の章で説明します。
sharingShortcutsManager = SharingShortcutsManager().also {
it.pushDirectShareTargets(this)
}
SHARE ボタンをタップした時の処理はこちらです。
private fun share() {
val sharingIntent = Intent(Intent.ACTION_SEND)
sharingIntent.type = "text/plain"
sharingIntent.putExtra(android.content.Intent.EXTRA_TEXT, bodyEditText.text.toString())
// sharesheetのプレビューに表示されるタイトルを設定(optional)
sharingIntent.putExtra(Intent.EXTRA_TITLE, getString(R.string.send_intent_title))
// Android Q 以降ではタイトル、画像などのプレビューを表示させることができる
// sharesheetのプレビューに表示される画像のURIを設定(optional)
val thumbnail = getClipDataThumbnail()
thumbnail?.let {
sharingIntent.clipData = it
sharingIntent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
}
startActivity(Intent.createChooser(sharingIntent, null))
}
private fun getClipDataThumbnail(): ClipData? {
val contentUri = saveThumbnailImage()
// フォルダアクセスの必要があるので、AndroidManifest.xmlでFileProviderの定義をする
ClipData.newUri(contentResolver, null, contentUri)
...
}
SelectContactActivity
共有する人物の選択肢を RecyclerView で表示しています。 Direct Share 特有の実装はないので、ここでは説明を省きます。
SendMessageActivity
送る相手とメッセージを表示します。 SEND
をタップするとトーストが表示されます。
Direct Share を使用した場合は Intent.EXTRA_SHORTCUT_ID
を取得することができます。今回は連絡先IDがセットされているので、これを元に連絡先のUI表示を行います。
val shortcutId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID)
SharingShortcutsManager
ShortcutManager とやり取りするクラスです。ショートカットに関連付けるカテゴリは自分たちでつけることができますが、 shortcuts.xml
で定義したものと一致する必要があります。
private val categoryTextShareTarget = "com.example.android.directshare.category.TEXT_SHARE_TARGET"
公開するショートカットのリストを追加するコードは以下の通りです。ここでは Contact.kt
で定義した4つの連絡先を追加しています。ちなみに ShortcutManagerCompat.getMaxShortcutCountPerActivity
で定義されている以上の数のショートカットを追加するとアプリがクラッシュしてしまいます。
fun pushDirectShareTargets(context: Context) {
val shortcuts = ArrayList<ShortcutInfoCompat>()
// さっきのカテゴリをSet化
val contactCategories = setOf(categoryTextShareTarget)
for (id in 0 until maxShortcuts) {
val contact = Contact.byId(id)
// ショートカットが静的ショートカットとして開かれた時のみ送られる
val staticLauncherShortcutIntent = Intent(Intent.ACTION_DEFAULT)
shortcuts.add(
// idはアクティビティが共有インテントを受け取った時の識別子として必要
// idは受け取ったインテントでEXTRA_SHORTCUT_IDとして受け取れます
ShortcutInfoCompat.Builder(context, Integer.toString(id))
// 共有先選択ダイアログに表示される短いラベルとアイコン
.setShortLabel(contact.name)
.setIcon(IconCompat.createWithResource(context, contact.icon))
.setIntent(staticLauncherShortcutIntent)
// ショートカットがキャッシュされるようにしている
// ショートカットの公開を停止してもsharesheetに表示される
.setLongLived(true)
// ショートカットのフィルタリングに使われる
.setCategories(contactCategories)
// 共有先の提案最適化のため
// 指定はオプションであるものの、強く推奨されている
.setPerson(
Person.Builder()
.setName(contact.name)
.build()
)
.build()
)
}
// 本当はCRUD操作等々で管理を改善した方がいいらしい
ShortcutManagerCompat.addDynamicShortcuts(context, shortcuts)
}
これで、 Direct Share サンプルアプリ作成完了です。