画面の回転中にAsyncTaskを処理する方法は?


88

インスタンスの状態を保存する方法や、画面の回転中にアクティビティが破壊されるのに対処する方法についてよく読みました。

多くの可能性があるようですが、AsyncTaskの結果を取得するのにどれが最適かわかりません。

単に再起動しisFinishing()てアクティビティのメソッドを呼び出すAsyncTaskがいくつかありますが、アクティビティが終了している場合は何も更新されません。

問題は、失敗または成功する可能性のあるWebサービスへの要求を実行するタスクが1つあり、タスクを再開するとユーザーの経済的損失が発生することです。

これをどのように解決しますか?考えられる解決策の長所または短所は何ですか?


1
ここで私の答えを参照してください。またsetRetainInstance(true)実際に何が役立つかについてのこの情報を見つけるかもしれません。
Timmmm 2012年

私がすることは、asyncTaskが実行している処理を(スレッドで)実行するローカルサービスを実装することです。結果を表示するには、データをアクティビティにブロードキャストします。現在、アクティビティはデータの表示のみを担当し、画面の回転によって処理が中断されることはありません
誰か

AsyncTaskの代わりにAsyncTaskLoaderを使用するのはどうですか?
Sourangshu Biswas 2015

回答:


6

私の最初の提案は、画面の回転時にアクティビティをリセットする必要があることを確認することです(デフォルトの動作)。回転で問題が発生するたび<activity>に、AndroidManifest.xmlのタグにこの属性を追加しましたが、問題はありませんでした。

android:configChanges="keyboardHidden|orientation"

奇妙に見えますが、それがあなたのonConfigurationChanged()メソッドに引き継ぐものです。それを提供しない場合は、レイアウトを再測定する以外に何もしません。これは、ほとんどの場合、回転を処理するのに完全に適切な方法のようです。 。


5
ただし、これにより、アクティビティがレイアウトを変更できなくなります。したがって、ユーザーは、ニーズではなく、アプリケーションによって指定された特定の方向でデバイスを使用するように強制されます。
Janusz 2010

77
この手法を使用すると、構成固有のリソースを簡単に使用できなくなります。たとえば、レイアウト、ドローアブル、文字列など、縦向きと横向きで異なるものが必要な場合は、デフォルトの動作が必要になります。構成の変更を上書きするのは、非常に特殊な場合(ゲーム、Webブラウザーなど)でのみ行う必要があり、自分自身を制約しているため、怠惰や利便性からではありません。
Romain Guy

38
さて、それだけです、ロメイン。「レイアウト、ドローアブル、文字列など、縦向きと横向きで異なるものが必要な場合は、デフォルトの動作が必要になります」とは、想像するよりもはるかにまれなユースケースだと思います。あなたが「非常に特殊なケース」と呼ぶものは、私が信じるほとんどの開発者です。すべての次元で機能する相対レイアウトを使用することがベストプラクティスであり、それほど難しくはありません。怠惰の話は非常に誤った方向に進んでいます。これらの手法は、開発時間を短縮するのではなく、ユーザーエクスペリエンスを向上させるためのものです。
ジムブラックラー2010

2
これはLinearLayoutに最適であることがわかりましたが、RelativeLayoutを使用すると、横向きモードに切り替えたときにレイアウトが正しく再描画されません(少なくともN1では)。:この質問を参照してくださいstackoverflow.com/questions/2987049/...
JohnRock

9
私はRomainに同意します(彼は彼が何について話しているかを知っていて、OSを開発しています)。アプリケーションをタブレットに移植したいときに、引き伸ばされたときにUIがひどく見えるとどうなりますか?この答えのアプローチを取る場合は、この怠惰なハックを行ったため、ソリューション全体を再コーディングする必要があります。
オースティンマホニー2011

46

code.google.com/p/shelvesでAsyncTask、私がどのようにsと向きの変更を処理するかを確認できます。これを行うにはさまざまな方法があります。このアプリで選択した方法は、現在実行中のタスクをキャンセルし、その状態を保存して、新しいタスクが作成されたときに保存された状態で新しいタスクを開始することです。簡単に実行でき、うまく機能します。ボーナスとして、ユーザーがアプリを離れたときにタスクを停止します。Activity

