最佳实践:屏幕旋转期间使用AsyncTask

153

AsyncTask是在另一个线程中运行复杂任务的好工具。

但是,如果在 AsyncTask 仍在运行时发生屏幕方向或其他配置更改,则当前的 Activity 将被销毁并重新启动。由于 AsyncTask 的实例与该活动相关联,因此它会失败并导致“强制关闭”消息窗口。

因此,我正在寻找某种“最佳实践”来避免这些错误,并防止 AsyncTask 失败。

目前我见过的方法有:

  • 禁用方向更改(肯定不是你应该处理这个问题的方式)
  • 让任务保持存活,并通过 onRetainNonConfigurationInstance 更新它,以使用新的活动实例
  • Activity 销毁时取消任务,并在创建 Activity 时重新启动任务。
  • 将任务绑定到应用程序类而不是活动实例。
  • 在“书架”项目中使用的某些方法(通过 onRestoreInstanceState)

一些代码示例:

Android AsyncTasks during a screen rotation, Part IPart II

ShelvesActivity.java

你能帮助我找到最佳方法来解决这个问题,并且易于实现吗?代码本身也很重要,因为我不知道如何正确解决这个问题。


有一个重复的问题,请查看此链接:https://dev59.com/Dm455IYBdhLWcg3wAvbQ。 - TeaCupApp
这是来自Mark Murphy的博客...AsyncTask和屏幕旋转可能会有所帮助...链接 - Gopal
虽然这是一篇旧帖子,但在我看来,这个方法更简单(而且更好?)。 - DroidDev
我只是想知道为什么文档没有涉及这些非常琐碎的情况。 - Sreekanth Karumanaghat
9个回答

141

不要使用 android:configChanges 来解决这个问题,这是非常糟糕的做法。

也不要使用 Activity#onRetainNonConfigurationInstance()。这种方式不够模块化,不适用于基于Fragment的应用程序。

您可以阅读我的文章,描述如何使用保留的Fragment处理配置更改。它很好地解决了在旋转更改中保留AsyncTask的问题。您只需要将AsyncTask托管在一个Fragment中,在Fragment上调用setRetainInstance(true),并通过保留的FragmentAsyncTask的进度/结果报告给其Activity


26
好的想法,但并非每个人都使用Fragments。在Fragments成为可选项之前,很多遗留代码就已经写好了。 - SMBiggs
15
支持库可以让我们在 Android 1.6 及以后的版本中使用 Fragment。你能否举个例子,说明一些仍在积极使用的旧代码可能会遇到支持库 Fragment 的问题?但我认为这不是一个问题。 - Alex Lockwood
4
@tactoth,我在回答中没有觉得有必要涉及这些问题,因为99.9%的人不再使用 TabActivity。老实说,我甚至不确定我们为什么要谈论这个......每个人都认为 Fragment 是正确的选择。 :) - Alex Lockwood
2
如果必须从嵌套的片段调用AsyncTask怎么办? - Eduardo Naveda
4
@AlexLockwood说:“大家都认为Fragment是正确的方法。”但方方团队的开发者可能不同意! - JBeckton
显示剩余19条评论

36

我通常通过在AsyncTask的.onPostExecute()回调中触发广播Intent来解决这个问题,这样它们就不会直接修改启动它们的Activity。活动使用动态BroadcastReceiver监听这些广播并相应地采取行动。

这样,AsyncTasks不需要关心处理其结果的特定Activity实例。它们只需在完成后“大声喊出”,如果此时有一个Activity(处于活动状态和焦点/处于恢复状态)对任务结果感兴趣,则会得到处理。

这涉及更多的开销,因为运行时需要处理广播,但我通常不介意。我认为使用LocalBroadcastManager而不是默认的系统范围广播可以加快速度。


6
如果您能在答案中添加一个例子,那将更有帮助。 - Sankar V
1
我认为这是提供活动和片段之间耦合度较少的解决方案。 - Roger Garzon Nieto
7
这可能是解决方案的一部分,但似乎无法解决AsyncTask在屏幕方向改变后重新创建的问题。 - miguel
4
如果你很不走运,在直播期间两个活动都不在进行中怎么办?(例如,你正在游戏中) - Sam

