从自定义视图启动AsyncTask的最佳实践

5

在IT技术中,我们经常需要启动一个耗时的计算线程,并且需要在稍后更新ActivityFragment以显示计算结果。

我一直遵循以下指南,这对我来说一直很有效:

AsyncTask需要在UI Fragment中执行onPostExecute

  1. 使用无UI的片段并使用setRetainInstance(true)
  2. 使用setTargetFragmentgetTargetFragment技术。
  3. 请参考https://dev59.com/92oy5IYBdhLWcg3wo_gn#12303649

AsyncTask需要在UI Activity中执行onPostExecute

  1. 使用无UI的片段并使用setRetainInstance(true)
  2. 使用onAttachonDetach来存储对Activity的引用。Google似乎不鼓励使用getActivityhttp://developer.android.com/guide/components/fragments.html
  3. 请参考https://dev59.com/92oy5IYBdhLWcg3wo_gn#16305029

然而,如果是从View派生的类呢?我计划从自定义View中启动AsyncTask。但是,我如何将onPostExecute返回到View中呢?

我问这个问题的原因是,在我的自定义视图中,某些触摸事件会触发它使用新的位图重新绘制自己。生成新位图需要耗费时间。因此,我计划启动一个AsyncTask来生成这样的位图,并将其传回自定义视图。但是,配置更改可能会导致自定义视图被重新创建。因此,在onPostExecute期间,我需要确保我的AsyncTask可以正确地引用视图。


你是在问如何在配置更改时保留一个位于View内部的AsyncTask吗? - Alex Lockwood
2
不行,不行,不行……派生类不应该知道像线程这样的东西甚至存在……它们也不应该能够自己获取数据,它们应该被“喂养”……因此最佳实践是:根本不要这样做 - Selvin
1
@Selvin 我不会说视图永远不应该知道线程。Romain Guy甚至在他的Google IO演讲中举了一个需要清理其线程的视图的例子(请参见5:59的此链接)。话虽如此,你说得对,在许多情况下,将线程放在活动/片段中可能是更好的编程设计。这只取决于用例。 - Alex Lockwood
2
@AlexLockwood 有点像。在我的自定义视图中,某些触摸事件会触发它使用新位图重新绘制自身。生成新位图需要时间。因此,我希望了解正确的方法,以适当地从自定义视图管理线程。一直以来,我都是从Activity和Fragment中管理线程。但是,我从未在自定义视图上这样做过。 - Cheok Yan Cheng
视图对配置更改一无所知......这是活动和片段应该担心的事情。如果视图线程确实必须保留,您必须将该逻辑移至活动/片段中(或在服务中生成位图等)。 - Alex Lockwood
在你的修改之后,我想说:看一下SurfaceView源代码...它与你的示例类似(OpenGL有自己的线程)。 - Selvin
2个回答

5
假设您仅在绘制相关操作中使用 AsyncTask(否则,您应该重新审视自己的逻辑 - 如评论所示),您可以直接在自定义的 View 类中创建 AsyncTask
class MyView extends View {

    private MyAsyncTask currentTask = null;

    // View details

    @Override
    public void onAttachedToWindow() {
        currentTask = new MyAsyncTask(this);
        currentTask.execute();
    }

    @Override
    public void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (currentTask != null) {
            currentTask.cancel(true);
            currentTask = null;
        }
    }

    static class MyAsyncTask extends AsyncTask<Void, Void, Bitmap> {

        private WeakReference<MyView> viewRef;

        MyAsyncTask(MyView view) {
            viewRef = new WeakReference<>(view);
        }

        // background task implementation

        @Override
        public void onPostExecute(Bitmap bitmap) {
            MyView view = viewRef.get();
            if (view == null) {
                return;
            }

            // you now can safely update your view - we're on UI thread
        }

    }

}

这就是一个安全的实现方式。它有一些缺点和重要部分:
  • 在任何时候,你的AsyncTask都不应该持有对View的强引用(这就是为什么该类被声明为static并持有对ViewWeakReference)。
  • 当你不再关心来自AsyncTask的结果时,请取消它。
  • 此实现将会丢弃已取消的AsyncTask可能有用的结果。如果这是问题所在,我建议完全从View中删除AsyncTask并寻找其他解决方案(单独的ExecutorHandlerThread)。

此外,AsyncTaskonPostExecute将从启动它的相同的looper线程中调用(在您的情况下,这是主线程,因此无论您从ActivityView还是其他地方开始它,都取决于如何管理这些任务)。


0

我会给你一个概括性的想法,你可以在任何需要的地方应用它(包括从ThreadThreadExecutor中使用,而不仅仅依赖于AsyncTask);

你可以直接使用一个库来处理events,其中事件被分发到一个公共的“总线”中,任何想要注册到总线并监听这些事件的类都可以:

对此,我将向您引用Otto,它运行良好且非常强大:http://square.github.io/otto/

或者,您可以像这样使用LocalBroadcastManagerhttps://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html自己实现它:

LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context)<那是为了获取它的引用。

onEventComplete:(发生在您的线程或AsyncTask上

Intent i = new Intent("DOWNLOAD_COMPLETE");
// ps. feel free to attach extras to the intent, in order to pass data back to your activity/fragment/view
lbm.sendBroadcast(i); 

然后在你的活动/片段/视图上创建一个接收器

BroadcastReceiver receiver = new BroadcastReceiver(){
    @Override
    public void onReceive (Context context, Intent intent){
       // feel free to read the extras from the intent with data here and update your view
    }
};

onStartListening:

lbm.registerReceiver(receiver, new IntentFilter("DOWNLOAD_COMPLETE"));

onStopListening:

lbm.unregisterReceiver(receiver);

如果您涉及到编程,那么您必须onStart/onStoponResume/onPauseonAttachedToWindow/onDetachedFromWindow(对于视图)期间开始和停止监听。


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