屏幕旋转期间如何处理AsyncTask?


88

我阅读了很多有关如何保存实例状态或如何处理在屏幕旋转期间被破坏的活动的文章。

似乎有很多可能性,但是我还没有弄清楚哪种方法最适合检索AsyncTask的结果。

我有一些AsyncTasks只是重新启动并调用isFinishing()活动的方法,如果活动完成,它们将不会更新任何内容。

问题是我有一个任务向可能失败或成功的Web服务发出请求,而重新启动该任务将给用户造成经济损失。

您将如何解决?可能的解决方案有哪些优点或缺点?


1
在这里查看我的答案。您可能还会发现有关setRetainInstance(true)实际上有帮助的信息。
Timmmm 2012年

我要做的只是实现一个本地服务,该服务执行asyncTask正在执行的处理(在线程中)。要显示结果,请将数据广播到您的活动中。现在,该活动仅负责显示数据,并且处理不会因屏幕旋转而中断。
某处某人2013年

使用AsyncTaskLoader而不是AsyncTask呢?
Sourangshu Biswas

Answers:


6

我的第一个建议是确保您确实需要在屏幕旋转时重置活动(默认行为)。每次遇到旋转问题时,都会将此属性添加到<activity>AndroidManifest.xml中的标签中,并且效果很好。

android:configChanges="keyboardHidden|orientation"

看起来很奇怪,但是它的作用是如何传递给您的onConfigurationChanged()方法,如果您不提供它,那么它除了重新测量布局外什么也没有做,这似乎是大多数时候处理旋转的完美方法。


5
但这将阻止活动更改布局。因此,迫使用户按照您的应用而不是他的需求所指定的特定方向使用他的设备。
Janusz

77
使用此技术可防止您轻松使用特定于配置的资源。例如,如果您希望布局,可绘制对象或字符串或其他在纵向和横向上有所不同的对象,则需要默认行为。覆盖配置更改仅应在非常特殊的情况下(游戏,Web浏览器等)进行,而不是出于懒惰或方便,因为这会限制自己。
罗曼·盖伊

38
好吧,罗曼。“如果您希望布局,可绘制对象或字符串或其他在纵向和横向上有所不同的用户,则将需要默认行为”,我认为这是一种比您设想的情况少得多的用例。我相信大多数开发人员都是您所说的“非常具体的情况”。使用适用于所有维度的相对布局是最佳实践,并不难。懒惰的说法是高度误导的,这些技术是在提高用户体验而不减少开发时间的同时。
吉姆·布莱克勒

2
我发现这非常适合LinearLayout,但是使用RelativeLayout时,切换到横向模式(至少不是在N1上)时不能正确重绘布局。看到这个问题:stackoverflow.com/questions/2987049/...
JohnRock

9
我同意Romain(他知道他在说什么,他在开发OS)。如果您想将应用程序移植到平板电脑上,并且拉伸后的UI看起来很恐怖,会发生什么?如果您采用此答案的方法,则您将需要重新编码整个解决方案,因为您使用了这种懒惰的技巧。
奥斯汀·马奥尼

46

您可以AsyncTaskcode.google.com/p/shelves上查看我如何处理s和方向更改。有多种方法可以执行此操作,我在此应用中选择的一种方法是取消任何当前正在运行的任务,保存其状态,并在创建新任务时以保存的状态开始新任务Activity。这很容易做到,效果很好,而且作为一项奖励,当用户离开应用程序时,它可以停止您的任务。

您还可以onRetainNonConfigurationInstance()用来将您AsyncTask的内容传递给新用户Activity(但是请注意不要以Activity这种方式泄漏前者。)


1
我尝试过并在图书搜索中断期间进行了旋转,与未旋转时相比,给我的结果要少得多,太糟糕了
max4ever 2012年

1
我在该代码中找不到AsyncTask的单一用法。有一个类UserTask看起来很相似。这个项目是否早于AsyncTask?
devconsole

7
AsyncTask来自UserTask。我最初为自己的应用程序编写了UserTask,后来将其转换为AsyncTask。对不起,我忘了它被重新命名。
罗曼·盖伊

@RomainGuy嗨,希望您一切都好。根据您的代码2,尽管开始时任务已取消,但请求未成功取消,但请求已发送到服务器。我不知道为什么 你能告诉我有什么办法解决这个问题吗?
iamcrypticcoder 2014年

10

这是我所见过的关于Android的最有趣的问题!!!实际上,最近几个月我一直在寻找解决方案。还没解决。

小心,只要覆写

android:configChanges="keyboardHidden|orientation"

东西还不够。

考虑当您的AsyncTask运行时用户接到电话的情况。服务器已经在处理您的请求,因此AsyncTask正在等待响应。在这一刻,您的应用程序进入后台,因为“电话”应用程序刚刚出现在前台。操作系统可能处于后台状态,因此可能会终止您的活动。


6

为什么不总是在Android提供的Singleton上始终引用当前的AsyncTask?

每当任务启动时,都可以在PreExecute或构建器上定义:

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

每当完成时,请将其设置为null。

这样,您将始终拥有一个引用,该引用使您可以根据自己的特定逻辑执行类似onCreate或onResume的操作:

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

如果为空,则表示当前没有任何运行!

:-)


这样行吗?有人测试过吗?如果发生电话中断或导航到新活动然后返回,该任务是否仍会被系统杀死?
罗伯特

6
Application实例具有其自身的生命周期-也可以被操作系统杀死,因此该解决方案可能会导致难以重现的错误。
Vit Khudenko

7
我以为:如果应用程序被杀死,整个应用程序也将被杀死(因此,所有AsyncTasks也是如此)?
manmal 2011年

我认为可以在没有所有异步任务的情况下杀死应用程序(非常少见)。但是@Arhimed在每个asynctask的开始和结束时都进行了一些易于执行的验证,可以避免这些错误。
neteinstein 2011年



3

以我的观点,最好通过将asynctaskonRetainNonConfigurationInstance与当前Activity对象解耦并在方向更改后将其绑定到新的Activity对象来存储它。在这里,我找到了一个很好的示例,说明如何使用AsyncTask和ProgressDialog。


2

Android:后台处理/异步配置更改

要在后台进程中保持异步状态,您可以使用片段帮助。

请参阅以下步骤:

步骤1:创建一个无标题的片段,让它说是后台任务,并在其中添加一个私有的异步任务类。

第2步(可选步骤):如果要将加载光标置于活动顶部,请使用以下代码:

步骤3:在主要的Activity中,实现步骤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

要考虑的一件事是AsyncTask的结果是否仅应用于启动任务的活动。如果是,那么罗曼·盖伊的答案是最好的。如果您的应用程序的其他活动onPostExecute可以使用它,则可以使用LocalBroadcastManager

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

您还需要确保活动被暂停时发送广播时,活动能够正确处理情况。


1

看一下这个帖子。这篇文章涉及AsyncTask在一个示例应用程序中同时发生屏幕旋转时执行长时间运行的操作和内存泄漏。该示例应用程序可在源伪造中获得


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">
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.