77

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

例: Android 画面の向きが変わると、Activityが破棄され、再度作成されます。そのため、メソッドをオーバーライドしてonRetainNonConfigurationInstance()、ダウンロードしたすべてのデータを .xml で実行して保存しますAsyncTask。これを行う私の目的は、方向の変更中に破棄されて作成さAsyncTaskれるたびに実行しないようにするActivityことですが、ログでわかるように、前のAsyncTaskものはまだ実行されています。(ただし、データは正しく保存されます)

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

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

4

6 に答える 6

146

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

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 は十分に文書化されていません。直感的ではありますが単純な asynctask の実装と使用は、すぐにメモリ リークを引き起こす可能性があります。

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

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

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

AsyncTask は、Activity インスタンスのライフ サイクルに従いません。アクティビティ内で AsyncTask を開始し、デバイスを回転させると、アクティビティが破棄され、新しいインスタンスが作成されます。しかし、AsyncTask は死ぬことはありません。完成するまで生き続けます。

そして完了すると、AsyncTask は新しいアクティビティの UI を更新しません。実際、表示されなくなったアクティビティの以前のインスタンスを更新します。たとえば、findViewById を使用してアクティビティ内のビューを取得すると、java.lang.IllegalArgumentException: View not attached to window manager というタイプの例外が発生する可能性があります。

メモリリークの問題

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

それにもかかわらず、内部クラスがその外部クラスのインスタンスである Activity.

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

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

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

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

Android サービスの使用

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

インフォグラフィックのおかげで、30 秒もかからずにアイデアを得ることができます。


長時間実行される操作に AsyncTasks を使用することは、非常に悪い考えです。それでも、1 秒または 2 秒後にビューを更新するなどの短期間のものには問題ありません。

RoboSpice Motivations アプリをダウンロードすることをお勧めします。このアプリでは、これについて詳しく説明し、ネットワーク関連の操作を実行するさまざまな方法のサンプルとデモンストレーションを提供しています。


ネットワークに関連しないタスク (たとえば、キャッシュなし) で RoboSpice の代替手段を探している場合は、Tapeも参照してください。

于 2012-10-30T21:41:53.930 に答える
16

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

AsyncTaskあなたがループで何度も何かをするとしましょう。isCancelled()次に、すべてのループをチェックインする必要があります。

while ( true ) {
    if ( isCancelled())
        break;
    doTheTask();
}

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

通常、クラスでフラグを設定するAsyncTaskか、クラスから適切な結果を返す必要があります。doInBackground()これにより、 で、目的onPostExecute()の作業を完了できたかどうか、または作業が途中でキャンセルされたかどうかを確認できます。

于 2011-06-04T18:34:56.147 に答える
1

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

あなたはそれをチェックしてください

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

-乾杯

于 2015-01-13T02:23:51.193 に答える
1

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

    <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
    }
于 2013-10-15T12:36:59.470 に答える
-1

MVCの観点から、Activity はControllerです。コントローラーがビューよりも長生きする操作を実行するのは間違っています(android.view.Viewから派生し、通常は既存のクラスを再利用するだけです)。したがって、AsyncTasks を開始するのはModelの責任です。

于 2013-01-31T05:46:16.880 に答える
-3

この投稿class MagicAppRestartから使用 して、すべてのAsyncTasksとともにプロセスを強制終了できます。Androidはアクティビティスタックを復元します(ユーザーは何も言及しません)。プロセスを再起動する前の唯一の通知は呼び出しであることに注意することが重要です。Androidアプリのライフサイクルロジックによると、アプリケーションはとにかくそのような終了の準備ができている必要があります。onPause()

私はそれを試しました、そしてそれはうまくいくようです。それでも、現時点では、Applicationクラスからの弱参照のような「より文明的な」メソッドを使用する予定です(私のAsyncTasksはかなり短命で、メモリをそれほど消費しないことを願っています)。

これがあなたが遊ぶことができるいくつかのコードです:

MagicAppRestart.java

package com.xyz;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;

/** This activity shows nothing; instead, it restarts the android process */
public class MagicAppRestart extends Activity {
    // Do not forget to add it to AndroidManifest.xml
    // <activity android:name="your.package.name.MagicAppRestart"/>
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        System.exit(0);
    }
    public static void doRestart(Activity anyActivity) {
        anyActivity.startActivity(new Intent(anyActivity.getApplicationContext(), MagicAppRestart.class));
    }
}

残りは、Eclipseがcom.xyz.AsyncTaskTestActivityの新しいAndroidプロジェクト用に作成したものです。

