Android的AsyncTask上下文行为

33

我一直在使用Android中的AsyncTasks,遇到了一个问题。

拿一个简单的例子来说,一个Activity只有一个AsyncTask。该任务在后台并没有做什么特别的事情,只是休眠8秒钟。

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

现在,这很好用,直到用户在AsyncTask工作时(在8秒睡眠窗口内)决定更改他的手机方向。

我理解Android活动生命周期,我知道活动会被销毁并重新创建。

这就是问题所在。AsyncTask正在引用一个按钮,并且显然保留对首次启动AsyncTask的上下文的引用。

我期望,这个旧的上下文(因为用户导致了方向的改变)要么变为null并使AsyncTask引发NPE,因为它试图使按钮可见;

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

更新:我通过保持对活动的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);
        }
    }
}

在执行此操作时,当异步任务正在工作时,请尝试更改手机的方向。


在代码中写“保持对Activity的弱引用”,但实际上并没有这样做,这样写有点误导人。更糟糕的是,你还说“当配置更改发生时切换”,这意味着什么都没有...切换什么? - Ewoks
4个回答

23

AsyncTask不适用于在Activity被销毁并重新启动后重复使用。内部Handler对象会变得陈旧,就像您所述的那样。在Romain Guy的Shelves示例中,他简单地取消任何当前正在运行的AsyncTask,然后在方向改变后重新启动新的任务。

将线程交给新Activity是可能的,但需要大量编写代码。目前没有一种普遍认可的方法可以做到这一点,但您可以阅读我的方法,了解更多信息: http://foo.jasonhudgins.com/2010/03/simple-progressbar-tutorial.html


3
如果您只需要上下文而不打算将其用于用户界面方面,您可以将ApplicationContext传递给您的AsyncTask。例如,您经常需要上下文来访问系统资源。
不要尝试从AsyncTask更新UI,并尽量避免自己处理配置更改,因为这可能会变得混乱。为了更新UI,您可以注册Broadcast接收器并发送Broadcast。
您还应将AsyncTask作为独立的公共类从活动中分离出来,正如上面提到的那样,这可以使测试变得更加容易。不幸的是,Android编程经常强化糟糕的做法,官方示例也没有提供帮助。

2

这种情况促使我始终防止我的Activity在屏幕方向改变时被销毁/重新创建。

要实现这一点,请将以下内容添加到您的清单文件中的<Activity>标签中:

android:configChanges="orientation|keyboardHidden" 

在您的Activity类中覆盖onConfigurationChanged:

@Override
public void onConfigurationChanged(final Configuration newConfig)
{
    // Ignore orientation change to keep activity from restarting
    super.onConfigurationChanged(newConfig);
}

5
你的方法有效,但你能否提供对这种方法的良好反驳?为什么操作系统默认情况下会销毁并重新创建活动呢?换句话说,如果按照你的建议进行操作,我会失去什么?顺便说一下,它按预期运行了。谢谢回复。 - dnkoutso
据我所知,您会失去的主要功能是无法在横向和纵向模式下拥有不同的Activity布局。可能还有其他一些东西会被"失去",但我不知道它们是什么。 - Mark B
1
谷歌通常不建议采用这种方法,除非你知道自己在做什么。 - emmby
2
我理解一个活动也可能因为来电等原因而结束。如果是这种情况,你仍然会有问题,对吗? - gschuager
14
我建议避免使用这个解决方案。问题1-在配置更改时Activity可能会重新启动。除了方向更改外,这可能是字体大小、语言环境等的更改。问题2-AsyncTask引用了Activity。如果finish() Activity,它将一直保留在内存中直到AsyncTask完成。如果用户启动Activity,按BACK键,再次启动等,则可能存在多个相同Activity的实例,并导致OOM错误。问题3-您无法为纵向/横向方向使用不同的资源。 - fhucho

2
为了避免这种情况,您可以使用此处提供的答案:https://dev59.com/DXI95IYBdhLWcg3w2R3z#2124731 但是,如果您需要销毁活动(横向和纵向布局不同),您可以将AsyncTask设置为公共类(在此处阅读为什么不应该是私有类:Android:AsyncTask建议:私有类还是公共类?),然后创建一个setActivity方法,在销毁/创建当前活动时设置对当前活动的引用。
您可以在此处查看示例:Android AsyncTask在外部类中

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