24
这是另一个使用Fragment处理运行时配置更改(例如用户旋转屏幕)的AsyncTask示例,使用setRetainInstance(true)。还演示了确定(定期更新)进度条。
该示例部分基于官方文档“在配置更改期间保留对象”
在此示例中,需要在后台线程中加载互联网图像到UI。
Alex Lockwood认为,在使用AsyncTask处理运行时配置更改时,“保留片段”是最佳实践。在Android Studio的Lint中,onRetainNonConfigurationInstance()已被弃用。官方文档警告我们不要使用android:configChanges,来自“自己处理配置更改”...

自己处理配置更改可能会使使用替代资源变得更加困难,因为系统不会自动应用它们。当您必须避免由于配置更改而重新启动时,应将此技术视为最后一种选择,并且不建议大多数应用程序使用。

然后就有了是否应该在后台线程中使用AsyncTask的问题。 AsyncTask官方参考文档警告...

异步任务应该理想地用于短时间操作(最多几秒钟)。如果您需要长时间运行线程,强烈建议使用java.util.concurrent包提供的各种API,例如Executor、ThreadPoolExecutor和FutureTask。

或者,可以使用服务、加载器(使用CursorLoader或AsyncTaskLoader)或内容提供程序执行异步操作。
我将本文其余部分分为:
  • 过程;以及
  • 上述过程的所有代码。

