Android更新Ui进阶精解(一)
《代码里的世界》
用文字札记描绘自己 android学习之路
转载请保留出处 by Qiao http://blog.csdn.net/qiaoidea/article/details/45115047
[Android更新Ui进阶精解(一)][4] android ui线程检查机制
[Android更新Ui进阶精解(二)][5] android 线程更新UI机制
1.回顾
[前面一篇][1]简单讲了如何快速使用handler更新ui。稍微补充一些:
- 更新ui时可以直接使用这种方法,你不须非要再new一个子线程才使用,比如:
viewPostBtn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
viewPostBtn.post(new Runnable() {
@Override
public void run() {
titleView.setText("viewPost——Result");
}
});
}
});
-
这几种方法可以同样延伸到很多类似的反不同意义的用法,比如
sendMessage()可以延伸的方法 - sendMessageAtTime(Message msg, long uptimeMillis)在指定时间uptimeMillis时发送消息msg;
- sendMessageDelayed(Message msg, long delayMillis)延迟delayMillis时间后发送消息msg
- sendEmptyMessage(int what) 发送一个指定类型what的空消息;
- sendEmptyMessageAtTime(int what, long uptimeMillis)在指定时间uptimeMillis时发送一条指定类型what的空消息;
- sendEmptyMessageDelayed(int what, long delayMillis)延迟delayMillis时间后发送一条指定类型what的空消息;
- sendMessageAtFrontOfQueue(Message msg)在消息队列头(优先)发送这条消息msg;
同样,post()可以延伸的方法
- postAtTime(Runnable r, long uptimeMillis)
- postAtTime(Runnable r, Object token, long uptimeMillis)
- postDelayed(Runnable r, long delayMillis)
- postAtFrontOfQueue(Runnable r)
请自行查阅相应方法,这里不予一一列出。
2.原理--源码分析
首先说[上篇][1]的第一个问题,android在生成页面的同时生成一个ViewRootImpl的对象,这个对象负责检查checkThread线程是否是在主ui线程,当我们尝试使用非ui线程更新视图时,checkThread则抛出异常。
1. 先看看负责检查线程的ViewRootImpl这段逻辑
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
// other code..
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
// other code..
}
好吧,为什么要这样? 引用一段比较合理解释:
“那么为什么Android要求只能在UI主线程中更改View呢?这就要说到Android的单线程模型了,因为如果支持多线程修改View的话,由此产生的线程同步和线程安全问题将是非常繁琐的,所以Android直接就定死了,View的操作必须在UI线程,从而简化了系统设计。”
2. 再看看视图创建时候是何时添加了这个检查对象的
我们从activity创建说起,首先获取一个窗口管理器WindowManager,然后设置并初始化其container。接着通过activity得到根视图DecorView(FramLayout),最后将DecorView添加到 activity的ViewManager 中去,而这个ViewManager 在addView时候就会生成一个ViewRootImpl对象。说这么多感觉表述不清楚,更容易犯糊涂了。 看代码
/**
* 以下方法是在调用activity Resume时候执行
* @ActivityClientRecord r 记录activity相关状态及参数
*/
if (r.window == null && !a.mFinished && willBeVisible) {
//获取根视图DecorView
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//获取并添加至ViewManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
}
然后更新和显示activity ,调用了r.activity.makeVisible()方法
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
//判断配置等是否需要更新view最后调用wm.updateViewLayout(decor, l); 方法,略过。。。
//最后显示activity
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible(); //显示
}
}
再看activity的makeVisible()方法
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes()); //注意这里。。
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
这个 ViewManager 的addview方法正是关键,它将添加我们提到的ViewRootImpl,其具体实现可以看WindowManagerGlobal:
//主要展示添加ViewRootImpl的过程,其他代码略
public final class WindowManagerGlobal {
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
mRoots.add(root);
}
}
}
至此,我们大略知道了ui线程是在何时对更新过程和加以控制检查,并了解了检查的内部原理。
看到这里很多小伙伴们肯定会不满了,你他瞄不是说讲解handler更新Ui的原理吗,净扯这些有屁用呢!额,由于篇幅限制,这段放在下一篇[Android更新Ui进阶精解(二)][5] 讲解。咱线继续上一话题:
那么,我们真的就不能在子线程里更新Ui了吗?显然不是,基于前面讲解的部分,既然是在onResume时候生成检查ViewRootImpl对象,所以我们其实可以在oncreate里更新Ui,比如
//onCreate调用这段
new Thread(new Runnable() {
@Override
public void run() {
titleView.setText("OtherThread");
}
}).start();
当然,你是不能这样用的
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(200); //睡了就再起不来了
} catch (InterruptedException e) {
e.printStackTrace();
}
titleView.setText("OtherThread");
}
}).start();
但是为什么我我们又能在OnResume里这么用?
@Override
protected void onResume() {
super.onResume();
new Thread(new Runnable() {
@Override
public void run() {
titleView.setText("OtherThread");
}
}).start();
}
爱哥说是因为消息队列Message Queue在接收和处理过程并非立即的,需要一个过程。(这一部分我大爱哥 [爱哥 --非UI线程更新UI][3] 其实有精讲,大家不妨看一下。)其实我觉得不妨可以大胆猜想,只要view是在渲染到视图之前,我们都是可以通过其他线程来更改的。大家有空可以研究下。
--
[1]: http://www.jianshu.com/p/4c60506c3ae1
[2]: http://www.cnblogs.com/xirihanlin/archive/2011/04/11/2012746.html
[3]: http://blog.csdn.net/aigestudio/article/details/43449123
[4]: http://www.jianshu.com/p/6de0a42a44d6
[5]: http://##