2011年10月30日日曜日

startActivityForResultメソッドのsample code

startActivityForResultメソッドのsample code

処理順序
  1. startActivityForResultメソッドを使い、他のActivityを呼び出す。
  2. 呼び出された側のActivityが必要な処理を行い、処理が終わると、このActivityは終了する。
  3. 呼出元ActivityのonActivityResultメソッドが呼ばれる。
ここに掲載したプログラムは、動作原理を理解するために、学習・実験用に作ったものです。
startActivityForResultメソッドは、例えば、「本体設定」画面から入力された値を取得する場合に有用かもしれません。
参考:「本体設定」の「音の設定」画面を表示する

呼び出す側
public class ForResultActivity extends Activity
implements
OnClickListener
{
final String TAG = "My";
static final int PICK_CONTACT_REQUEST = 0;
Button bu;
@Override
public void onCreate(Bundle savedInstanceState){
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    bu = (Button)findViewById(R.id.Button);
    bu.setOnClickListener(this);
}

@Override
public void onClick(View v){
    if(v==bu){
        Intent it;
        it = new Intent(this, WorkerActivity.class);
        try{
            startActivityForResult(it, PICK_CONTACT_REQUEST);
        }
        catch(ActivityNotFoundException e){
            Log.e(TAG, "ActivityNotFoundException");
        }
    }
}


@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data){
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode==PICK_CONTACT_REQUEST){
        //「戻る」キーの押し下げでも、ここに来る。

        if(resultCode==RESULT_OK){
            //「戻る」キーの押し下げでは、ここには来ない。
            Log.i(TAG, "RESULT_OK");
            Log.i(TAG, data.getStringExtra("What are these?"));
            Log.i(TAG, String.valueOf(data.getIntExtra("How many pens?", -1)));
        }
    }
}
}

呼び出される側
public class WorkerActivity extends Activity{
    final String TAG = "My";
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.work);
        Log.i(TAG, "WorkerActivity");
        Intent data;
        data = new Intent();
        data.putExtra("What are these?", "These are pens.");
        data.putExtra("How many pens?", 3);
        setResult(RESULT_OK, data);
        finish();
    }
}

2011年10月24日月曜日

共有アプリ一覧ダイアログをカスタマイズする

共有アプリ一覧ダイアログをカスタマイズする

他のアプリを「共有」機能を使って起動させる場合、共有が可能なアプリの一覧を表示させて、その一覧の中から、利用者が、お好みのアプリを選択することになります。

Implicit Intentを使って共有コマンドを発行する場合、指定した条件に合致する全てのアプリがアプリ一覧に表示されてしまいます。この時表示される「アプリ一覧表示ダイアログ」は、androidシステムが準備したものです。

自分のアプリが他のアプリから「共有」機能により起動可能な場合であって、かつ自分のアプリで「共有」機能を使う場合、「共有一覧ダイアログ」には自分のアプリも表示されてしまいます。
もし、そこで、自分のアプリを選択した場合、自分のアプリで自分のアプリを共有したことになります。
このような些末な件は、多くのアプリにおいては、どちらでも良いことです。
しかし、アプリの性質上、これは困る場合もあります。

このような問題の場合、共有アプリ一覧表示プログラムをカスタマイズすることになります。

仕掛けの流れは次のようになります。
  1. PackageManagerを使って、指定した条件に合致する、端末内の全てのアプリ情報を取得する。
  2. 取得した情報の中から、任意のアプリを削除する。
  3. 一覧をAlertDialogに表示する。
  4. 利用者が任意のアプリを選択する。
  5. 選択されたアプリをExplicit Intentで実行する。
つまり、Implicit Intentを使わないようにするのです。
PackageManagerのコーディングは次のとおりです。
    PackageManager pm = getPackageManager();
このような書き方だと、自分のアプリだけの情報を取得しただけかと思ってしまいますが、端末の全てのアプリの情報を取り扱う仕掛けになっているので注意しましょう。

参考

下に、上記1~3までを実行するsourceを掲載しておきます。
int i;
Intent it;
PackageManager pm;
List<ResolveInfo> lri;
ArrayList<ResolveInfoList> al;
ArrayAdapter<ResolveInfoList> aa;
AlertDialog.Builder b;
TextView tv;
ResolveInfoList ril;
Resources r;

r = getResources();
al = new ArrayList<ResolveInfoList>();
it = new Intent();
//起動するアプリを限定させて頂きます。
it.setAction(Intent.ACTION_SEND);
it.setType("text/plain");
pm = getPackageManager();
lri = pm.queryIntentActivities(it, PackageManager.MATCH_DEFAULT_ONLY);

