异步任务、片段、视图和返回栈

8

我已经使用这种模式有一段时间了。这里是一个非常牵强的例子,展示了AsyncTask和进度指示器的结合:

new AsyncTask<Void, Void, Void>() {
    WeakReference<MyFragment> weakFragment = new WeakReference<MyFragment>(MyFragment.this);

    @Override
    protected void onPreExecute() {
        Fragment fragment = weakFragment.get();
        if(fragment != null) fragment.getView().findViewById(R.id.progress).setVisibility(View.VISIBLE);        
    }

    @Override
    protected Document doInBackground(Void... params) {
        Fragment fragment = weakFragment.get();
        if(fragment == null) return null;

        // Do something ...
    }

    @Override
    protected void onPostExecute() {                
        Fragment fragment = weakFragment.get();
        if(fragment != null) fragment.getView().findViewById(R.id.progress).setVisibility(View.GONE);
    }
}.execute();

方向变化时它的效果很好,但我注意到当我将片段从后台堆栈弹出时,片段非空但fragment.getView()为null。这显然会导致崩溃。你们在使用什么方法?我似乎找不到完美的解决方案。重要说明,这是在一个片段中,在其onActivityCreated(...)中调用setRetainInstance(true);


使用消息总线或本地广播接收器。 - 323go
进度条?:| 这有点过分了,你不觉得吗? - JVillella
1个回答

4
AsyncTask是一种异步任务,这意味着通常你不知道它何时完成以及何时调用onPostExecute()。我认为你的问题就在这里。当你旋转设备时,你的asynctask还没有完成,而你的片段视图被销毁并快速重新创建,同时asynctask仍在工作线程上运行,当它完成工作时,你的片段已经有了一个视图,getView的返回值不是null。但如果旋转时间过长,你将得到null,因为AsyncTask已经完成,但你的片段没有任何视图。现在当你把你的片段推到backstack上时,这种情况正好发生了(看图片),但片段本身没有被销毁(片段从backstack返回到布局)。现在你的AsyncTask完成并调用你的fragment getView方法,它没有任何view,所以你会得到NPE
我的解决方案:
首先,你应该使用WeakReference<ProgressBar>而不是WeakReference<MyFragment>,并在onPostExecute()中检查它是否为null,如果为null,则不做任何操作,因为默认值是不可见的,如果它不为null,则调用setVisibility(View.GONE)
更新:

片段仍然存在,但视图已被释放。这可能吗?

是的,只需看一下链接中的figure2即可。当片段处于活动状态(绿色框)时,将片段添加到back stack片段进入onPauseonStoponDestroyView。然后,当片段从back stack弹出时,它直接进入onCreateView,箭头连接的文本是:the fragment returns to the layout from backstack
为了确认我的答案,你可以创建一个AsyncTask,并在doInbackground中调用SystemClock.sleep(30000)。现在,你可以将你的片段推到backstack上,并从backstack中弹出它,没有异常,因为asynctask还没有完成,当它调用onPostExecute()时,你的片段已经有了一个视图。
另一个好处是你可以看到setRetainInstance
当你将你的fragment推入后退栈时,它可能会完全销毁,并且当它弹出时,你可能会得到新的fragment引用。但是,当你在配置更改中使用“setRetainInstance(true);”时,你的fragment在Activity重新创建时得以保留,这意味着它仍然是相同的fragment,并且不会调用“onCreate”。这是非常重要的,因为如果你在“onCreate”方法中启动你的“AsyncTask”,当你将你的fragment推入后退栈并弹出它时,你可能会得到一个新的fragment,这意味着“onCreate”方法会运行,另一个“AsyncTask”会被触发。这意味着你无法控制调用“AsyncTask”的次数,所以请注意内存泄漏!

好的,我明白你的意思。这就是为什么我一开始使用弱引用来引用片段的原因。我认为我遇到的问题是片段仍然存在,但视图已被释放。这种情况可能发生吗?也许更多的解释会有所帮助。 - JVillella
感谢 @mmlooloo 的帮助。这解决了我的问题,但是我仍然不确定在异步任务中处理片段和fragment.getView()的最佳实践。 - JVillella
我不明白你的意思,但是你的问题并不是很复杂。在某些情况下,你可能有一个片段,而该片段没有任何视图,因此调用getView将导致NPE。 - mmlooloo
如果我需要访问片段的视图,现在我需要检查片段是否存活(因为我应该持有一个弱引用),检查它的getView()是否存活,以及可能所有它的数据成员也要检查,因为它们可能为空。这正确吗?对于一些相当小的东西来说,这似乎过于繁琐了。 - JVillella
是的,但我想告诉你的是:有些情况下,你的片段仍然存活,但没有任何视图。因此,简单地调用getView会导致NPE。为了解决这个问题,将你的小部件存储在一个弱引用中,而不是片段中。另外,你不需要任何sharedpreferences,如果你注意到了,我已经更新了我的答案解决部分。 - mmlooloo

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