过程

  1. Start with a basic AsyncTask as an inner class of an activity (it doesn't need to be an inner class but it will probably be convenient to be). At this stage the AsyncTask does not handle runtime configuration changes.

    public class ThreadsActivity extends ActionBarActivity {
    
        private ImageView mPictureImageView;
    
        private class LoadImageFromNetworkAsyncTask
                              extends AsyncTask<String, Void, Bitmap> {
    
            @Override
            protected Bitmap doInBackground(String... urls) {
                return loadImageFromNetwork(urls[0]);
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
                mPictureImageView.setImageBitmap(bitmap);
            }
        }
    
        /**
         * Requires in AndroidManifext.xml
         *  <uses-permission android:name="android.permission.INTERNET" />
         */
        private Bitmap loadImageFromNetwork(String url) {
            Bitmap bitmap = null;
            try {
                bitmap = BitmapFactory.decodeStream((InputStream)
                                              new URL(url).getContent());
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bitmap;
        }
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            mPictureImageView =
                (ImageView) findViewById(R.id.imageView_picture);
        }
    
        public void getPicture(View view) {
            new LoadImageFromNetworkAsyncTask()
                .execute("http://i.imgur.com/SikTbWe.jpg");
        }
    
    }
    
  2. Add a nested class RetainedFragment that extends the Fragement class and doesn't have it's own UI. Add setRetainInstance(true) to the onCreate event of this Fragment. Provide procedures to set and get your data.

    public class ThreadsActivity extends Activity {
    
        private ImageView mPictureImageView;
        private RetainedFragment mRetainedFragment = null;
        ...
    
        public static class RetainedFragment extends Fragment {
    
            private Bitmap mBitmap;
    
            @Override
            public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
    
                // The key to making data survive
                // runtime configuration changes.
                setRetainInstance(true);
            }
    
            public Bitmap getData() {
                return this.mBitmap;
            }
    
            public void setData(Bitmap bitmapToRetain) {
                this.mBitmap = bitmapToRetain;
            }
        }
    
        private class LoadImageFromNetworkAsyncTask
                        extends AsyncTask<String, Integer,Bitmap> {
        ....
    
  3. In the outermost Activity class's onCreate() handle the RetainedFragment: Reference it if it already exists (in case the Activity is restarting); create and add it if it doesn't exist; Then, if it already existed, get data from the RetainedFragment and set your UI with that data.

    public class ThreadsActivity extends Activity {
    
        ...
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_threads);
    
            final String retainedFragmentTag = "RetainedFragmentTag";
    
            mPictureImageView =
                      (ImageView) findViewById(R.id.imageView_picture);
            mLoadingProgressBar =
                    (ProgressBar) findViewById(R.id.progressBar_loading);
    
            // Find the RetainedFragment on Activity restarts
            FragmentManager fm = getFragmentManager();
            // The RetainedFragment has no UI so we must
            // reference it with a tag.
            mRetainedFragment =
              (RetainedFragment) fm.findFragmentByTag(retainedFragmentTag);
    
            // if Retained Fragment doesn't exist create and add it.
            if (mRetainedFragment == null) {
    
                // Add the fragment
                mRetainedFragment = new RetainedFragment();
                fm.beginTransaction()
                    .add(mRetainedFragment, retainedFragmentTag).commit();
    
            // The Retained Fragment exists
            } else {
    
                mPictureImageView
                    .setImageBitmap(mRetainedFragment.getData());
            }
        }
    
  4. Initiate the AsyncTask from the UI

    public void getPicture(View view) {
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }
    
  5. Add and code a determinate progress bar:

    • Add a progress bar to the UI layout;
    • Get a reference to it in the Activity oncreate();
    • Make it visible and invisble at the start and end of the process;
    • Define the progress to report to UI in onProgressUpdate.
    • Change the AsyncTask 2nd Generic parameter from Void to a type that can handle progress updates (e.g. Integer).
    • publishProgress at regular points in doInBackground().

以上过程的所有代码

活动布局。

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.mysecondapp.ThreadsActivity">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        android:paddingBottom="@dimen/activity_vertical_margin"
        android:paddingLeft="@dimen/activity_horizontal_margin"
        android:paddingRight="@dimen/activity_horizontal_margin"
        android:paddingTop="@dimen/activity_vertical_margin">

        <ImageView
            android:id="@+id/imageView_picture"
            android:layout_width="300dp"
            android:layout_height="300dp"
            android:background="@android:color/black" />

        <Button
            android:id="@+id/button_get_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_below="@id/imageView_picture"
            android:onClick="getPicture"
            android:text="Get Picture" />

        <Button
            android:id="@+id/button_clear_picture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignBottom="@id/button_get_picture"
            android:layout_toEndOf="@id/button_get_picture"
            android:layout_toRightOf="@id/button_get_picture"
            android:onClick="clearPicture"
            android:text="Clear Picture" />

        <ProgressBar
            android:id="@+id/progressBar_loading"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@id/button_get_picture"
            android:progress="0"
            android:indeterminateOnly="false"
            android:visibility="invisible" />

    </RelativeLayout>
</ScrollView>

这个活动包含:子类化的AsyncTask内部类;子类化的RetainedFragment内部类,用于处理运行时配置更改(例如,当用户旋转屏幕时);还有一个定期更新确定进度条。

public class ThreadsActivity extends Activity {

    private ImageView mPictureImageView;
    private RetainedFragment mRetainedFragment = null;
    private ProgressBar mLoadingProgressBar;

    public static class RetainedFragment extends Fragment {

        private Bitmap mBitmap;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);

            // The key to making data survive runtime configuration changes.
            setRetainInstance(true);
        }

        public Bitmap getData() {
            return this.mBitmap;
        }

        public void setData(Bitmap bitmapToRetain) {
            this.mBitmap = bitmapToRetain;
        }
    }

    private class LoadImageFromNetworkAsyncTask extends AsyncTask<String,
            Integer, Bitmap> {

        @Override
        protected Bitmap doInBackground(String... urls) {
            // Simulate a burdensome load.
            int sleepSeconds = 4;
            for (int i = 1; i <= sleepSeconds; i++) {
                SystemClock.sleep(1000); // milliseconds
                publishProgress(i * 20); // Adjust for a scale to 100
            }

            return com.example.standardapplibrary.android.Network
                    .loadImageFromNetwork(
                    urls[0]);
        }

        @Override
        protected void onProgressUpdate(Integer... progress) {
            mLoadingProgressBar.setProgress(progress[0]);
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            publishProgress(100);
            mRetainedFragment.setData(bitmap);
            mPictureImageView.setImageBitmap(bitmap);
            mLoadingProgressBar.setVisibility(View.INVISIBLE);
            publishProgress(0);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_threads);

        final String retainedFragmentTag = "RetainedFragmentTag";

        mPictureImageView = (ImageView) findViewById(R.id.imageView_picture);
        mLoadingProgressBar = (ProgressBar) findViewById(R.id.progressBar_loading);

        // Find the RetainedFragment on Activity restarts
        FragmentManager fm = getFragmentManager();
        // The RetainedFragment has no UI so we must reference it with a tag.
        mRetainedFragment = (RetainedFragment) fm.findFragmentByTag(
                retainedFragmentTag);

        // if Retained Fragment doesn't exist create and add it.
        if (mRetainedFragment == null) {

            // Add the fragment
            mRetainedFragment = new RetainedFragment();
            fm.beginTransaction().add(mRetainedFragment,
                                      retainedFragmentTag).commit();

            // The Retained Fragment exists
        } else {

            mPictureImageView.setImageBitmap(mRetainedFragment.getData());
        }
    }

    public void getPicture(View view) {
        mLoadingProgressBar.setVisibility(View.VISIBLE);
        new LoadImageFromNetworkAsyncTask().execute(
                "http://i.imgur.com/SikTbWe.jpg");
    }

    public void clearPicture(View view) {
        mRetainedFragment.setData(null);
        mPictureImageView.setImageBitmap(null);
    }
}

