最佳实践:方向更改期间的 AsyncTask

Posted

技术标签:

【中文标题】最佳实践:方向更改期间的 AsyncTask【英文标题】:Best practice: AsyncTask during orientation change 【发布时间】:2011-10-31 00:23:45 【问题描述】:

AsyncTask 非常适合在另一个线程中运行复杂的任务。

但是当AsyncTask 仍在运行时发生方向更改或其他配置更改时,当前的Activity 将被销毁并重新启动。当AsyncTask 的实例连接到该活动时,它会失败并导致“强制关闭”消息窗口。

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

到目前为止我看到的是:

禁用方向更改。(肯定不是您应该处理的方式。) 让任务存活并通过onRetainNonConfigurationInstance用新的活动实例更新它 Activity 被销毁时取消任务,Activity 再次创建时重新启动它。 将任务绑定到应用程序类而不是活动实例。 “货架”项目中使用的一些方法(通过 onRestoreInstanceState)

一些代码示例:

android AsyncTasks during a screen rotation, Part I 和 Part II

ShelvesActivity.java

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

【问题讨论】:

有一个重复,检查这个***.com/questions/4584015/…。 这是来自 Mark Murphy 的博客...AsyncTask 和 ScreenRotation 可能会有所帮助...link 虽然这是一篇旧帖子,但thisIMO 是一种更简单(而且更好?)的方法。 我只是想知道为什么文档中没有谈到这种非常琐碎的情况。 【参考方案1】:

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

不要使用Activity#onRetainNonConfigurationInstance()。这种模块化程度较低,不适合基于Fragment 的应用程序。

您可以read my article 描述如何使用保留的Fragments 处理配置更改。它很好地解决了在旋转变化中保留AsyncTask 的问题。您基本上需要在Fragment 中托管您的AsyncTask,在Fragment 上调用setRetainInstance(true),并通过保留的FragmentAsyncTask 的进度/结果报告回Activity

【讨论】:

好主意,但不是每个人都使用 Fragments。早在 Fragments 成为一种选择之前,就已经编写了很多遗留代码。 @ScottBiggs 片段可通过支持库一直追溯到 Android 1.6。您能否举例说明一些仍在积极使用的遗留代码,这些代码在使用支持库 Fragments 时会遇到困难?因为老实说,我认为这不是问题。 @tactoth 我觉得没有必要在我的回答中解决这些问题,因为 99.9% 的人不再使用TabActivity。老实说,我不确定我们为什么要谈论这个……每个人都同意Fragments 是要走的路。 :) 如果必须从嵌套的 Fragment 调用 AsyncTask 怎么办? @AlexLockwood - “每个人都同意片段是要走的路。”。 Squared 的开发人员会不同意!【参考方案2】:

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

这样,AsyncTasks 就不必关心处理其结果的特定 Activity 实例。当他们完成时他们只是“喊”,如果一个 Activity 大约在那个时间(活跃和专注/处于恢复状态)对任务的结果感兴趣,那么它将被处理。

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

【讨论】:

如果你能在答案中添加一个例子会更有帮助 我认为这是减少活动和片段之间耦合的解决方案 这可能是解决方案的一部分,但它似乎无法解决在方向更改后重新创建 AsyncTask 的问题。 如果你运气不好,直播期间没有任何活动怎么办? (即您处于中转状态)【参考方案3】:

这是另一个 AsyncTask 示例,它使用 Fragment 处理运行时配置更改(如当用户旋转屏幕时)和 setRetainInstance(true)。还演示了一个确定的(定期更新的)进度条。

示例部分基于官方文档Retaining an Object During a Configuration Change。

在此示例中,需要后台线程的工作仅仅是将图像从 Internet 加载到 UI 中。

Alex Lockwood 似乎是对的,在使用“保留片段”处理 AsyncTasks 的运行时配置更改时,这是最佳实践。 onRetainNonConfigurationInstance() 在 Android Studio 的 Lint 中已弃用。官方文档警告我们不要使用android:configChanges,来自Handling the Configuration Change Yourself,...

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

还有一个问题是是否应该为后台线程使用 AsyncTask。

official reference for AsyncTask 警告...

AsyncTasks 最好用于短时间的操作(最多几秒钟)。如果您需要保持线程长时间运行,强烈建议您使用 java.util.concurrent 提供的各种 API Executor、ThreadPoolExecutor、FutureTask等包。

也可以使用服务、加载程序(使用 CursorLoader 或 AsyncTaskLoader)或内容提供程序来执行异步操作。

我将帖子的其余部分分解为:

程序;和 上述过程的所有代码。