AsyncTaskTestActivity.java

package com.xyz;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;

public class AsyncTaskTestActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.d("~~~~","~~~onCreate ~~~ "+this);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
    public void onStartButton(View view) {
        Log.d("~~~~","~~~onStartButton {");
        class MyTask extends AsyncTask<Void, Void, Void> {

            @Override
            protected Void doInBackground(Void... params) {
                // TODO Auto-generated method stub
                Log.d("~~~~","~~~doInBackground started");
                try {
                    for (int i=0; i<10; i++) {
                        Log.d("~~~~","~~~sleep#"+i);
                        Thread.sleep(200);
                    }
                    Log.d("~~~~","~~~sleeping over");
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                Log.d("~~~~","~~~doInBackground ended");
                return null;
            }
            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);
                taskDone();
            }
        }
        MyTask task = new MyTask();
        task.execute(null);
        Log.d("~~~~","~~~onStartButton }");
    }
    private void taskDone() {
        Log.d("~~~~","\n\n~~~taskDone ~~~ "+this+"\n\n");
    }
    public void onStopButton(View view) {
        Log.d("~~~~","~~~onStopButton {");
        MagicAppRestart.doRestart(this);
        Log.d("~~~~","~~~onStopButton }");
    }
    public void onPause() {   Log.d("~~~~","~~~onPause ~~~ "+this);   super.onPause(); }
    public void onStop() {    Log.d("~~~~","~~~onStop ~~~ "+this);    super.onPause(); }
    public void onDestroy() { Log.d("~~~~","~~~onDestroy ~~~ "+this); super.onDestroy(); }
}

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <Button android:text="Start" android:onClick="onStartButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
    <Button android:text="Stop" android:onClick="onStopButton" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

</LinearLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.xyz"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:minSdkVersion="7" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".AsyncTaskTestActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MagicAppRestart"/>
    </application>
</manifest>

およびログの関連部分(と呼ばれるだけonPauseであることに注意してください):

D/~~~~    (13667): ~~~onStartButton {
D/~~~~    (13667): ~~~onStartButton }
D/~~~~    (13667): ~~~doInBackground started
D/~~~~    (13667): ~~~sleep#0
D/~~~~    (13667): ~~~sleep#1
D/~~~~    (13667): ~~~sleep#2
D/~~~~    (13667): ~~~sleep#3
D/~~~~    (13667): ~~~sleep#4
D/~~~~    (13667): ~~~sleep#5
D/~~~~    (13667): ~~~sleep#6
D/~~~~    (13667): ~~~sleep#7
D/~~~~    (13667): ~~~sleep#8
D/~~~~    (13667): ~~~sleep#9
D/~~~~    (13667): ~~~sleeping over
D/~~~~    (13667): ~~~doInBackground ended
D/~~~~    (13667): 
D/~~~~    (13667): 
D/~~~~    (13667): ~~~taskDone ~~~ com.xyz.AsyncTaskTestActivity@40516988
D/~~~~    (13667): 




D/~~~~    (13667): ~~~onStartButton {
D/~~~~    (13667): ~~~onStartButton }
D/~~~~    (13667): ~~~doInBackground started
D/~~~~    (13667): ~~~sleep#0
D/~~~~    (13667): ~~~sleep#1
D/~~~~    (13667): ~~~sleep#2
D/~~~~    (13667): ~~~sleep#3
D/~~~~    (13667): ~~~sleep#4
D/~~~~    (13667): ~~~sleep#5
D/~~~~    (13667): ~~~onStopButton {
I/ActivityManager(   81): Starting: Intent { cmp=com.xyz/.MagicAppRestart } from pid 13667
D/~~~~    (13667): ~~~onStopButton }
D/~~~~    (13667): ~~~onPause ~~~ com.xyz.AsyncTaskTestActivity@40516988
I/ActivityManager(   81): Process com.xyz (pid 13667) has died.
I/WindowManager(   81): WIN DEATH: Window{4073ceb8 com.xyz/com.xyz.AsyncTaskTestActivity paused=false}
I/ActivityManager(   81): Start proc com.xyz for activity com.xyz/.AsyncTaskTestActivity: pid=13698 uid=10101 gids={}
I/ActivityManager(   81): Displayed com.xyz/.AsyncTaskTestActivity: +44ms (total +65ms)
D/~~~~    (13698): ~~~onCreate ~~~ com.xyz.AsyncTaskTestActivity@40517238
于 2013-01-30T05:09:36.357 に答える