在这个例子中,库函数(使用显式包前缀com.example.standardapplibrary.android.Network引用)会执行真正的工作...
public static Bitmap loadImageFromNetwork(String url) {
    Bitmap bitmap = null;
    try {
        bitmap = BitmapFactory.decodeStream((InputStream) new URL(url)
                .getContent());
    } catch (Exception e) {
        e.printStackTrace();
    }
    return bitmap;
}

请将您的后台任务所需的所有权限添加到 AndroidManifest.xml 文件中...
<manifest>
...
    <uses-permission android:name="android.permission.INTERNET" />

将您的活动添加到AndroidManifest.xml中...
<manifest>
...
    <application>
        <activity
            android:name=".ThreadsActivity"
            android:label="@string/title_activity_threads"
            android:parentActivityName=".MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="com.example.mysecondapp.MainActivity" />
        </activity>

太好了。你应该写一篇关于这个的博客。 - Akh
2
@AKh。你是不是意味着我的回答在Stackoverflow上占用了太多的空间? - John Bentley
1
我认为他的意思是你有一个很棒的答案,应该写一篇博客!=)@JohnBentley - Sandy D.
@SandyD.昨天谢谢你积极的解释。我希望她或他是有意这样做的。 - John Bentley
我也觉得那是个非常棒的回答,我也是这样解读的。像这样完整的回答真的很棒! - Leonardo Sibela
感谢您的反馈 @LeonardoSibela。很高兴它对您有帮助。 - John Bentley

3

最近,我在这里找到了一个不错的解决方案该方案基于通过保留配置保存任务对象。在我看来,该解决方案非常优雅,我已经开始使用它。您只需要将asynctask嵌套到basetask中即可。


非常感谢您提供这个有趣的答案。除了相关问题中提到的解决方案外,这是一个很好的解决方案。 - caw
5
很不幸,这个解决方案使用了已经弃用的方法。 - Damien

3
您可以使用Loader来实现这一点。在此处查看文档

2
加载器已经在Android API 28中被弃用(如链接所示)。 - Sumit
加载器并没有被弃用,只是调用它们的方式发生了变化。 - EdgeDev

3

基于@Alex Lockwood的回答以及@William和@quickdraw mcgraw在此帖子中的回答:如何处理活动/片段暂停时的处理程序消息,我编写了一个通用解决方案。

这样就可以处理旋转,并且如果活动在异步任务执行期间进入后台,活动将在恢复后接收回调(onPreExecute、onProgressUpdate、onPostExecute和onCancelled),因此不会抛出IllegalStateException(请参见如何处理活动/片段暂停时的处理程序消息)。

如果有相同但具有通用参数类型的内容,例如AsyncTask(例如:AsyncTaskFragment<Params,Progress,Result>),那将是很好的,但我没有能够快速完成这项工作,目前也没有时间。如果有人想要改进,请随意!