また、を使用onRetainNonConfigurationInstance()AsyncTaskて新しいものに渡すこともできますActivityActivityただし、この方法で前のものをリークしないように注意してください)。


1
私はそれを試し、本の検索の中断中に回転すると、回転していないときよりも結果が少なくなり、ひどい
max4ever 2012年

1
そのコードでAsyncTaskの単一の使用法を見つけることができませんでした。似たようなクラスUserTaskがあります。このプロジェクトはAsyncTaskよりも前のものですか?
devconsole 2013年

7
AsyncTaskはUserTaskから来ました。私はもともと自分のアプリ用にUserTaskを作成し、後でそれをAsyncTaskに変換しました。名前が変更されたことを忘れてしまい申し訳ありません。
Romain Guy

@RomainGuyこんにちは、あなたが元気であることを願っています。コードによると、最初はタスクがキャンセルされましたが、正常にキャンセルされなかったにもかかわらず、2つのリクエストがサーバーに送信されます。理由はわかりません。これを解決する方法はありますか?
iamcrypticcoder 2014年

10

これは私がAndroidに関して見た中で最も興味深い質問です!!! 実際、私はこの数か月間、すでに解決策を探していました。まだ解決していません。

注意してください、単にオーバーライドします

android:configChanges="keyboardHidden|orientation"

ものは十分ではありません。

AsyncTaskの実行中にユーザーが電話を受信した場合を考えてみます。リクエストはすでにサーバーによって処理されているため、AsyncTaskは応答を待っています。この瞬間、電話アプリがフォアグラウンドになったばかりなので、アプリはバックグラウンドになります。OSはバックグラウンドにあるため、アクティビティを強制終了する可能性があります。


6

Androidが提供するシングルトンの現在のAsyncTaskへの参照を常に保持しませんか?

タスクが開始するときはいつでも、PreExecuteまたはBuilderで、以下を定義します。

((Application) getApplication()).setCurrentTask(asyncTask);

終了するたびに、nullに設定します。

そうすれば、特定のロジックに応じて、onCreateやonResumeなどを実行できる参照が常にあります。

this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();

nullの場合、現在実行中のものがないことがわかります。

:-)


これは機能しますか?誰かがこれをテストしましたか?電話の中断が発生した場合、または新しいアクティビティに移動してから戻った場合でも、タスクはシステムによって強制終了されますか?
ロバート

6
Applicationインスタンスには独自のライフサイクルがあります。OSによっても強制終了される可能性があるため、このソリューションでは再現が難しいバグが発生する可能性があります。
Vit Khudenko 2011

7
私は思った:アプリケーションが殺されると、アプリ全体が殺される(したがって、すべてのAsyncTasksも)?
manmal 2011

すべての非同期タスクがなくてもアプリケーションを強制終了できると思います(非常にまれです)。しかし、@ Arhimedを使用すると、各非同期タスクの開始時と終了時に簡単に確認できるため、バグを回避できます。
neteinstein 2011

5

これを行う最も適切な方法は、フラグメントを使用して、ローテーションで非同期タスクのインスタンスを保持することです。

これは非常に単純な例へのリンクであり、この手法をアプリに簡単に統合できます。

https://gist.github.com/daichan4649/2480065


保持されたフラグメントを利用する別のチュートリアルは次のとおり
2011/09/01


3

私の見解では、非同期タスクをonRetainNonConfigurationInstance現在のアクティビティオブジェクトから切り離し、方向の変更後に新しいアクティビティオブジェクトにバインドすることで、非同期タスクを保存することをお勧めします。ここで、AsyncTaskとProgressDialogを操作する方法の非常に良い例を見つけました。


2

Android:バックグラウンド処理/構成変更を伴う非同期操作

バックグラウンドプロセス中に非同期操作の状態を維持するには:フラグメントの助けを借りることができます。

次の手順を参照してください。

ステップ1:ヘッダーのないフラグメントを作成します。たとえば、バックグラウンドタスクを作成し、その中にプライベート非同期タスククラスを追加します。

ステップ2(オプションのステップ):アクティビティの上にロードカーソルを置きたい場合は、以下のコードを使用します。

ステップ3:メインアクティビティで、ステップ1で定義したBackgroundTaskCallbacksインターフェイスを実装します

