1.handler 介紹
handler 的正常工作需要Looper 否則會報錯(下面會從源碼知悉報錯的原因)
Handler發(fā)送消息的過程是向消息隊列MessageQueue 插入了一條消息,MessageQueue 是由Looper 負責(zé)維護的MessageQueue 的next 方法返回這條消息給當前線程的Looper,Looper 收到消息后開始處理龙誊,最終消息由Looper交個Handler處理,即Handler的dispatchMessage()方法被調(diào)用,最后回調(diào)到handler的 handleMessage中進行處理
在android中汰聋,非UI線程中是不能更新UI的门粪,如果在子線程中做UI相關(guān)操作,可能會出現(xiàn)程序崩潰烹困。一般的做法是玄妈,創(chuàng)建一個Message對象,Handler發(fā)送該message髓梅,給handler傳遞主線程的Looper,將線程切回主線程就可以在Handler的handleMessage()方法中做ui相關(guān)操作
handler 主要有兩個功能:
1.刷新UI拟蜻,(需要用主線程的looper)
2.不刷新ui,只是處理消息
1.1 使用handler刷新UI
主線程中創(chuàng)建Handler,接受各線程發(fā)送來的消息進行UI操作我們使用的比較多枯饿,不做過多說明
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
btn.setText("更新");
//ToastUtils.showShort("---handler()----");
LogUtils.e("----handler-----Thread.currentThread="+Thread.currentThread().getName());
break;
default:
break;
}
}
};
new Thread(new Runnable() {
@Override public void run() {
Message message = handler1.obtainMessage();
message.arg1 = 1;
handler1.sendMessage(message);
}
}).start();
在子線程中酝锅,創(chuàng)建Handler 進行UI更新,就特別需要注意,在創(chuàng)建handler 時要傳Looper.getMainLooper(),將線程切回到主線程進行
new Thread(new Runnable() {
@Override
public void run() {
Handler handler = new Handler(Looper.getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
btn.setText("更新")
//ToastUtils.showShort("---handler()----");
LogUtils.e("----handler-----Thread.currentThread="+Thread.currentThread().getName());
break;
default:
break;
}
}
};
Message message = new Message();
message.what = 0;
handler.sendMessage(message);
}
}).start();
1.2 子線程進行消息處理
子線程中只進行消息處理時奢方,不更新UI 時
(1) 初始化handler可以同上傳Looper.getMainLooper()切到主線程
(2) 也可以不傳Looper.getMainLooper()搔扁,但是必須在子線程中新建一個Looper 對象
new Thread(new Runnable() {
@Override
public void run() {
LogUtils.e("1111-----Thread.currentThread="+Thread.currentThread().getName());
Looper.prepare();
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
LogUtils.e("22--Thread.currentThread="+Thread.currentThread().getName());
break;
}
}
};
Message message = new Message();
message.what = 0;
handler.sendMessage(message);
Looper.loop();
}
}).start();
如果子線程中創(chuàng)建handler 不調(diào)用Looper.prepare(),會提示運行時異常:
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
查看handler源碼可知
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
sThreadLocal.get()是空的爸舒,而 Looper.prepare()就完成了這個設(shè)置操作
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
主線程為什么不需要調(diào)用Looper.prepare()? 因為在ActivityThread中 會默認創(chuàng)建Looper對象
1.3 子線程真的不能更新UI嗎
我們試著在activity,onCreate()方法中啟動這個線程
new Thread(new Runnable() {
@Override
public void run() {
LogUtils.e("1111-----Thread.currentThread="+Thread.currentThread().getName());
Looper.prepare();
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what){
case 0:
btnLogin.setText("WTF");
//ToastUtils.showShort("---handler()----");
LogUtils.e("22--Thread.currentThread="+Thread.currentThread().getName());
break;
}
}
};
Message message = new Message();
message.what = 0;
handler.sendMessage(message);
Looper.loop();
}
}).start();
運行后發(fā)現(xiàn)也沒有出現(xiàn)崩潰現(xiàn)象,難道我們就可以下結(jié)論子線程也可以更新UI 了么
其實不然
我們試著在更新UI前讓子線程休眠200ms ,發(fā)現(xiàn)出現(xiàn)崩潰了稿蹲,或者我們把這段代碼放在Button 的點擊事件中去處理扭勉,也會發(fā)生崩潰!崩潰日志是這樣的:
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a
view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:7583)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1298)
那我們就要從這段崩潰日志中找原因苛聘,跟蹤源碼涂炎,我們發(fā)現(xiàn)ViewRootImpl類中會有一個checkThread()方法來判斷當前訪問UI的線程是不是主線程,因為mThread這個主線程是在app程序啟動時就初始化的
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
其中 mThread 是創(chuàng)建 ViewRootImpl 的線程设哗,而ViewRootImpl是在主線程中創(chuàng)建的唱捣,所以,我們習(xí)慣地稱它為主線程熬拒,mThread和當前代碼運行的線程來做了個等式運算爷光,相同就出錯,也就是說澎粟,并不是子線程不能刷新UI蛀序,準確來說,是發(fā)送進行 UI 刷新消息的消息活烙,因為真正的底層刷新也不是當前 APP 的主線程徐裸。而是限制了,如果當ViewRootImpl是由子線程創(chuàng)造的啸盏,那么就可以在該子線程中發(fā)送更新UI的消息重贺,自然地就能更新了,那么為什么限制呢?
這背后是線程同步的開銷問題回懦。顯然兩個線程同時繪制屏幕气笙,屏幕會顯示一些意想不到的圖像,所以兩個線程不能同時去更新Ui怯晕。需要互斥潜圃,比如鎖。結(jié)果就是同一時刻只有一個線程可以做ui舟茶。那么當兩個線程互斥幾率較大時谭期,或者互斥的代碼很復(fù)雜,選擇其中一個長期持有其他發(fā)消息就是典型的解決方案吧凉。所以普遍的要求ui只能單線程操作
在ActivityThread中隧出,我們找到handleResumeActivity方法,跟進這個方法我們會發(fā)現(xiàn) activity就是在這里面調(diào)用onResume()生命周期的阀捅,這方法之后胀瞪,有一個makeVisible()方法;
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
跟進去,我們發(fā)現(xiàn)饲鄙,這里是activity 讓view顯示的地方
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
往WindowManager中添加DecorView赏廓,那現(xiàn)在應(yīng)該關(guān)注的就是WindowManager的addView方法了涵紊。而WindowManager是一個接口來的,
我們應(yīng)該找到WindowManager的實現(xiàn)類才行幔摸,而WindowManager的實現(xiàn)類是WindowManagerImpl摸柄,找到了WindowManagerImpl的addView方法,
如下:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
里面調(diào)用了WindowManagerGlobal的addView方法既忆,那現(xiàn)在就鎖定WindowManagerGlobal的addView方法
我們發(fā)現(xiàn)
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams); mViews.add(view);
mRoots.add(root); mParams.add(wparams);
...
}
ViewRootImpl是在WindowManagerGlobal的addView方法中創(chuàng)建的驱负。
所以我們可以總結(jié)一下,我們在onCreate()或者onResume()中使用子線程更新UI時患雇,由于viewRootImpl 還未創(chuàng)建完成跃脊,所以并沒有報錯,我們延時200ms 后苛吱,ViewRootImpl已經(jīng)創(chuàng)建了酪术,可以執(zhí)行checkThread方法檢查當前線程。
所以翠储,系統(tǒng)考慮資源調(diào)度及線程開銷上绘雁,通過viewRootImpl線程判斷的方式來讓UI線程(長期持有發(fā)送消息進行UI更新操作的線程)來進行UI更新操作。