想實現Android隊列功能?Handler內功心法(1),萬分膜拜

mb612e29786577c 2021-09-18 04:34:13 阅读数:559

android 功能 handler 心法 膜拜
 }
sThreadLocal.set(new Looper(quitAllowed));

}


可以看到在Looper.prepareMainLooper()方法中創建了當前線程的Looper,同時將Looper實例存放到線程局部變量sThreadLocal(ThreadLocal)中,也就是每個線程有自己的Looper。在創建Looper的時候也創建了該線程的消息隊列,可以看到prepareMainLooper會判斷sMainLooper是否有值,如果調用多次,就會拋出异常,所以也就是說主線程的Looper和MessageQueue只會有一個。同理子線程中調用Looper.prepare()時,會調用prepare(true)方法,如果多次調用,也會拋出每個線程只能由一個Looper的异常,總結起來就是每個線程中只有一個Looper和MessageQueue。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

 //Looper.java

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();

}


再來看看主線程sMainThreadHandler = thread.getHandler(),getHandler獲取到的實際上就是mH這個Handler。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

 //ActivityThread.java

final H mH = new H();

@UnsupportedAppUsage

final Handler getHandler() {
return mH;

}


mH這個Handler是ActivityThread的內部類,通過查看handMessage方法,可以看到這個Handler處理四大組件,Application等的一些消息,比如創建Service,綁定Service的一些消息。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

 //ActivityThread.java

class H extends Handler {

···
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case EXIT_APPLICATION:
if (mInitialApplication != null) {
mInitialApplication.onTerminate();
}
Looper.myLooper().quit();
break;
case RECEIVER:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
handleReceiver((ReceiverData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case CREATE_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
handleCreateService((CreateServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case BIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
handleBindService((BindServiceData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case UNBIND_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
handleUnbindService((BindServiceData)msg.obj);
schedulePurgeIdler();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case SERVICE_ARGS:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
handleServiceArgs((ServiceArgsData)msg.obj);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
case STOP_SERVICE:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
handleStopService((IBinder)msg.obj);
schedulePurgeIdler();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
···
case APPLICATION_INFO_CHANGED:
mUpdatingSystemConfig = true;
try {
handleApplicationInfoChanged((ApplicationInfo) msg.obj);
} finally {
mUpdatingSystemConfig = false;
}
break;
case RUN_ISOLATED_ENTRY_POINT:
handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
(String[]) ((SomeArgs) msg.obj).arg2);
break;
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
// Client transactions inside system process are recycled on the client side
// instead of ClientLifecycleManager to avoid being cleared before this
// message is handled.
transaction.recycle();
}
// TODO(lifecycler): Recycle locally scheduled transactions.
break;
case RELAUNCH_ACTIVITY:
handleRelaunchActivityLocally((IBinder) msg.obj);
break;
case PURGE_RESOURCES:
schedulePurgeIdler();
break;
}
Object obj = msg.obj;
if (obj instanceof SomeArgs) {
((SomeArgs) obj).recycle();
}
if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
}

}


最後我們查看Looper.loop()方法:

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

 //Looper.java

public static void loop() {

 //獲取ThreadLocal中的Looper
final Looper me = myLooper();
···
final MessageQueue queue = me.mQueue;
···
for (;;) { //死循環
//獲取消息
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
···
msg.target.dispatchMessage(msg);
···
//回收複用
msg.recycleUnchecked();
}

}


在loop方法中是一個死循環,在這裏從消息隊列中不斷的獲取消息queue.next(),然後通過Handler(msg.target)進行消息的分發,其實並沒有什麼具體的綁定,因為Handler在每個線程中對應只有一個Looper和消息隊列MessageQueue,自然要靠它來處理,也就是是調用Looper.loop()方法。在Looper.loop()的死循環中不斷的取消息,最後回收複用。
這裏要强調一下Message中的參數target(Handler),正是這個變量,每個Message才能找到對應的Handler進行消息分發,讓多個Handler同時工作。
再來看看子線程中是如何處理的,首先在子線程中創建一個Handler並發送Runnable。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
new Thread(new Runnable() {
@Override
public void run() {
new Handler().post(new Runnable() {
@Override
public void run() {
Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
}
});
}
}).start();
}

運行後可以看到錯誤日志,可以看到提示我們需要在子線程中調用Looper.prepare()方法,實際上就是要創建一個Looper和你的Handler進行“關聯”。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

--------- beginning of crash

2020-11-09 15:51:03.938 21122-21181/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: Thread-2

Process: com.jackie.testdialog, PID: 21122
java.lang.RuntimeException: Can't create handler inside thread Thread[Thread-2,5,main] that has not called Looper.prepare()
at android.os.Handler.<init>(Handler.java:207)
at android.os.Handler.<init>(Handler.java:119)
at com.jackie.testdialog.HandlerActivity$1.run(HandlerActivity.java:31)
at java.lang.Thread.run(Thread.java:919)

添加Looper.prepare()創建Looper,同時調用Looper.loop()方法開始處理消息。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

@Override

protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_three);
new Thread(new Runnable() {
@Override
public void run() {
//創建Looper,MessageQueue
Looper.prepare();
new Handler().post(new Runnable() {
@Override
public void run() {
Toast.makeText(HandlerActivity.this,"toast",Toast.LENGTH_LONG).show();
}
});
//開始處理消息
Looper.loop();
}
}).start();
}

