Android AsyncTask 上下文行为

Posted

技术标签:

【中文标题】Android AsyncTask 上下文行为【英文标题】:Android AsyncTask context behavior 【发布时间】:2011-01-08 15:26:23 【问题描述】:

我一直在使用 android 中的 AsyncTasks,我正在处理一个问题。

举一个简单的例子,一个带有一个 AsyncTask 的 Activity。后台的任务并没有做任何壮观的事情,它只是休眠了 8 秒。

在 onPostExecute() 方法中的 AsyncTask 结束时,我只是将按钮可见性状态设置为 View.VISIBLE,只是为了验证我的结果。

现在,这非常有效,直到用户决定在 AsyncTask 工作时(在 8 秒睡眠窗口内)改变他的手机方向。

我了解 Android Activity 生命周期,并且我知道该 Activity 会被销毁并重新创建。

这就是问题所在。AsyncTask 指的是一个按钮,并且显然包含对首先启动 AsyncTask 的上下文的引用。

我希望,这个旧上下文(因为用户导致方向更改)要么变为 null,并且 AsyncTask 抛出 NPE 以引用它试图使其可见的按钮。

相反,没有抛出 NPE,AsyncTask 认为按钮引用不为空,将其设置为可见。结果?屏幕上什么都没有发生!

更新:我已经通过在活动中保留WeakReference 并在配置更改发生时切换来解决这个问题。这很麻烦。

代码如下:

public class Main extends Activity 

    private Button mButton = null;
    private Button mTestButton = null;

    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        mButton = (Button) findViewById(R.id.btnStart);
        mButton.setOnClickListener(new OnClickListener () 
            @Override
            public void onClick(View v) 
                new taskDoSomething().execute(0l);
            
        );
        mTestButton = (Button) findViewById(R.id.btnTest);   
    

    private class TaskDoSomething extends AsyncTask<Long, Integer, Integer> 
    
        @Override
        protected Integer doInBackground(Long... params) 
            Log.i("LOGGER", "Starting...");
            try 
                Thread.sleep(8000);
             catch (InterruptedException e) 
                e.printStackTrace();
            
            return 0;
        

        @Override
        protected void onPostExecute(Integer result) 
            Log.i("LOGGER", "...Done");
            mTestButton.setVisibility(View.VISIBLE);
        
    

尝试执行它并在 AsyncTask 工作时更改您的手机方向。

【问题讨论】:

写“保持对活动的弱引用”而不是在代码中这样做有点误导。更糟糕的是,您进一步说“并在发生配置更改时切换”,这实际上没有任何意义......切换什么? 【参考方案1】:

AsyncTask 并非设计为在 Activity 被拆除并重新启动后重复使用。正如您所说,内部 Handler 对象变得陈旧。在 Romain Guy 的 Shelves 示例中,他简单地取消了任何当前正在运行的 AsyncTask,然后在方向更改后重新启动新的。

可以将您的 Thread 移交给新的 Activity,但它会增加很多管道。没有普遍同意的方法,但你可以在这里阅读我的方法:http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html

【讨论】:

【参考方案2】:

如果您只需要一个上下文而不将其用于 ui 内容,您可以简单地将 ApplicationContext 传递给您的 AsyncTask。例如,您通常需要系统资源的上下文。

不要尝试从 AsyncTask 更新 UI,并尽量避免自己处理配置更改,因为它可能会变得混乱。为了更新 UI,您可以注册一个广播接收器并发送一个广播。

您还应该将 AsyncTask 作为与上述活动分开的公共类,它使测试变得更加容易。不幸的是,Android 编程经常会强化不良做法,而官方示例也无济于事。

【讨论】:

【参考方案3】:

这是导致我始终防止我的 Activity 在方向更改时被销毁/重新创建的类型。

为此,请将其添加到清单文件中的 &lt;Activity&gt; 标记中:

android:configChanges="orientation|keyboardHidden" 

并在您的 Activity 类中覆盖 onConfigurationChanged:

@Override
public void onConfigurationChanged(final Configuration newConfig)

    // Ignore orientation change to keep activity from restarting
    super.onConfigurationChanged(newConfig);

【讨论】:

您的方法有效,但您对这种方法有很好的反驳意见吗?为什么默认情况下操作系统会破坏并重新创建活动呢?换句话说,按照您的建议,我会“失去”什么?顺便说一句,它按预期工作。感谢您的回复。 据我所知,您将失去的主要是横向和纵向模式具有不同 Activity 布局的能力。您可能会“失去”其他一些东西,但我不知道它们是什么。 除非您知道自己在做什么,否则 Google 通常不建议使用这种方法。 我了解活动也可能因来电等原因而结束。如果是这样的话,你还是会有问题的,对吧? 我建议避免使用此解决方案。问题 #1 - 可以在配置更改时重新启动活动。除了方向变化之外,这还可以是字体大小、语言环境等的变化。问题#2 - AsyncTask 有对 Activity 的引用。如果您完成() Activity,它将保留在内存中,直到 AsyncTask 完成。如果用户启动 Activity、按 BACK、再次启动等,内存中可能存在同一个 Activity 的多个实例,并可能导致 OOM 错误。问题 #3 - 您不能将不同的资源用于纵向/横向。【参考方案4】:

为避免这种情况,您可以在此处使用答案:https://***.com/a/2124731/327011

但是,如果您需要销毁活动(纵向和横向的不同布局),您可以将 AsyncTask 设为公共类(在此处阅读为什么它不应该是私有的 Android: AsyncTask recommendations: private class or public class?),然后创建一个方法 setActivity 来设置在销毁/创建时引用当前活动。

您可以在此处查看示例:Android AsyncTask in external class

【讨论】:

以上是关于Android AsyncTask 上下文行为的主要内容,如果未能解决你的问题,请参考以下文章

Android:AsyncTask 需要一个上下文来在离开包含活动后显示警报

在上下文不工作的情况下从 AsyncTask onPostExecute 调用 Android Intent

如何在 android 中将 asynctask 代码更改为 rxandroid 代码?

Android AsyncTask

AsyncTask 和上下文

在 AsyncTask 或 Service 上下载文件?