class BackgroundTask extends Fragment {
public BackgroundTask() {

}

// Add a static interface 

static interface BackgroundTaskCallbacks {
    void onPreExecute();

    void onCancelled();

    void onPostExecute();

    void doInBackground();
}

private BackgroundTaskCallbacks callbacks;
private PerformAsyncOpeation asyncOperation;
private boolean isRunning;
private final String TAG = BackgroundTask.class.getSimpleName();

/**
 * Start the async operation.
 */
public void start() {
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION ENTER *********");
    if (!isRunning) {
        asyncOperation = new PerformAsyncOpeation();
        asyncOperation.execute();
        isRunning = true;
    }
    Log.d(TAG, "********* BACKGROUND TASK START OPERATION EXIT *********");
}

/**
 * Cancel the background task.
 */
public void cancel() {
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION ENTER *********");
    if (isRunning) {
        asyncOperation.cancel(false);
        asyncOperation = null;
        isRunning = false;
    }
    Log.d(TAG, "********* BACKGROUND TASK CANCEL OPERATION EXIT *********");
}

/**
 * Returns the current state of the background task.
 */
public boolean isRunning() {
    return isRunning;
}

/**
 * Android passes us a reference to the newly created Activity by calling
 * this method after each configuration change.
 */
public void onAttach(Activity activity) {
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH ENTER *********");
    super.onAttach(activity);
    if (!(activity instanceof BackgroundTaskCallbacks)) {
        throw new IllegalStateException(
                "Activity must implement the LoginCallbacks interface.");
    }

    // Hold a reference to the parent Activity so we can report back the
    // task's
    // current progress and results.
    callbacks = (BackgroundTaskCallbacks) activity;
    Log.d(TAG, "********* BACKGROUND TASK ON ATTACH EXIT *********");
}

public void onCreate(Bundle savedInstanceState) {
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE ENTER *********");
    super.onCreate(savedInstanceState);
    // Retain this fragment across configuration changes.
    setRetainInstance(true);
    Log.d(TAG, "********* BACKGROUND TASK ON CREATE EXIT *********");
}

public void onDetach() {
    super.onDetach();
    callbacks = null;
}

private class PerformAsyncOpeation extends AsyncTask<Void, Void, Void> {
    protected void onPreExecute() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPreExecute();
        }
        isRunning = true;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON PRE EXECUTE EXIT *********");
    }

    protected Void doInBackground(Void... params) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND ENTER *********");
        if (callbacks != null) {
            callbacks.doInBackground();
        }
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > DO IN BACKGROUND EXIT *********");
        return null;
    }

    protected void onCancelled() {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL ENTER *********");
        if (callbacks != null) {
            callbacks.onCancelled();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON CANCEL EXIT *********");
    }

    protected void onPostExecute(Void ignore) {
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE ENTER *********");
        if (callbacks != null) {
            callbacks.onPostExecute();
        }
        isRunning = false;
        Log.d(TAG,
                "********* BACKGROUND TASK :-> ASYNC OPERATION :- > ON POST EXECUTE EXIT *********");
    }
}

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    setRetainInstance(true);
}

public void onStart() {
    super.onStart();
}

public void onResume() {
    super.onResume();
}

public void onPause() {
    super.onPause();
}

public void onStop() {
    super.onStop();
}

public class ProgressIndicator extends Dialog {

public ProgressIndicator(Context context, int theme) {
    super(context, theme);
}

private ProgressBar progressBar;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    setContentView(R.layout.progress_indicator);
    this.setCancelable(false);
    progressBar = (ProgressBar) findViewById(R.id.progressBar);
    progressBar.getIndeterminateDrawable().setColorFilter(R.color.DarkBlue, android.graphics.PorterDuff.Mode.SCREEN);
}

@Override
public void show() {
    super.show();
}

@Override
public void dismiss() {
    super.dismiss();
}

@Override
public void cancel() {
    super.cancel();
}

