マルチタッチで2つのアクティビティーにジャンプ!?--Android開発

2015年11月23日


Android端末では、タッチパネルがマルチタッチに対応しており、一度に複数のボタンを押すことができますが、うっかりすると、複数のアクティビティーを同時に起動するような、やっかいな状態遷移をしてしまうこともあるようです。

startActiity()を一度に2回実行してしまう

Androidでは、ユーザインターフェースを実現する画面一枚一枚を、Activity(アクティビティー)と呼んでいます。

アプリは、複数のActivityを移動しながら操作する構成となる場合が多いです。

例えば、メイン画面があり、データをロードする場合は「ロード画面」、データをセーブする場合は「セーブ画面」に移動する構成のアプリを考えます。

画面遷移の例

ところで、メイン画面で、「ロード」と「セーブ」のボタンを同時に押すと、何が起こるのでしょうか?

ほとんどの場合は、どちらか早く押した一方のボタンだけクリックされたと判定して、「セーブ」「ロード」のいずれか一方にだけ遷移します。

ところがごくまれに、ボタンを押すタイミングによっては、「ロード」と「セーブ」の両方が押されたと判断されてしまうこともあります。

このときはまず、「ロード」と「セーブ」の一方のリンク先に、先にジャンプし、次に、もう一方のリンク先にジャンプします。

例えば、「セーブ」→「ロード」の順に押されたと判定された場合は、以下のように動作します。

ロードとセーブの両方に遷移する例

このような動作は、選択肢の「はい」と「いいえ」を両方選んでしまうような不都合な動作につながりかねず、バグや意図しない裏技を作ってしまう原因となりえます。

ちなみに、ほぼ同時に2つのボタンが押された場合に、後に押されたリンク先のほうが先に表示されるのは、アクティビティーを積み上げるスタックの都合によります。詳しくは、後で説明します。

なぜ2つのリンク先へ飛んでしまうのか

メイン画面(MainActivity)には、「ロード」「セーブ」の2つのボタンがあります。

メイン画面

ソースコード上は、それぞれのボタンがクリックされると、startActivityForResult()を実行して、それぞれ「ロード画面」「セーブ画面」のアクティビティーを開始するようにしています。

    public void button1Clicked(View v){
        Log.d("main", "button1 clicked.");
        Intent i = new Intent(this, LoadActivity.class);
        startActivityForResult(i, 1);
    }

    public void button2Clicked(View v){
        Log.d("main", "button2 clicked.");
        Intent i = new Intent(this, SaveActivity.class);
        i.putExtra("text", edit1.getText().toString());
        startActivityForResult(i, 2);
    }

ところが、メイン画面で「ロード」「セーブ」をほぼ同時に押すと、違った動作となってしまいます。

ポイントとなるのは、startActivityForResult()を実行したからといって、表示中のアクティビティーで、イベントの受付をすぐにやめてしまうとは限らない点です。

startActivity()やstartActivityForResult()を実行すると、たいていは、現在のアクティビティーの上に、新しく起動したアクティビティーが表示されるので、現在のアクティビティーに対するイベントの受付は、すぐに終わります。従って、アクティビティーを起動するためのボタンは、1つしか押すことができません。

しかし、マルチタッチ機能等を活用して、立て続けにボタンを押した場合などで、ボタン操作のイベントが蓄積しているときは、startActivity()やstartActivityForResult()を実行したからといって、現在のアクティビティーに対するイベント処理の受付を終えるような仕組みにはなっていない様子なので、現在のアクティビティーでボタンのクリックなどのイベントが残っていれば、引き続きイベントの処理が行われてしまうようです。

従って、1つのアクティビティーが、startActivity()やstartActivityForResult()を立て続けに複数回実行してしまうこともありえます。

ボタンを複数個配置して、次に遷移する画面を選択するデザインを行うと、操作のタイミングによっては、両方の選択肢にジャンプしてしまう可能性があることを、知っておくべきでしょう。

アクティビティーのスタックに積む順序

メイン画面で「セーブ」→「ロード」の順に立て続けに操作すると、画面の遷移は「ロード画面」→「セーブ画面」→「メイン画面」の順となります。

ロードとセーブの両方に遷移する例

操作した順序と、表示される順序が逆になっているのは、アクティビティーのスタックを積む仕組みにあるようです。

まず「セーブ」をクリックすると、アクティビティーのスタックで、「メイン画面」の上に「セーブ画面」が積まれます。

次に「ロード」をクリックすると、「セーブ画面」の上にさらに「ロード画面」が積まれます。

画面に表示されるのは、スタックで最も上位となるアクティビティーなので、ジャンプ先として先に表示されるのは「ロード画面」となります。

スタックが積まれる様子

「ロード画面」のアクティビティーをfinish()させて終了させると、スタックから「ロード画面」が取り除かれるので、次は「セーブ画面」が表示されます。

さらに、「セーブ画面」のアクティビティーを終了させる(finish()させる)と、その下位にあるメイン画面に戻ります。

onActivityResult()が実行されるタイミング

メイン画面から「ロード」「セーブ」のアクティビティーを起動するときは、startActivityForResult()を使っています。

「ロード」「セーブ」のアクティビティーを次々と起動した場合、対応するonActivityResult()は、どちらもメイン画面のアクティビティーで実行されます。

ロード画面→セーブ画面の順に表示された場合は、onActivityResult()は、表示がメイン画面に戻ったタイミングで、ロード画面のonActivityResult()→セーブ画面のonActivityResult()の順に、たてつづけに実行されるようです。

メイン画面から立ち上げたアクティビティーがそれぞれ、終了した順番に従って、onActivityResult()が実行されている様子です。

先に表示される「ロード画面」のonActivityResult()は、ロード画面を閉じたタイミングではなく、その次のセーブ画面を閉じ、表示がメイン画面に戻ってから実行される点に注意する必要があります。

まとめ

おまけ

この例を説明するために作成したAndroidアプリのソース等をダウンロードできるようにしておきます。Android Studio 1.5で作ったプロジェクトを圧縮したものになっています。Android 4.0.4以降で実行できます。

Test205-151124.zip

インストーラに相当するAPKファイルは、圧縮されたファイルを展開すると出てくる app/build/outputs/apk 以下にあります。

ソフトに保証はありません。端末が壊れたりしても筆者は責任を負いかねますので、お使いになる場合は自己責任にてお願いします。

なお、この文章は、このアプリケーションをAndroid 6.0をターゲットにビルドしたものを、Android 5.1.1が入ったNexus 7(2012)で実行した場合に発生した現象を示しています。

やってみましょう


杉原俊雄のホームページAndroidアプリ開発メモ(もくじ)

(c) 2015 Toshio Sugihara. All rights reserved.