1,原生SystemUI更新状态栏图标
原生SystemUI中,PhoneStatusBarPolicy中通过StatusBarIconList提供的setIcon、removeIcon等方法在主线程直接操作UI,简单直接。
但这里存在一个问题,如果有一些Icon更新的通知来自于子线程,就需要把消息传递到主线程完成UI操作,否则会报错。SystemUI对这套传递机制做了一个封装,我们往下看。
2,其他应用更新状态栏图标
在应用中,我们不能直接访问StatusBarIconList的实例,需要通过StatusBarManager,以Binder的形式向系统服务请求修改状态栏Icon。CommandQueue作为IStatusBar的实现类,向IStatusBarService服务注册了回调,这样服务接收到请求,会把请求发送到CommandQueue。
CommandQueue本身既实现了IStatusBar回调接口,也作为回调的发起者。StatusBarIconList的实现类就实现了CommandQueue.Callbacks接口,并将自己向CommandQueue注册。这样CommandQueue接收到回调后,又向StatusBarIconControllerImpl发送请求,从而实现了其他应用调用StatusBarIconList提供的setIcon、removeIcon等方法操作状态栏的目的。
3,线程什么时候完成了切换?
为什么PhoneStatusBarPolicy更新状态栏Icon需要在主线程,而从StatusBarManager过来的运行在Binder线程的请求可以直接操作状态栏Icon?
细心的人一定看到了,在上图,有一个大大的Handler!没错,CommandQueue接到异步请求后,通过Handler,将消息发往了主线程:
//CommandQueue.java
private Handler mHandler = new H(Looper.getMainLooper());
public void setIcon(String slot, StatusBarIcon icon) {
synchronized (mLock) {
// don't coalesce these
mHandler.obtainMessage(MSG_ICON, OP_SET_ICON, 0,
new Pair<String, StatusBarIcon>(slot, icon)).sendToTarget();
}
}
CommandQueue的setIcon函数中并没有更新UI,而是将消息通过Handler发往了主线程的MessageQueue。自己投的篮自己抢篮板,发消息的mHandler接收到消息后,就可以通知回调真正处理UI了,因为这时来自Binder线程的请求已经洗白,在主线程中执行了。
//CommandQueue.java
private final class H extends Handler {
private H(Looper l) {
super(l);
}
public void handleMessage(Message msg) {
final int what = msg.what & MSG_MASK;
switch (what) {
case MSG_ICON: {
switch (msg.arg1) {
case OP_SET_ICON: {
Pair<String, StatusBarIcon> p = (Pair<String, StatusBarIcon>) msg.obj;
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).setIcon(p.first, p.second);
}
break;
}
case OP_REMOVE_ICON:
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).removeIcon((String) msg.obj);
}
break;
}
break;
}
最后总结下其他应用操作状态栏Icon的流程:
4,android中异步更新UI有几种方式?
一般我们会说有4种方式:
1,Handler发送Message
2,AsycTask
3,Activity.runOnUiThread
4,Handler.post(Runnable)
看起来选择很丰富嘛!但实际上,这四种方式本质上都是第一种方式!让我们逐一分析。
4.1,Handler发送消息
这种方法不多说了,原理请阅读Handler源码。前面SystemUI的例子用的就是Handler发送消息实现子线程通知主线程更新UI。
4.2,AsycTask
AsycTask本质是对Handler的封装。使用方法如下。
创建继承自AsycTask的类,并重写以下方法:
//耗时方法,运行在子线程
@WorkerThread
protected abstract Result doInBackground(Params... params);
//在doInBackground之前调用,在UI线程内执行
@MainThread
protected void onPreExecute() {
}
//在执行中,且在调用publishProgress方法时,在UI线程内执行,用于更新进度
@MainThread
protected void onProgressUpdate(Progress... values) {
}
//在doInBackground之后调用,在UI线程内执行
@MainThread
protected void onPostExecute(Result result) {
}
需要关注的,就是子线程转向主线程是如何做到的,可以看到就是通过Handler:
//子线程中通过Handler向主线程发送进度信息,从而在主线程执行onProgressUpdate函数
@WorkerThread
protected final void publishProgress(Progress... values) {
if (!isCancelled()) {
getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
new AsyncTaskResult<Progress>(this, values)).sendToTarget();
}
}
其他关于AsycTask的细节就不展开了。
4.3,Activity.runOnUiThread
这个就简单了,直接上代码:
@Override
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action); //又是Handler
} else {
action.run();
}
}
4.4,Handler.post(Runnable)
其实就是把Runnable封装成了Message,本质上还是通过Handler发Message:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
5,深度思考
1,Android为什么不让子线程更新UI
因为android并没有对UI更新做并发处理(没有加锁),强行并发会导致UI显示混乱。
2,Android怎么控制不让子线程更新UI
Activity进行UI操作时,会是用ViewRootImpl做了线程检测:
//ViewRootImpl.java
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
ViewRootImpl会用保存的主线程(mThread )与当前执行线程作比较,如果当前不是主线程,则抛异常。
3,有没有办法在子线程更新UI?
有。Activity中的ViewRootImpl对象是在onResume方法回调之后才创建的,在这之前的onCreate方法,甚至是onResume方法里都可以实现子线程更新UI,因为此时还没有创建ViewRootImpl对象, 可以跳过线程检测的流程。
网友评论