在非UI线程上实例化视图

4
我知道UI元素(视图层次结构)只能从UI线程中操作。对于后台操作,可以使用AsyncTask,它提供了事件处理程序以访问UI线程。
简而言之,是否允许在非UI线程中实例化与“getApplicationContext()”相关联的View?此自定义View后代 - 一旦实例化 - 将添加到从UI线程调用的视图层次结构中。因此,仅在“Asynctask.doInBackground()”中完成构造函数调用; 它的附加(“addView(...)”)操作仍在UI线程中完成,因为它是添加到Activity的根布局层次结构中。
详细情况如下:
public MyView extends View {
     public MyView(Context context) { 
            ...
     }
...
}
  1. 我创建了一个自定义视图,重写了onDraw(...)等方法。

  2. 当用户在我的主活动中点击某个特定菜单项时,会创建并显示另一个名为MyOtherActivity的活动,“其屏幕正是MyView”。

  3. 由于MyOtherActivity的屏幕必须立即显示,因此我在用户在主活动的其他位置(例如,他还没有点击该菜单项)时使用AsyncTask预先实例化了MyView。 MyView引用存储在静态数据成员中。

  4. 当调用MyOtherActivity.onCreate()时,它的构造函数代码从静态变量中取出MyView,并通过addView(...)将其添加到其布局根层次结构中。

  5. (我知道静态变量可能会引入内存泄漏,所以一旦不再需要,就将其设置为null。)

MyView在不同的线程中被实例化(并在其构造函数中获取getApplicationContext()的返回值),这是否是一个问题(并可能导致意外问题)?


1
参考android-developers讨论:https://groups.google.com/group/android-developers/browse_thread/thread/7be473d2a332d0af - CommonsWare
谢谢,马克。对于所有读者:简要来说,不要像我一样做。始终在UI线程中实例化所有视图。在2天的限制结束后,我会回答自己的问题。现在我只是编辑它。 - Thomas Calc
@ThomasCalc 我不认为我们需要等两天才能得到答案。我认为这是一个好问题,值得得到一个真正的答案(所以我刚刚提供了一个)。 - Darshan Rivka Whittle
@DarshanComputing 当我新注册时,有一个两天的限制,但也许现在仍然适用。所以你添加了一个是个好主意,谢谢。我刚刚将其设置为已接受。 - Thomas Calc
2个回答

5

关于“事件处理和线程”的说明在 View 文档中有明确的答案:

注意:整个视图树都是单线程的。在调用任何视图的方法时,您必须始终在 UI 线程上操作。 如果您正在其他线程上进行工作,并希望从该线程更新视图的状态,则应使用 Handler。

因此,不仅是显然影响 UI 外观的事情,例如 addView(),而是“任何 View 上的 任何 方法”。

在 @CommonsWare 链接的android-developers 讨论中,多位 Android 框架团队的高级工程师都确认了这一点应被认真对待。


你引用的文档可能会对初学者有些困惑。我们知道,“任何方法”也包括构造函数,因为构造函数实际上也是一种方法。可能会让初学者感到困惑的是,文档暗示该规则仅适用于现有视图上的方法调用(“在任何视图上”),并未说明它们的构造过程(实例化)的情况。 - Thomas Calc
我并不是说会普遍混淆。只是如果他们问:“嗯,我能在这里实例化一个视图吗?让我们查一下文档”,那么他只会看到方法调用。而学校教的是什么呢?你只能在实例化对象之后调用(实例)方法。因此初学者可能会得出错误的结论,即文档仅适用于现有视图。 - Thomas Calc
1
@ThomasCalc,正是因为这种微妙之处,我认为这是一个非常好的问题。我也曾经想过:“我知道你不应该在UI线程外部对UI进行操作,但是也许你可以在另一个线程上实例化一个View,只要你在UI线程上将其添加到UI中。”我很高兴你提出了这个问题,也很高兴Dianne Hackborn和Romain Guy确认了答案。我认为“任何视图上的任何方法”都是清晰明了且无歧义的,即使没有他们的确认。无论如何,谢谢——现在我比“UI事情发生在UI线程上”的理解更好了。 - Darshan Rivka Whittle
在我看来,这是不含糊的,但对于初学者来说,有时候更多的信息可能会很方便(比如“任何视图上的任何方法,包括它们的构造函数”)。是的,“UI 事情发生在 UI 线程”非常普遍,有时候边界并不那么明显,例如考虑官方的 AsyncDrawable(它根本不是异步创建的,只是其底层位图)。 - Thomas Calc
讨论中总有一个很好的解释,更清楚地说明为什么你“不应该”做某事。希望 Android 文档能够像这个一样详细:“即使从一个线程实例化视图并在另一个线程中将其附加到视图层次结构中也是不安全的,因为许多视图将在内部创建与它们在创建时绑定的任何线程相关的 Handler 对象,导致您的视图层次结构的某些部分在错误的线程上运行,产生糟糕的结果。” 这个简单的一行可能值得整个糟糕的 Android 文档,只说“不应该”。 - Farid

0

这是一个示例,演示如何使用AsyncTask将视图添加到FrameLayout中

public void addFLview(View view) {
    MyAsyncTask as = new MyAsyncTask();
    as.execute(view);
}

异步任务类

private class MyAsyncTask extends AsyncTask<View, Void, View> {
    @Override
    protected View doInBackground(View... params) {
        return params[0];
    }
    @Override
    protected void onPostExecute(View view) {
        super.onPostExecute(view);
        myFrameLayout.addView(view);
    }
}

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