web-dev-qa-db-ja.com

AsyncTaskは、アクティビティが破壊されても停止しません

アクティビティの作成時に実行を開始し、バックグラウンドで処理を行うAsyncTaskオブジェクトがあります(最大100個の画像をダウンロードします)。すべて正常に動作しますが、私には理解できないこの独特の動作があります。

たとえば:Android画面の向きが変更されると、アクティビティは破棄されて再度作成されます。したがって、onRetainNonConfigurationInstance()メソッドをオーバーライドし、AsyncTaskで実行されたダウンロードデータをすべて保存します。これは、方向の変更中にアクティビティが破棄され作成されるたびにAsyncTaskを実行しないようにするためですが、ログで確認できるように、以前のAsyncTaskはまだ実行中です(データは正しく保存されます)

アクティビティのonDestroy()メソッドでAsyncTaskをキャンセルしようとしましたが、ログにはまだAsyncTaskが実行中として表示されています。

これは本当に奇妙な振る舞いであり、AsyncTaskを停止/キャンセルする正しい手順を誰かが教えてくれれば本当にありがたいです。

ありがとう

67
Raja

@Romain Guyの答えは正しいです。それにもかかわらず、私は補足情報を追加し、長時間実行されるAsyncTaskに、さらにネットワーク指向のasynctasksに使用できるライブラリーまたは2へのポインターを提供したいと思います。

AsyncTasksは、バックグラウンドで作業を行うために設計されています。はい、cancelメソッドを使用して停止できます。インターネットからコンテンツをダウンロードする際には、 IOブロッキング状態のときにスレッドを処理する を強くお勧めします。次のようにダウンロードを整理する必要があります。

public void download() {
    //get the InputStream from HttpUrlConnection or any other
    //network related stuff
    while( inputStream.read(buffer) != -1 && !Thread.interrupted() ) {
      //copy data to your destination, a file for instance
    }
    //close the stream and other resources
}

Thread.interruptedフラグを使用すると、スレッドがブロッキングio状態を適切に終了するのに役立ちます。スレッドは、cancelメソッドの呼び出しにより敏感に反応します。

AsyncTaskの設計上の欠陥

しかし、AsyncTaskの持続時間が長すぎると、2つの異なる問題に直面することになります。

  1. アクティビティはアクティビティのライフサイクルにほとんど結び付けられておらず、アクティビティが停止してもAsyncTaskの結果は得られません。確かに、はい、できますが、それは大まかな方法​​になります。
  2. AsyncTaskはあまり文書化されていません。直感的ではありますが、単純な非同期タスクの実装と使用は、メモリリークをすぐに引き起こす可能性があります。

RoboSpice (紹介したいライブラリ)は、この種のリクエストを実行するためにバックグラウンドサービスを使用します。ネットワーク要求用に設計されています。要求の結果の自動キャッシュなどの追加機能を提供します。

AsyncTasksが長時間実行されるタスクに悪い理由は次のとおりです。次の理由は、 RoboSpice motivations の抜粋からの適応です。RoboSpiceの使用がAndroidプラットフォームのニーズを満たす理由を説明するアプリです。

AsyncTaskとアクティビティのライフサイクル

AsyncTasksは、アクティビティインスタンスのライフサイクルに従いません。アクティビティ内でAsyncTaskを開始し、デバイスを回転させると、アクティビティが破棄され、新しいインスタンスが作成されます。ただし、AsyncTaskは停止しません。完了するまで生き続けます。

そして、完了すると、AsyncTaskは新しいアクティビティのUIを更新しません。実際、それはもはや表示されていないアクティビティの前のインスタンスを更新します。たとえば、findViewByIdを使用してActivity内のビューを取得すると、タイプJava.lang.IllegalArgumentException:Viewがウィンドウマネージャーにアタッチされないという例外が発生する可能性があります。

メモリリークの問題

AsyncTasksをアクティビティの内部クラスとして作成すると非常に便利です。 AsyncTaskはタスクの完了時または進行中にアクティビティのビューを操作する必要があるため、アクティビティの内部クラスを使用すると便利なようです。内部クラスは外部クラスの任意のフィールドに直接アクセスできます。