代码:

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Message;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v7.app.AppCompatActivity;

public class AsyncTaskFragment extends Fragment {

    /* ------------------------------------------------------------------------------------------ */
    // region Classes & Interfaces

    public static abstract class Task extends AsyncTask<Object, Object, Object> {

        private AsyncTaskFragment _fragment;

        private void setFragment(AsyncTaskFragment fragment) {

            _fragment = fragment;
        }

        @Override
        protected final void onPreExecute() {

            // Save the state :
            _fragment.setRunning(true);

            // Send a message :
            sendMessage(ON_PRE_EXECUTE_MESSAGE, null);
        }

        @Override
        protected final void onPostExecute(Object result) {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_POST_EXECUTE_MESSAGE, result);
        }

        @Override
        protected final void onProgressUpdate(Object... values) {

            // Send a message :
            sendMessage(ON_PROGRESS_UPDATE_MESSAGE, values);
        }

        @Override
        protected final void onCancelled() {

            // Save the state :
            _fragment.setRunning(false);

            // Send a message :
            sendMessage(ON_CANCELLED_MESSAGE, null);
        }

        private void sendMessage(int what, Object obj) {

            Message message = new Message();
            message.what = what;
            message.obj = obj;

            Bundle data = new Bundle(1);
            data.putString(EXTRA_FRAGMENT_TAG, _fragment.getTag());
            message.setData(data);

            _fragment.handler.sendMessage(message);
        }
    }

    public interface AsyncTaskFragmentListener {

        void onPreExecute(String fragmentTag);
        void onProgressUpdate(String fragmentTag, Object... progress);
        void onCancelled(String fragmentTag);
        void onPostExecute(String fragmentTag, Object result);
    }

    private static class AsyncTaskFragmentPauseHandler extends PauseHandler {

        @Override
        final protected void processMessage(Activity activity, Message message) {

            switch (message.what) {

                case ON_PRE_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPreExecute(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
                case ON_POST_EXECUTE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onPostExecute(message.getData().getString(EXTRA_FRAGMENT_TAG), message.obj); break; }
                case ON_PROGRESS_UPDATE_MESSAGE : { ((AsyncTaskFragmentListener)activity).onProgressUpdate(message.getData().getString(EXTRA_FRAGMENT_TAG), ((Object[])message.obj)); break; }
                case ON_CANCELLED_MESSAGE : { ((AsyncTaskFragmentListener)activity).onCancelled(message.getData().getString(EXTRA_FRAGMENT_TAG)); break; }
            }
        }
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Attributes

    private Task _task;
    private AsyncTaskFragmentListener _listener;
    private boolean _running = false;

    private static final String EXTRA_FRAGMENT_TAG = "EXTRA_FRAGMENT_TAG";
    private static final int ON_PRE_EXECUTE_MESSAGE = 0;
    private static final int ON_POST_EXECUTE_MESSAGE = 1;
    private static final int ON_PROGRESS_UPDATE_MESSAGE = 2;
    private static final int ON_CANCELLED_MESSAGE = 3;

    private AsyncTaskFragmentPauseHandler handler = new AsyncTaskFragmentPauseHandler();

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Getters

    public AsyncTaskFragmentListener getListener() { return _listener; }
    public boolean isRunning() { return _running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Setters

    public void setTask(Task task) {

        _task = task;
        _task.setFragment(this);
    }

    public void setListener(AsyncTaskFragmentListener listener) { _listener = listener; }
    private void setRunning(boolean running) { _running = running; }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Fragment lifecycle

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }

    @Override
    public void onResume() {

        super.onResume();
        handler.resume(getActivity());
    }

    @Override
    public void onPause() {

        super.onPause();
        handler.pause();
    }

    @Override
    public void onAttach(Activity activity) {

        super.onAttach(activity);
        _listener = (AsyncTaskFragmentListener) activity;
    }

    @Override
    public void onDetach() {

        super.onDetach();
        _listener = null;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */



    /* ------------------------------------------------------------------------------------------ */
    // region Utils

    public void execute(Object... params) {

        _task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params);
    }

    public void cancel(boolean mayInterruptIfRunning) {

        _task.cancel(mayInterruptIfRunning);
    }

    public static AsyncTaskFragment getRetainedOrNewFragment(AppCompatActivity activity, String fragmentTag) {

        FragmentManager fm = activity.getSupportFragmentManager();
        AsyncTaskFragment fragment = (AsyncTaskFragment) fm.findFragmentByTag(fragmentTag);

        if (fragment == null) {

            fragment = new AsyncTaskFragment();
            fragment.setListener( (AsyncTaskFragmentListener) activity);
            fm.beginTransaction().add(fragment, fragmentTag).commit();
        }

        return fragment;
    }

    // endregion
    /* ------------------------------------------------------------------------------------------ */
}

