Help us understand the problem. What is going on with this article?

Codelabs から学ぶ Direct Share

Codelabsの Direct Share to an Android app (eventタグ:Android Dev Summit 2019) を元にDirect Share(ダイレクトシェア)について説明します。

Direct Share とは

Android 6.0 から導入された機能で、アプリからコンテンツを共有する時に直接共有する相手を選択することができるというものです。この機能を使わない場合は、まず共有するアプリを選択した上で連絡先を指定する必要があります。

スクリーンショット 2019-12-22 14.42.28.png

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

スクリーンショット 2019-12-22 14.42.28.png スクリーンショット 2019-12-22 14.42.28.png スクリーンショット 2019-12-22 14.42.28.png

この Codelabs ではすでに Direct Share 以外の部分は実装されていて、自分でコードを書く部分はこれくらいでした。

  • 共有ショートカットを公開する
  • 古いAndroidバージョンとの後方互換性を保ちつつ Direct Share 機能を使えるようにする
  • コンテンツプレビューにタイトルとサムネイルを追加する

実装

shortcuts.xml

ここでは以下のことを宣言します。

  • シェアするデータのタイプ
  • 共有ショートカットのカテゴリ
  • 共有インテントを扱うActivity
app/src/main/res/xml/shortcuts.xml
<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.xmlandroid.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 が入っています。

build.gradle
implementation "androidx.core:core:${versions.androidxCore}"
implementation "androidx.sharetarget:sharetarget:${versions.shareTarget}"

MainActivity

共有するテキストを入力、送信する画面です。

スクリーンショット 2019-12-22 14.42.28.png

今回の例では、ユーザがアプリを開くたび(= MainActivity が立ち上がるたび)に共有ショートカットをプッシュします。この処理の詳細は SharingShortcutsManager の章で説明します。

onCreate
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 サンプルアプリ作成完了です。

参考文献

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした