Android Tips #35 ShareCompat で簡単に共有アクションをつくる

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

ShareCompat とは

ShareCompat は Activity 間のデータのやりとり (共有) のためのユーティリティクラスです。Support Package に含まれているクラスなので Android 1.6 (APIレベル4) から使用することができます。
Activity 間のデータのやりとりは Intent を使っておこないますが ShareCompat を使うことで ACTION_SEND や ACTION_SEND_MULTIPLE といった共有アクションの暗黙的 Intent をつくったり、逆に共有アクションの詳細な情報を取得するといったことを簡単に実装することができます。
Android 4.0 (APIレベル14) から共有アクションの履歴を表示する ShareActionProvider が使えるようになりましたが ShareCompat で同等の機能をバージョンごとにソースを分岐させる必要なく実装できます!
ということで、今回はこの ShareCompat を使った共有アクションのつくりかたを解説したいと思います!

ShareCompat を使って共有アクションをつくる

以下のような「オプションメニューで共有アクションの暗黙的 Intent を起動する」サンプルをつくってみます! EditText に入力された内容をデータとして取り扱っています。

share_compat02

1. オプションメニューをつくる

まずはトリガーになるオプションメニューを普通につくります。Android 3.0 (APIレベル11) 以上は ActionBar に常に表示しておきたいので android:showAsAction は always にしておきます。

activity_simple_send.xml

<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <item
        android:id="@+id/menu_share"
        android:orderInCategory="100"
        android:showAsAction="always"
        android:title="Share"
        android:icon="@android:drawable/ic_menu_share"
        />
</menu>

2. ShareCompat.IntentBuilder をインスタンス化する

共有アクションの Intent をつくるには ShareCompat.IntentBuilder を使います。スタティックメソッド ShareCompat.IntentBuilder#from() を使ってインスタンスを取得します。引数には Intent を送出する元の Activity を渡します。

ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder.from(MainActivity.this);

3. データをくっつける

上記で得た IntentBuilder インスタンスに遷移先の Activity に渡したいデータをくっつけます。下記のようなデータがつけられます。

setChooserTitle() String アプリ選択ダイアログのタイトル
setEmailTo() String[] メールの送信先 (TO)
setEmailCc() String[] メールの送信先 (CC)
setEmailBcc() String[] メールの送信先 (BCC)
setSubject() String タイトル
setText() String テキスト
setHtmlText() String HTML テキスト
setStream() Uri 画像ファイルなどのデータの URI
setType() String データの種類 (mime-type)
addEmailTo() String または String[] 追加するメールの送信先 (TO)
addEmailCc() String または String[] 追加するメールの送信先 (CC)
addEmailBcc() String または String[] 追加するメールの送信先 (BCC)
addStream() Uri 追加する画像ファイルなどのデータの URI

データの URI がひとつの場合は ACTION_SEND として、複数の場合は ACTION_SEND_MULTIPLE として Intent がつくられます。 setType() で指定した mime-type が各アプリの Activity で指定している IntentFilter とマッチしていれば暗黙的 Intent で起動できる Activity となります。

4. Intent を起動する

最後に onOptionsItemSelected() のタイミングで ShareCompat.IntentBuilder#startChooser() を呼んで Intent を起動します。
これで完成です!

SimpleSendActivity.java

package jp.classmethod.android.sample.sharecompat;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ShareCompat;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.EditText;

public class SimpleSendActivity extends Activity {
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_simple_send);
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_simple_send, menu);
        return true;
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (R.id.menu_share == item.getItemId()) {
            EditText address = (EditText) findViewById(R.id.address_text);
            EditText subject = (EditText) findViewById(R.id.subject_text);
            EditText message = (EditText) findViewById(R.id.message_text);
            
            String[] toList = new String[] {address.getText().toString()};
            
            // IntentBuilder をインスタンス化
            ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder.from(SimpleSendActivity.this);
            // データをセットする
            builder.setChooserTitle("Choose Send App");
            builder.setEmailTo(toList);
            builder.setSubject(subject.getText().toString());
            builder.setText(message.getText().toString());
            builder.setType("text/plain");
            // Intent を起動する
            builder.startChooser();
        }
        return super.onOptionsItemSelected(item);
    }

}

share_compat01

ActionBar のシェアボタンを押すと…

share_compat02

Android 2.3.3 (APIレベル10) 以前のバージョンではオプションメニューに表示されます(下図は Android 1.6 の場合)。

share_compat02_api4

ShareCompat を使って共有アクションをつくる (履歴付き)

Android 4.0 から共有アクションのアプリ一覧が履歴付きで表示できる ShareActionProvider が使えるようになりましたが、同等の機能を ShareCompat で作ってみたいと思います。使いかたは上記とほぼ同じですが、処理のタイミングが少し異なるので注意しましょう。

履歴付きの共有アクションは Android 4.0 以降のみ表示されます。Android 3.2 (APIレベル13) 以前のバージョンでは通常の共有アクションとして動作します。

1. Activity#onPrepareOptionsMenu() をオーバーライドする

Activity#onPrepareOptionsMenu() はオプションメニューがつくられたときに呼ばれるメソッドです。この時点で ShareCompat.IntentBuilder をインスタンス化し、データをセットします。そして ShareCompat.configureMenuItem() で対象の MenuItem に共有アクションをセットします。

@Override
public boolean onPrepareOptionsMenu(Menu menu) {
    super.onPrepareOptionsMenu(menu);
    
    String[] toList = new String[] {mAddress.getText().toString()};
    
    // IntentBuilder をインスタンス化
    ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder.from(CustomSendActivity.this);
    // データをセットする
    builder.setChooserTitle("Choose Send App");
    builder.setEmailTo(toList);
    builder.setSubject(mSubject.getText().toString());
    builder.setText(mMessage.getText().toString());
    builder.setType("text/plain");
    
    ShareCompat.configureMenuItem(menu, R.id.menu_share, builder);
    
    return true;
}

これで ActionBar の任意のアイテムが履歴付きになりました!一度共有アクションを実行したあとは最後に使ったアプリが右側に表示されます。

share_compat03

2. データが更新されるようにする

上記実装では ActionBar に履歴を表示できるようになりましたが Activity の生成時にしか呼ばれないので実際は EditText などデータが書き換わるものは共有できません。あくまで onPrepareOptionsMenu() が最初に呼ばれたときに既にデータがあるものだけしか Intent にデータがセットできないです。
そんなときに登場するのが ActivityCompat#invalidateOptionsMenu() です。このメソッドを呼ぶと onPrepareOptionsMenu() がもう一度走ります。引数には表示中の Activity を渡します。 EditText で内容が書き換わったタイミングで更新したい場合は TextWatcher#onTextChanged() など、データを更新したい任意のタイミングで呼びます。

CustomSendActivity.java

package jp.classmethod.android.sample.sharecompat;

import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.ShareCompat;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.Menu;
import android.widget.EditText;

public class CustomSendActivity extends Activity {
    
    private EditText mAddress;
    private EditText mSubject;
    private EditText mMessage;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_send);
        
        mAddress = (EditText) findViewById(R.id.address_text);
        mSubject = (EditText) findViewById(R.id.subject_text);
        mMessage = (EditText) findViewById(R.id.message_text);
        
        mAddress.addTextChangedListener(new UITextWatcher());
        mSubject.addTextChangedListener(new UITextWatcher());
        mMessage.addTextChangedListener(new UITextWatcher());
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_send, menu);
        return true;
    }
    
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        super.onPrepareOptionsMenu(menu);
        
        String[] toList = new String[] {mAddress.getText().toString()};
        
        // IntentBuilder をインスタンス化
        ShareCompat.IntentBuilder builder = ShareCompat.IntentBuilder.from(CustomSendActivity.this);
        // データをセットする
        builder.setChooserTitle("Choose Send App");
        builder.setEmailTo(toList);
        builder.setSubject(mSubject.getText().toString());
        builder.setText(mMessage.getText().toString());
        builder.setType("text/plain");
        
        ShareCompat.configureMenuItem(menu, R.id.menu_share, builder);
        
        return true;
    }
    
    private class UITextWatcher implements TextWatcher {

        @Override
        public void afterTextChanged(Editable editable) {
        }

        @Override
        public void beforeTextChanged(CharSequence text, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence text, int start, int count, int after) {
            ActivityCompat.invalidateOptionsMenu(CustomSendActivity.this);
        }
        
    }

}

share_compat04

Intent を受け取った Activity にちゃんとデータが渡されます!例として au のEメールアプリに送ってみました。

share_compat05