您需要使用PauseHandler:
import android.app.Activity;
import android.os.Handler;
import android.os.Message;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Message Handler class that supports buffering up of messages when the activity is paused i.e. in the background.
 *
 * https://dev59.com/V2sz5IYBdhLWcg3wOlOh
 */
public abstract class PauseHandler extends Handler {

    /**
     * Message Queue Buffer
     */
    private final List<Message> messageQueueBuffer = Collections.synchronizedList(new ArrayList<Message>());

    /**
     * Flag indicating the pause state
     */
    private Activity activity;

    /**
     * Resume the handler.
     */
    public final synchronized void resume(Activity activity) {
        this.activity = activity;

        while (messageQueueBuffer.size() > 0) {
            final Message msg = messageQueueBuffer.get(0);
            messageQueueBuffer.remove(0);
            sendMessage(msg);
        }
    }

    /**
     * Pause the handler.
     */
    public final synchronized void pause() {
        activity = null;
    }

    /**
     * Store the message if we have been paused, otherwise handle it now.
     *
     * @param msg   Message to handle.
     */
    @Override
    public final synchronized void handleMessage(Message msg) {
        if (activity == null) {
            final Message msgCopy = new Message();
            msgCopy.copyFrom(msg);
            messageQueueBuffer.add(msgCopy);
        } else {
            processMessage(activity, msg);
        }
    }

    /**
     * Notification message to be processed. This will either be directly from
     * handleMessage or played back from a saved message when the activity was
     * paused.
     *
     * @param activity  Activity owning this Handler that isn't currently paused.
     * @param message   Message to be handled
     */
    protected abstract void processMessage(Activity activity, Message message);
}

使用示例:

public class TestActivity extends AppCompatActivity implements AsyncTaskFragmentListener {

    private final static String ASYNC_TASK_FRAGMENT_A = "ASYNC_TASK_FRAGMENT_A";
    private final static String ASYNC_TASK_FRAGMENT_B = "ASYNC_TASK_FRAGMENT_B";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        Button testButton = (Button) findViewById(R.id.test_button);
        final AsyncTaskFragment fragment = AsyncTaskFragment.getRetainedOrNewFragment(TestActivity.this, ASYNC_TASK_FRAGMENT_A);

        testButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                if(!fragment.isRunning()) {

                    fragment.setTask(new Task() {

                        @Override
                        protected Object doInBackground(Object... objects) {

                            // Do your async stuff

                            return null;
                        }
                    });

                    fragment.execute();
                }
            }
        });
    }

    @Override
    public void onPreExecute(String fragmentTag) {}

    @Override
    public void onProgressUpdate(String fragmentTag, Float percent) {}

    @Override
    public void onCancelled(String fragmentTag) {}

    @Override
    public void onPostExecute(String fragmentTag, Object result) {

        switch (fragmentTag) {

            case ASYNC_TASK_FRAGMENT_A: {

                // Handle ASYNC_TASK_FRAGMENT_A
                break;
            }
            case ASYNC_TASK_FRAGMENT_B: {

                // Handle ASYNC_TASK_FRAGMENT_B
                break;
            }
        }
    }
}