それにもかかわらず、それは、内部クラスがその外部クラスのインスタンスで非表示の参照を保持することを意味します:Activity。

長期的には、これによりメモリリークが発生します。AsyncTaskが長時間続く場合、Androidは表示できなくなるため、それを削除したいのに対し、アクティビティは「アライブ」のままになります。アクティビティをガベージコレクションすることはできません。これは、デバイス上のリソースを保持するためのAndroidの中心的なメカニズムです。

タスクの進行状況は失われます

いくつかの回避策を使用して、長時間実行される非同期タスクを作成し、アクティビティのライフサイクルに応じてライフサイクルを管理できます。 アクティビティのonStopメソッドでAsyncTaskをキャンセルする または、非同期タスクを終了させて​​、進行を失わないようにすることができます アクティビティの次のインスタンスに再リンクします =。

これは可能であり、RobopSpiceの動機でどのように行われるかを示しますが、複雑になり、コードは実際には一般的ではありません。さらに、ユーザーがアクティビティを離れて戻ってきた場合でも、タスクの進行は失われます。この同じ問題はローダーでも発生しますが、上記の再リンクの回避策を使用したAsyncTaskと同等の単純なものになります。

Androidサービスを使用する

最良のオプションは、サービスを使用して長時間実行されるバックグラウンドタスクを実行することです。そして、それがまさにRoboSpiceによって提案されたソリューションです。繰り返しになりますが、ネットワーク用に設計されていますが、ネットワークに関連しないものにも拡張できます。このライブラリには 多数の機能 があります。

infographics のおかげで、30秒未満でそれを知ることさえできます。


長時間実行する操作にAsyncTasksを使用するのは、本当に非常に悪い考えです。それでも、1秒または2秒後にViewを更新するなど、短い生存期間には適しています。

RoboSpice Motivations app をダウンロードすることをお勧めします。実際にこの詳細を説明し、ネットワーク関連のものを実行するさまざまな方法のサンプルとデモを提供します。


ネットワークに関連しないタスク(キャッシングなしなど)のためにRoboSpiceの代替を探している場合は、 Tape もご覧ください。

142
Snicolas

ロマン・ガイは正しい。実際、非同期タスクは、どのような場合でも独自のジョブを完了する責任があります。割り込みは最善の方法ではないため、誰かがタスクをキャンセルまたは停止することを望んでいるかどうかを継続的に確認する必要があります。

あなたのAsyncTaskがループ内で何度も何かをしたとしましょう。次に、すべてのループでisCancelled()を確認する必要があります。

_while ( true ) {
    if ( isCancelled())
        break;
    doTheTask();
}
_

doTheTask()は実際の仕事であり、すべてのループでそれを行う前に、タスクをキャンセルする必要があるかどうかを確認します。

通常、AsyncTaskクラスにフラグを設定するか、doInBackground()から適切な結果を返す必要があります。これにより、onPostExecute()で、希望する場合、または作業が途中でキャンセルされた場合。

14
Cagatay Kalan

アクティビティは方向の変更時に再作成されます。はい、そうです。ただし、このイベントが発生するたびに非同期タスクを続行できます。

あなたはそれをチェックします

@Override
protected void onCreate(Bundle savedInstanceState) { 
     if ( savedInstanceState == null ) {
           startAsyncTask()
     } else {
           // ** Do Nothing async task will just continue.
     }
}

-乾杯

1
ralphgabb

以下はあなたの問題を解決しませんが、それを防ぎます:アプリマニフェストでこれを行います:

    <activity
        Android:name=".(your activity name)"
        Android:label="@string/app_name"
        Android:configChanges="orientation|keyboardHidden|screenSize" > //this line here
    </activity>

これを追加すると、アクティビティは設定の変更時にリロードされません。向きが変更されたときに変更を加える場合は、アクティビティの次のメソッドをオーバーライドします。

@Override
public void onConfigurationChanged(Configuration newConfig) {
    super.onConfigurationChanged(newConfig);

        //your code here
    }
1
Frane Poljak