ShareCompat で共有アクションの情報を取得する

ShareCompat では共有がリクエストされた Activity で共有アクションの情報を取得することができます。実装はとてもシンプルで ShareCompat.IntentReader をインスタンス化して getText() などのようなメソッドでデータを取得するだけです。アプリケーション名や Activity 名、アプリアイコンなども取得できるのでいろいろ使えそうですね。ただし呼び出し元のアプリケーション情報は必ずしも受け取れるわけではないので注意しましょう。

RecieveActivity.java

package jp.classmethod.android.sample.sharecompat;

import android.app.Activity; import android.content.ComponentName; import android.os.Bundle; import android.support.v4.app.ShareCompat; import android.widget.ImageView; import android.widget.TextView;

public class RecieveActivity extends Activity {

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_recieve);

// ShareCompat.IntentReader をインスタンス化 ShareCompat.IntentReader reader = ShareCompat.IntentReader.from(RecieveActivity.this);

// アプリアイコン ImageView appImageView = (ImageView) findViewById(R.id.app_image_view); appImageView.setImageDrawable(reader.getCallingApplicationIcon()); // Activity アイコン ImageView activityImageView = (ImageView) findViewById(R.id.activity_image_view); activityImageView.setImageDrawable(reader.getCallingActivityIcon());

// 呼び出し元の Activity String callingActivity = "null"; ComponentName componentName = reader.getCallingActivity(); if (componentName != null) { callingActivity = componentName.getClassName(); }

// 各データを読み込んで TextView に表示する String detail = "CallingApplicationLabel=" + reader.getCallingApplicationLabel(); detail += "\n" + "CallingPackage=" + reader.getCallingPackage(); detail += "\n" + "CallingActivity=" + callingActivity; detail += "\n" + "Type=" + reader.getType(); detail += "\n" + "Subject=" + reader.getSubject(); detail += "\n" + "Text=" + reader.getText(); detail += "\n" + "HtmlText=" + reader.getHtmlText(); String [] emailToList = reader.getEmailTo(); if (emailToList != null) { for (String emailTo : emailToList) { detail += "\n" + "EmailTo=" + emailTo; } } else { detail += "\n" + "EmailTo=null"; } String [] emailCcList = reader.getEmailCc(); if (emailCcList != null) { for (String emailCc : emailCcList) { detail += "\n" + "EmailCc=" + emailCc; } } else { detail += "\n" + "EmailCc=null"; } String [] emailBccList = reader.getEmailBcc(); if (emailBccList != null) { for (String emailBcc : emailBccList) { detail += "\n" + "EmailBcc=" + emailBcc; } } else { detail += "\n" + "EmailBcc=null"; } int streamCount = reader.getStreamCount(); detail += "\n" + "StreamCount=" + streamCount; for (int i = 0; i < streamCount; i++) { detail += "\n" + "Stream=" + reader.getStream(i); } TextView textView = (TextView) findViewById(R.id.text_view); textView.setText(detail); } } [/java]

AndroidManifest に対象の Activity に IntentFilter を登録するのを忘れずに。

<activity
    android:name="jp.classmethod.android.sample.sharecompat.RecieveActivity"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="android.intent.action.SEND" />

        <category android:name="android.intent.category.DEFAULT" />

        <data android:mimeType="*/*" />
    </intent-filter>
    <intent-filter>
        <action android:name="android.intent.action.SEND_MULTIPLE" />

        <category android:name="android.intent.category.DEFAULT" />

        <data android:mimeType="*/*" />
    </intent-filter>
</activity>

では実行してみます。先ほどと同じアプリにしてあるので共有アクションでこのアプリを選ぶと情報が表示されます。

share_compat06

Google Chrome から弊社ブログを共有してみました。

share_compat07

share_compat08

取得できる内容がかなり少ないですね…。ギャラリーとかでもやってみましたが同じような感じでした。

share_compat09

どうやらアプリ名やらアイコンやらが取得できるアプリはかなり少なそうです。

ソースコード

今回実装したソースコードを github で公開しました。ぜひ実装時の参考にしてください!

suwa-yuki/ShareCompatSample

まとめ

いままでは Intent を普通に作って startActivity() していたと思いますが ShareCompat を使うと処理がきれいにまとまりました。また ShareActionProvider を使わずにバージョン間で処理を分岐することなく実装できるのはとても良いですね!

参考