インスタンスの状態を保存する方法や、画面の回転中に破壊されるアクティビティに対処する方法について、多くのことを読みました。
多くの可能性があるように見えますが、AsyncTaskの結果を取得するのにどれが最適かはわかりません。
単純に再び開始され、アクティビティのisFinishing()
メソッドを呼び出すAsyncTasksがいくつかあります。アクティビティが終了しても、何も更新されません。
問題は、失敗または成功する可能性のあるWebサービスへの要求を行うタスクが1つあり、タスクを再起動するとユーザーの経済的損失が発生することです。
これをどのように解決しますか?可能な解決策の長所と短所は何ですか?
私の最初の提案は、画面の回転時にアクティビティをリセットする必要があることを確認することです(デフォルトの動作)。ローテーションで問題が発生するたびに、この属性をAndroidManifest.xmlの_<activity>
_タグに追加しましたが、問題ありませんでした。
_Android:configChanges="keyboardHidden|orientation"
_
奇妙に見えますが、それがonConfigurationChanged()
メソッドに渡すのは、もしあなたがそれを提供しなければ、レイアウトを再測定すること以外は何もしません。ほとんどの場合、回転を処理します。
code.google.com/p/shelves でAsyncTask
sと向きの変更の処理方法を確認できます。さまざまな方法がありますが、このアプリで選択したのは、現在実行中のタスクをキャンセルし、その状態を保存し、新しいActivity
が作成されたときに保存された状態で新しいタスクを開始することです。それは簡単で、うまく機能し、ボーナスとして、ユーザーがアプリを離れたときにタスクを停止します。
onRetainNonConfigurationInstance()
を使用してAsyncTask
を新しいActivity
に渡すこともできます(ただし、以前のActivity
がこのようにリークしないように注意してください)。
これは、Androidに関して私が見た中で最も興味深い質問です!!!実際、私は過去数ヶ月の間にすでに解決策を探していました。まだ解決していません。
注意してください、単にオーバーライドします
Android:configChanges="keyboardHidden|orientation"
ものは十分ではありません。
AsyncTaskの実行中にユーザーが電話を受けた場合を考えます。リクエストはすでにサーバーによって処理されているため、AsyncTaskは応答を待っています。電話アプリがフォアグラウンドになったため、この瞬間にアプリはバックグラウンドになります。 OSはバックグラウンドにあるため、アクティビティを強制終了する場合があります。
Androidが提供するシングルトンの現在のAsyncTaskへの参照を常に保持しないのはなぜですか?
PreExecuteまたはBuilderでタスクが開始されるたびに、以下を定義します。
_((Application) getApplication()).setCurrentTask(asyncTask);
_
終了するたびに、nullに設定します。
そうすれば、特定のロジックに合わせてonCreateまたはonResumeのようなことを実行できる参照が常にあります。
this.asyncTaskReference = ((Application) getApplication()).getCurrentTask();
Nullの場合、現在実行中のものがないことがわかります。
:-)
これに最も適切な方法は、フラグメントを使用して、非同期タスクのインスタンスをローテーションで保持することです。
以下は、この手法をアプリに簡単に統合できるようにする非常に簡単な例へのリンクです。
私の観点では、現在のActivityオブジェクトからデカップリングし、方向の変更後に新しいActivityオブジェクトにバインドするonRetainNonConfigurationInstance
を介してasynctaskを保存する方が良いでしょう。 ここ AsyncTaskとProgressDialogを操作する非常に良い例が見つかりました。
Pro Android 4
。著者は、あなたがweak reference
。
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
}
}
これをご覧ください post 。この投稿には、1つのサンプルアプリケーションで画面の回転が発生した場合に、AsyncTaskが長時間実行操作とメモリリークを実行することが含まれます。サンプルアプリは source forge で利用できます
考慮すべきことの1つは、AsyncTaskの結果をタスクを開始したアクティビティでのみ使用可能にするかどうかです。はいの場合、 Romain Guyの答え が最適です。アプリケーションの他のアクティビティで使用できるようにする場合は、onPostExecute
でLocalBroadcastManager
を使用できます。
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(new Intent("finished"));
また、アクティビティが一時停止中にブロードキャストが送信された場合、アクティビティが状況を正しく処理することを確認する必要があります。
私の解決策。
私の場合、同じコンテキストの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();
}
_
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">
@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);
}
}
}