switch(lri.size()){
case 0:
    tv = (TextView)findViewById(R.id.TextRegister);
    tv.setText(R.string.noData);
    return;
default: break;
}

//自分自身を含めないようにする。
ApplicationInfo ai = null;
try{
    ai = pm.getApplicationInfo(getPackageName(), 0);
}
catch(PackageManager.NameNotFoundException e){
}
String sLabel;
sLabel = ai.loadLabel(pm).toString();

//AlertDialogに表示させる一覧を作成する。
for(i=0; i<lri.size(); i++){
    String s;
    ResolveInfo ri;
    ri = lri.get(i);
    s = ri.loadLabel(pm).toString();
    if(s.equals(sLabel)==true);
    else{
        ril = new ResolveInfoList();
        ril.sResolveInfo = s ;
        ril.ri = ri;
        al.add(ril);//リストに追加する。
    }
}
ril = new ResolveInfoList();
ril.sResolveInfo = r.getString(R.string.noShare);
al.add(ril);
aa = new ArrayAdapter<ResolveInfoList>(this, 
android.R.layout.select_dialog_singlechoice, al);
aa.sort(new MyComparator());//文字列をキーにして並べ替える。
b = new AlertDialog.Builder(this);
b.setTitle(R.string.DialogTitle);
b.setSingleChoiceItems(aa, 0, this);
b.setNegativeButton(R.string.Close,
    new DialogInterface.OnClickListener(){
        public void onClick(DialogInterface dialog, int id) {
            dialog.cancel();//「閉じる」ボタンを押しても何もしない。
        }
    }
);
adServiceDialog = b.show();//見せる。

2011年10月7日金曜日

onCreateの引数の意味

onCreateの引数の意味

この記事は【必須】再起動阻止 configChangesの続きです。

onCreateメソッドの引数
ActivityにおけるonCreateメソッドには、Bundle savedInstanceStateという引数が付いています。androidの学習を始めた時から目にしていましたが、特に気にはなりませんでした。ここに来てやっと、使いたくは無いが、使うべき時がきてしまいました。

この引数の内容は、通常はnullです。
しかし、実機の端末を、縦位置から横位置へ(又は横位置から縦位置へ)変える(以下「configuration change」と呼ぶ。)と、null以外の値が代入されてきます。
このため、現在の起動が、初回の起動なのか、再起動なのかを捕捉することに使えます。

各技法の評価

AndroidManifest.xmlを書き換える技法
前回の私の記事では、再起動の回避策として、「AndroidManifest.xmlの<activity>の属性としてandroid:configChanges="orientation"を書く」という技法を紹介しました。しかし、この技法に対しては、Handling Runtime Changesにおいて次のようなコメントがあります。
This technique should be considered a last resort when you must avoid restarts due to a configuration change and is not recommended for most applications.
このように書かれてしまうと、使いづらくなります。

onCreateメソッドの引数を使う技法
onCreateメソッドのBundleを使う技法に対しては、「it is not designed to carry large objects (such as bitmaps)」という指摘はありますが、初回起動か再起動かの判別をしたいだけなので、有効な技法と言えます。
誰しもが目にするonCreateメソッドの引数として付いてきているのです。なので、androidの基本的な設計思想として、「configuration changeに基づく再起動は発生するのが当たり前であり、発生した時には、これを使え」と言っているようです。
Bundleの特性を活かして、再起動の前から、再起動の後へ、任意の値を渡すことができます。
しかし、再起動を許可してしまうため、Javaのcodeにおいて、初回起動時に行う部分と、再起動時に行う部分とを書き分けなければならなくなります。
このような書き分け作業を行ったとしても、そのコーディングのメリットは、「再起動による不都合を防止する」という程度であって、自分のアプリへの積極的なメリットはありません。

Intentによる再起動
「onCreateメソッドの引数」技法が使えるのは、configuration changeによる再起動の場合です。
Intentを受けた場合には、onCreateメソッドの引数は常にnullです。
このため、Intentによる初回起動or再起動の確認は別の手段を講じなければなりません。

私なりに考えた方法
再起動への対応を考える
configuration changeに伴う再起動への対応 その1
configuration changeに伴う再起動への対応 その2

上記知見を得るために使ったsourceを掲載しておきます。エミュレータでは有効に稼働しません。実機を使ってください。
public class OnSaveInstanceActivity extends Activity{
    final String TAG = "OSIA";
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        if(savedInstanceState==null){//初回起動
            Log.i(TAG, "null at onCreate");
            //ここに初回だけ実行させるコードを書けば良い。
        }
        else{//再起動
            Log.i(TAG, "Not null at onCreate");
        }
    }
}