程序

    从一个基本的 AsyncTask 作为 Activity 的内部类开始(它不需要是内部类,但它可能会很方便)。在这个阶段,AsyncTask 不处理运行时配置更改。

    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");
        
    
    
    

    添加一个嵌套类 RetainedFragment,它扩展了 Fragment 类并且没有它自己的 UI。将 setRetainInstance(true) 添加到该 Fragment 的 onCreate 事件中。提供设置和获取数据的程序。

    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> 
        ....
    

    在最外层的Activity类的onCreate()中处理RetainedFragment:如果已经存在就引用它(以防Activity正在重启);如果它不存在,则创建并添加它;然后,如果它已经存在,则从 RetainedFragment 获取数据并使用该数据设置您的 UI。

    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());
            
        
    

    从 UI 启动 AsyncTask

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

    添加并编码一个确定的进度条:

    在 UI 布局中添加进度条; 在Activity oncreate()中获取对它的引用; 使其在进程开始和结束时可见和不可见; 在 onProgressUpdate 中定义要向 UI 报告的进度。 将 AsyncTask 2nd Generic 参数从 Void 更改为可以处理进度更新的类型(例如 Integer)。 在 doInBackground() 中的常规点发布进度。

上述过程的所有代码

活动布局。

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

    <RelativeLayout
        android:layout_
        android:layout_
        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_
            android:layout_
            android:background="@android:color/black" />

        <Button
            android:id="@+id/button_get_picture"
            android:layout_
            android:layout_
            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_
            android:layout_
            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_
            android:layout_
            android:layout_below="@id/button_get_picture"
            android:progress="0"
            android:indeterminateOnly="false"
            android:visibility="invisible" />

    </RelativeLayout>
</ScrollView>

Activity 带有:子类 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。您的意思是说我的回答在 *** 上占用了太多空间吗? 我认为他只是意味着你有一个很棒的答案&你应该写一个博客! =) @JohnBentley @SandyD.yesterday 感谢您的积极解释。我确实希望她或他有意这样做。 我也认为这是一个很棒的答案,我也是这样解释的。像这样的非常完整的答案很棒!【参考方案4】:

最近,我找到了一个很好的解决方案here。它基于通过 RetainConfiguration 保存任务对象。在我看来,这个解决方案非常优雅,对我来说我已经开始使用它了。你只需要从基本任务中嵌套你的异步任务就可以了。

【讨论】:

非常感谢您提供这个有趣的答案。除了相关问题中提到的解决方案之外,这是一个很好的解决方案。 不幸的是,此解决方案使用了已弃用的方法。【参考方案5】:

基于@Alex Lockwood 的回答以及@William 和@quickdraw mcgraw 对这篇文章的回答:How to handle Handler messages when activity/fragment is paused,我写了一个通用的解决方案。

这样处理轮换,如果在异步任务执行期间activity进入后台,activity将在恢复后接收回调(onPreExecute、onProgressUpdate、onPostExecute & onCancelled),因此不会抛出IllegalStateException(见@ 987654322@).

如果有相同的但具有通用参数类型,例如 AsyncTask(例如:AsyncTaskFragment),那就太好了,但我没有设法快速完成,目前没有时间。如果有人想改进,请随意!

代码:

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://***.com/questions/8040280/how-to-handle-handler-messages-when-activity-fragment-is-paused
 */
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;
            
        
    

【讨论】:

【参考方案6】:

您可以为此使用加载程序。在此处查看Doc

【讨论】:

从 Android API 28 开始不推荐使用加载器(链接会告诉您)。 Loaders 没有被弃用,只是你如何称呼它们发生了变化【参考方案7】:

对于那些想要躲避 Fragments 的人,您可以使用 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();
    

【讨论】:

【参考方案8】:

我已经实现了library,它可以在您的任务执行时解决活动暂停和娱乐的问题。

您应该实现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">

正如docs中提到的那样

屏幕方向发生了变化——用户旋转了设备。

注意:如果您的应用程序针对 API 级别 13 或更高级别(如声明的 通过 minSdkVersion 和 targetSdkVersion 属性),那么你应该 还要声明“screenSize”配置,因为它也会改变 当设备在纵向和横向之间切换时。

【讨论】:

最好避免这种情况。 developer.android.com/guide/topics/resources/… "注意:自己处理配置更改会使使用替代资源变得更加困难,因为系统不会自动为您应用它们。当您必须避免因配置而重新启动时,应将此技术视为最后的手段更改,不建议用于大多数应用程序。”

以上是关于最佳实践:方向更改期间的 AsyncTask的主要内容,如果未能解决你的问题,请参考以下文章

异步编程最佳实践

Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案

Android 屏幕旋转 处理 AsyncTask 和 ProgressDialog 的最佳方案

在测试期间在 Web 应用程序的页面上定位组件的最佳实践是啥?

基于WPS的Word最佳实践系列(利用表格控制排版)

以正确方向以编程方式显示 UIView 的最佳实践