2
对于那些想要避免使用Fragment的人,您可以使用onRetainCustomNonConfigurationInstance()和一些连接来保留在方向更改时运行的AsyncTask。
(请注意,此方法是已弃用的onRetainNonConfigurationInstance()的替代方法。)
似乎这个解决方案并不经常被提及。我写了一个简单的运行示例来说明。
干杯!
public class MainActivity extends AppCompatActivity {

private TextView result;
private Button run;
private AsyncTaskHolder asyncTaskHolder;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    result = (TextView) findViewById(R.id.textView_result);
    run = (Button) findViewById(R.id.button_run);
    asyncTaskHolder = getAsyncTaskHolder();
    run.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            asyncTaskHolder.execute();
        }
    });
}

private AsyncTaskHolder getAsyncTaskHolder() {
    if (this.asyncTaskHolder != null) {
        return asyncTaskHolder;
    }
    //Not deprecated. Get the same instance back.
    Object instance = getLastCustomNonConfigurationInstance();

    if (instance == null) {
        instance = new AsyncTaskHolder();
    }
    if (!(instance instanceof ActivityDependant)) {
        Log.e("", instance.getClass().getName() + " must implement ActivityDependant");
    }
    return (AsyncTaskHolder) instance;
}

@Override
//Not deprecated. Save the object containing the running task.
public Object onRetainCustomNonConfigurationInstance() {
    return asyncTaskHolder;
}

@Override
protected void onStart() {
    super.onStart();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.attach(this);
    }
}

@Override
protected void onStop() {
    super.onStop();
    if (asyncTaskHolder != null) {
        asyncTaskHolder.detach();
    }
}

void updateUI(String value) {
    this.result.setText(value);
}

interface ActivityDependant {

    void attach(Activity activity);

    void detach();
}

class AsyncTaskHolder implements ActivityDependant {

    private Activity parentActivity;
    private boolean isRunning;
    private boolean isUpdateOnAttach;

    @Override
    public synchronized void attach(Activity activity) {
        this.parentActivity = activity;
        if (isUpdateOnAttach) {
            ((MainActivity) parentActivity).updateUI("done");
            isUpdateOnAttach = false;
        }
    }

    @Override
    public synchronized void detach() {
        this.parentActivity = null;
    }

    public synchronized void execute() {
        if (isRunning) {
            Toast.makeText(parentActivity, "Already running", Toast.LENGTH_SHORT).show();
            return;
        }
        isRunning = true;
        new AsyncTask<Void, Integer, Void>() {

            @Override
            protected Void doInBackground(Void... params) {
                for (int i = 0; i < 100; i += 10) {
                    try {
                        Thread.sleep(500);
                        publishProgress(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }

            @Override
            protected void onProgressUpdate(Integer... values) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI(String.valueOf(values[0]));
                }
            }

            @Override
            protected synchronized void onPostExecute(Void aVoid) {
                if (parentActivity != null) {
                    ((MainActivity) parentActivity).updateUI("done");
                } else {
                    isUpdateOnAttach = true;
                }
                isRunning = false;
            }
        }.execute();
    }
}

0

我已经实现了,可以解决在执行任务时活动暂停和重建的问题。

你应该实现AsmykPleaseWaitTaskAsmykBasicPleaseWaitActivity。即使你旋转屏幕并在应用程序之间切换,你的活动和后台任务也将正常工作。


-9

快速解决方案(不建议使用)

避免Activity自我销毁和重建的方法是在清单文件中声明您的Activity: android:configChanges="orientation|keyboardHidden|screenSize

  <activity
        android:name=".ui.activity.MyActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:label="@string/app_name">

正如文档所述:

屏幕方向已更改 - 用户已旋转设备。

注意:如果您的应用程序针对API级别13或更高版本(由minSdkVersion和targetSdkVersion属性声明),则还应该声明“screenSize”配置,因为当设备在纵向和横向方向之间切换时,它也会发生变化。


1
最好避免这种情况。请注意:自己处理配置更改可能会使使用替代资源变得更加困难,因为系统不会自动应用它们。当您必须避免由于配置更改而重新启动应用时,此技术应被视为最后的手段,并且不建议在大多数应用中使用。请查看https://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange - David

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接