web-dev-qa-db-ja.com

Android AsyncTaskコンテキストの動作

私はAndroid=でAsyncTasksを使用していて、問題を扱っています。

簡単な例として、1つのAsyncTaskを持つアクティビティを考えます。バックグラウンドでのタスクは何も壮大なことはせず、8秒間スリープします。

OnPostExecute()メソッドのAsyncTaskの最後で、ボタンの可視性ステータスをView.VISIBLEに設定しているだけで、結果を確認しています。

これは、AsyncTaskが動作しているときに(8秒のスリープウィンドウ内で)ユーザーが電話の向きを変更することを決定するまで、うまく機能します。

Androidアクティビティのライフサイクルを理解しており、アクティビティが破棄されて再作成されることを知っています。

ここで問題が発生します。AsyncTaskはボタンを参照しており、明らかにAsyncTaskを開始したコンテキストへの参照を保持しています。

この古いコンテキスト(ユーザーが方向を変更したため)がnullになり、AsyncTaskが表示しようとしているボタンへの参照に対してNPEをスローすることを期待します。

代わりに、NPEはスローされず、AsyncTaskはボタン参照がnullではないと見なし、それを可視に設定します。結果?画面では何も起こりません。

pdate:アクティビティへのWeakReferenceを保持し、構成変更が発生したときに切り替えることで、これに取り組みました。これは面倒です。

これがコードです:

public class Main extends Activity {

    private Button mButton = null;
    private Button mTestButton = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mButton = (Button) findViewById(R.id.btnStart);
        mButton.setOnClickListener(new OnClickListener () {
            @Override
            public void onClick(View v) {
                new taskDoSomething().execute(0l);
            }
        });
        mTestButton = (Button) findViewById(R.id.btnTest);   
    }

    private class TaskDoSomething extends AsyncTask<Long, Integer, Integer> 
    {
        @Override
        protected Integer doInBackground(Long... params) {
            Log.i("LOGGER", "Starting...");
            try {
                Thread.sleep(8000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 0;
        }

        @Override
        protected void onPostExecute(Integer result) {
            Log.i("LOGGER", "...Done");
            mTestButton.setVisibility(View.VISIBLE);
        }
    }
}

それを実行してみてください、そしてAsyncTaskが働いている間にあなたの電話の向きを変えてください。

33
dnkoutso

AsyncTaskは、アクティビティが破棄されて再起動された後に再利用されるようには設計されていません。あなたが述べたように、内部のHandlerオブジェクトは古くなります。 Romain GuyによるShelvesの例では、現在実行中のAsyncTaskをすべてキャンセルし、方向変更後に新しいものを再起動します。

スレッドを新しいアクティビティに渡すことは可能ですが、多くの配管が追加されます。これを行う方法については一般的に合意されていませんが、私の方法についてはこちらで読むことができます: http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html

23
jasonhudgins

コンテキストのみが必要で、UIに使用しない場合は、ApplicationContextをAsyncTaskに渡すだけです。たとえば、システムリソースのコンテキストが必要になることがよくあります。

AsyncTaskからUIを更新しようとしないでください。また、面倒になる可能性があるため、構成の変更を自分で処理しないようにしてください。 UIを更新するには、ブロードキャストレシーバーを登録して、ブロードキャストを送信します。

上記のように、アクティビティとは別のパブリッククラスとしてAsyncTaskも必要です。これにより、テストが非常に簡単になります。残念ながら、Androidプログラミングは、しばしば悪い習慣を強化し、公式の例は役に立ちません。

3
Ralph Mueller

これを回避するために、ここで回答ギビンを使用できます: https://stackoverflow.com/a/2124731/327011

しかし、アクティビティ(ポートレートとランドスケープで異なるレイアウト)を破棄する必要がある場合は、AsyncTaskをパブリッククラスにすることができます(プライベートにすべきでない理由はこちらをご覧ください Android:AsyncTaskの推奨:プライベートクラスまたはパブリッククラス? )次に、メソッドsetActivityを作成して、破棄または作成されるたびに現在のアクティビティへの参照を設定します。

次の例をご覧ください。 外部クラスのAndroid AsyncTask

2
neteinstein

これは、方向を変更したときにアクティビティが破棄または再作成されないようにするための方法です。

これを行うには、これをマニフェストファイルの<Activity>タグに追加します。

Android:configChanges="orientation|keyboardHidden" 

そして、ActivityクラスのonConfigurationChangedをオーバーライドします。

@Override
public void onConfigurationChanged(final Configuration newConfig)
{
    // Ignore orientation change to keep activity from restarting
    super.onConfigurationChanged(newConfig);
}
2
Mark B