這裏需要注意在所有事情處理完成後應該調用quit方法來終止消息循環,否則這個子線程就會一直處於循環等待的狀態,因此不需要的時候終止Looper,調用Looper.myLooper().quit()。
看完上面的代碼可能你會有一個疑問,在子線程中更新UI(進行Toast)不會有問題嗎,我們Android不是不允許在子線程更新UI嗎,實際上並不是這樣的,在ViewRootImpl中的checkThread方法會校驗mThread != Thread.currentThread(),mThread的初始化是在ViewRootImpl的的構造器中,也就是說一個創建ViewRootImpl線程必須和調用checkThread所在的線程一致,UI的更新並非只能在主線程才能進行。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

void checkThread() {

if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}

}


這裏需要引入一些概念,Window是Android中的窗口,每個Activity和Dialog,Toast分別對應一個具體的Window,Window是一個抽象的概念,每一個Window都對應著一個View和一個ViewRootImpl,Window和View通過ViewRootImpl來建立聯系,因此,它是以View的形式存在的。我們來看一下Toast中的ViewRootImpl的創建過程,調用toast的show方法最終會調用到其handleShow方法。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

 //Toast.java

public void handleShow(IBinder windowToken) {

 ···
if (mView != mNextView) {
// Since the notification manager service cancels the token right
// after it notifies us to cancel the toast there is an inherent
// race and we may attempt to add a window after the token has been
// invalidated. Let us hedge against that.
try {
mWM.addView(mView, mParams); //進行ViewRootImpl的創建
trySendAccessibilityEvent();
} catch (WindowManager.BadTokenException e) {
/* ignore */
}
}

}


這個mWM(WindowManager)的最終實現者是WindowManagerGlobal,其的addView方法中會創建ViewRootImpl,然後進行root.setView(view, wparams, panelParentView),通過ViewRootImpl來更新界面並完成Window的添加過程。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

 //WindowManagerGlobal.java

root = new ViewRootImpl(view.getContext(), display); //創建ViewRootImpl

view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//ViewRootImpl
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}

}


setView內部會通過requestLayout來完成异步刷新請求,同時也會調用checkThread方法來檢驗線程的合法性。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

@Override

public void requestLayout() {

if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}

}


因此,我們的ViewRootImpl的創建是在子線程,所以mThread的值也是子線程,同時我們的更新也是在子線程,所以不會產生异常,同時也可以參考這篇文章分析,寫的非常詳細。同理下面的代碼也可以驗證這個情况。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

//子線程中調用

public void showDialog(){

 new Thread(new Runnable() {
@Override
public void run() {
//創建Looper,MessageQueue
Looper.prepare();
new Handler().post(new Runnable() {
@Override
public void run() {
builder = new AlertDialog.Builder(HandlerActivity.this);
builder.setTitle("jackie");
alertDialog = builder.create();
alertDialog.show();
alertDialog.hide();
}
});
//開始處理消息
Looper.loop();
}
}).start();
}

在子線程中調用showDialog方法,先調用alertDialog.show()方法,再調用alertDialog.hide()方法,hide方法只是將Dialog隱藏,並沒有做其他任何操作(沒有移除Window),然後再在主線程調用alertDialog.show();便會拋出Only the original thread that created a view hierarchy can touch its views异常了。

  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.

2020-11-09 18:35:39.874 24819-24819/com.jackie.testdialog E/AndroidRuntime: FATAL EXCEPTION: main

Process: com.jackie.testdialog, PID: 24819
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:8191)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1420)
at android.view.View.requestLayout(View.java:24454)
at android.view.View.setFlags(View.java:15187)
at android.view.View.setVisibility(View.java:10836)
at android.app.Dialog.show(Dialog.java:307)
at com.jackie.testdialog.HandlerActivity$2.onClick(HandlerActivity.java:41)
at android.view.View.performClick(View.java:7125)
at android.view.View.performClickInternal(View.java:7102)

### 總結
最後為了幫助大家深刻理解Android相關知識點的原理以及面試相關知識,這裏放上相關的我搜集整理的24套**騰訊、字節跳動、阿裏、百度2019-2021面試真題解析**,我把技術點整理成了**視頻和PDF**(實際上比預期多花了不少精力),包**知識脈絡 + 諸多細節**。
還有?**高級架構技術進階腦圖、Android開發面試專題資料**?幫助大家學習提昇進階,也節省大家在網上搜索資料的時間來學習,也可以分享給身邊好友一起學習。
![想實現Android隊列功能?Handler內功心法(1),萬分膜拜_移動開發](https://s2.51cto.com/images/20210918/1631910246862812.jpg)
![想實現Android隊列功能?Handler內功心法(1),萬分膜拜_移動開發_02](https://s6.51cto.com/images/20210918/1631910246352863.jpg)
![想實現Android隊列功能?Handler內功心法(1),萬分膜拜_程序員_03](https://s7.51cto.com/images/20210918/1631910246607714.jpg)
**[CodeChina開源項目:《Android學習筆記總結+移動架構視頻+大廠面試真題+項目實戰源碼》](https://ali1024.coding.net/public/P7/Android/git)**
網上學習 Android的資料一大堆,但如果學到的知識不成體系,遇到問題時只是淺嘗輒止,不再深入研究,那麼很難做到真正的技術提昇。希望這份系統化的技術體系對大家有一個方向參考。
> 2021年雖然路途坎坷,都在說Android要沒落,但是,不要慌,做自己的計劃,學自己的習,競爭無處不在,每個行業都是如此。相信自己,沒有做不到的,只有想不到的。祝大家2021年萬事大吉。
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
版权声明:本文为[mb612e29786577c]所创,转载请带上原文链接,感谢。 https://gsmany.com/2021/09/20210918043412481b.html