public class MyActivity extends FragmentActivity implements BackgroundTaskCallbacks,{

private static final String KEY_CURRENT_PROGRESS = "current_progress";

ProgressIndicator progressIndicator = null;

private final static String TAG = MyActivity.class.getSimpleName();

private BackgroundTask task = null;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(//"set your layout here");
    initialize your views and widget here .............



    FragmentManager fm = getSupportFragmentManager();
    task = (BackgroundTask) fm.findFragmentByTag("login");

    // If the Fragment is non-null, then it is currently being
    // retained across a configuration change.
    if (task == null) {
        task = new BackgroundTask();
        fm.beginTransaction().add(task, "login").commit();
    }

    // Restore saved state
    if (savedInstanceState != null) {
        Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON CREATE :: "
                + task.isRunning());
        if (task.isRunning()) {
            progressIndicator = new ProgressIndicator(this,
                    R.style.TransparentDialog);
            if (progressIndicator != null) {
                progressIndicator.show();
            }
        }
    }
}

@Override
protected void onPause() {
    // TODO Auto-generated method stub
    super.onPause();

}

@Override
protected void onSaveInstanceState(Bundle outState) {
    // save the current state of your operation here by saying this 

    super.onSaveInstanceState(outState);
    Log.i(TAG, "KEY_CURRENT_PROGRESS_VALUE ON SAVE INSTANCE :: "
            + task.isRunning());
    outState.putBoolean(KEY_CURRENT_PROGRESS, task.isRunning());
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}


private void performOperation() {

            if (!task.isRunning() && progressIndicator == null) {
                progressIndicator = new ProgressIndicator(this,
                        R.style.TransparentDialog);
                progressIndicator.show();
            }
            if (task.isRunning()) {
                task.cancel();
            } else {
                task.start();
            }
        }


@Override
protected void onDestroy() {
    super.onDestroy();
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
    }
    progressIndicator = null;
}

@Override
public void onPreExecute() {
    Log.i(TAG, "CALLING ON PRE EXECUTE");
}

@Override
public void onCancelled() {
    Log.i(TAG, "CALLING ON CANCELLED");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();

}

public void onPostExecute() {
    Log.i(TAG, "CALLING ON POST EXECUTE");
    if (progressIndicator != null) {
        progressIndicator.dismiss();
        progressIndicator.cancel();
        progressIndicator = null;
    }
}

@Override
public void doInBackground() {
    // put your code here for background operation
}

}


1

考慮すべきことの1つは、AsyncTaskの結果を、タスクを開始したアクティビティのみが使用できるようにするかどうかです。はいの場合、RomainGuyの答えが最適です。アプリケーションの他のアクティビティで使用できるようにする必要がある場合は、でonPostExecuteを使用できますLocalBroadcastManager

LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));

また、アクティビティが一時停止しているときにブロードキャストが送信されたときに、アクティビティが状況を正しく処理することを確認する必要があります。



0

私の解決策。

私の場合、同じコンテキストのAsyncTasksのチェーンがあります。アクティビティは最初のものにのみアクセスできました。実行中のタスクをキャンセルするには、次のようにしました。

public final class TaskLoader {

private static AsyncTask task;

     private TaskLoader() {
         throw new UnsupportedOperationException();
     }

     public static void setTask(AsyncTask task) {
         TaskLoader.task = task;
     }

    public static void cancel() {
         TaskLoader.task.cancel(true);
     }
}

タスクdoInBackground()

protected Void doInBackground(Params... params) {
    TaskLoader.setTask(this);
    ....
}

アクティビティonStop()またはonPause()

protected void onStop() {
    super.onStop();
    TaskLoader.cancel();
}

0
@Override
protected void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    final AddTask task = mAddTask;
    if (task != null && task.getStatus() != UserTask.Status.FINISHED) {
        final String bookId = task.getBookId();
        task.cancel(true);

        if (bookId != null) {
            outState.putBoolean(STATE_ADD_IN_PROGRESS, true);
            outState.putString(STATE_ADD_BOOK, bookId);
        }

        mAddTask = null;
    }
}

@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
    if (savedInstanceState.getBoolean(STATE_ADD_IN_PROGRESS)) {
        final String id = savedInstanceState.getString(STATE_ADD_BOOK);
        if (!BooksManager.bookExists(getContentResolver(), id)) {
            mAddTask = (AddTask) new AddTask().execute(id);
        }
    }
}

0

android:configChanges = "keyboardHidden | orientation | screenSize"を追加することもできます

あなたのマニフェストの例に私はそれが役立つことを願っています

 <application
    android:name=".AppController"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:configChanges="keyboardHidden|orientation|screenSize"
    android:theme="@style/AppTheme">
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.