Android Handler機制詳解原理
Looper是整個跨線程通信的管理者
// 內(nèi)部持有的變量如下:
ThreadLocal<Looper>
MainLooper
Observer
MessageQueue
Thread
1.首先先回憶一下Handler怎么用
Android線程通信分為以下兩種情況
- 1.子線程發(fā)消息給UI線程
- 2.UI線程發(fā)消息給子線程
- 3.子線程發(fā)消息給另個子線程
1.子線程發(fā)消息給UI線程
class FragmentContentActivity : AppCompatActivity() {
val FLAG = 1
lateinit var handler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
when (msg.what) {
FLAG -> {
findViewById<TextView>(R.id.text).text = msg.data["Text"].toString()
}
}
}
}
thread {
Thread.sleep(2000L)
handler.sendMessage(Message.obtain().apply {
what = FLAG
data = Bundle().apply {
this.putString("Text", "ThreadMessage")
}
})
}
}
}
2.UI線程/子線程發(fā)消息給子線程
class FragmentContentActivity : AppCompatActivity() {
val THREAD_FLAG =2
lateinit var threadHandler: Handler
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
thread {
Looper.prepare()
threadHandler = object :Handler(Looper.myLooper()!!){
override fun handleMessage(msg: Message) {
when(msg.what){
THREAD_FLAG -> {
Toast.makeText(
this@FragmentContentActivity,
"${msg.data["Text"]}",
Toast.LENGTH_SHORT
).show()
}
}
}
}
Looper.loop()
}
}
override fun onResume() {
super.onResume()
findViewById<TextView>(R.id.text).postDelayed({
threadHandler.sendMessage(Message.obtain().apply {
what = THREAD_FLAG
data = Bundle().apply {
putString("Text","UI Message")
}
})
},2000L)
}
}
**在子線程的使用中,我們發(fā)現(xiàn)必須要進行Looper.prepare()和Looper.loop()前后這兩個操作,因此,帶著這個疑問來看一下Looper的邏輯
**
// 在調(diào)用prepare()之后一定要調(diào)用loop(),最后結(jié)束消息循環(huán)的時候調(diào)用quit()
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));
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
prepare()就是將初始化一個Looper對象放入到ThreadLocal中,初始化Looper,同時mQueue
public static void loop(){
Binder.clearCallingIdentity()
for (;;) {
Message msg = queue.next(); // might block
long origWorkSource = ThreadLocalWorkSource.setUid(msg.workSourceUid);
try {
// 其實 loop()只做了這一個調(diào)用,其他的都是監(jiān)控當前消息循環(huán)時間是否超時,應(yīng)該和ANR有關(guān)
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
if (logSlowDelivery) {
if (slowDeliveryDetected) {
if ((dispatchStart - msg.when) <= 10) {
Slog.w(TAG, "Drained");
slowDeliveryDetected = false;
}
} else {
if (showSlowLog(slowDeliveryThresholdMs, msg.when, dispatchStart, "delivery",
msg)) {
// Once we write a slow delivery log, suppress until the queue drains.
slowDeliveryDetected = true;
}
}
}
if (logSlowDispatch) {
showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
}
//消息實體回收
msg.recycleUnchecked();
可以看到Looper.loop其實只是在for循環(huán)中,獲取mQueue的下一個msg節(jié)點,然后調(diào)用msg.target.dispatchMessage(msg)。乍看只是msg對象內(nèi)部的操作。
因為loop()其實邏輯上算死循環(huán),這意味著,當前線程的自發(fā)的順序執(zhí)行命令到此結(jié)束了,只能通過其他線程觸發(fā)handler機制,來被動的在當前線程執(zhí)行命令,當前線程完全變成了一個響應(yīng)線程
Looper類只是初始化并開啟線程死循環(huán)的一個開關(guān),具體工作在MessageQueue中進行
MessageQueue 消息隊列
隊列內(nèi)消息的添加不是直接調(diào)用MessageQueue,而是由與Looper相關(guān)聯(lián)的Handler調(diào)用
MessageQueue的內(nèi)部持有的變量如下: ArrayList mMessages SparseArray IdleHandler[] mBlocked
MessageQueue類的功能主要有:元素插入隊列,獲取隊列的頭部元素,查找隊列中元素,綜述就是對隊列的增刪改查,其中 mMessage就是這個隊列的入口也是這個隊列的頭結(jié)點
boolean enqueueMessage(Message msg,long when) //msg 元素插入隊列 boolean hasMessages(Handler h,int what,Object object) //查找handler下的msg.what/object相同的Msg boolean hasEqualMessages(Handler h,int what,Object obj)//查找 msg.object.equal(obj)的msg removeMessages(Handler h,int what,Object obj)/(Handler h,Runnable r,Object obj) removeEqualMessages(...) //刪除與參數(shù)msg.object相同或equal的msg Message next() //獲取隊列中的頭部元素
可以看出,這些方法內(nèi)部都調(diào)用了 synchronized(this),隊列的操作都是線程同步的
Message next() ->
...
// linux機制下的總線進入輪詢,線程相當于掛起狀態(tài),nextPollTimeOut是掛起多長時間
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//先判斷msg.target是否為null,表示當前消息是不是異步消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
//同步屏障:取出當前隊列中的異步消息
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//重新計算線程進入掛起狀態(tài)的時間
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
...
可以看出next()內(nèi)部主要有兩種獲取msg的邏輯
1.當前消息都是普通消息,按照msg.when的大小排序,每一次循環(huán)執(zhí)行,通過檢測when是否大于now來決定是否獲取msg,或是掛起當前線程。
2.當前消息中有異步消息,優(yōu)先獲取msg.isAsynchronous()==true的,或者按照此異步消息的等待時間,來重新設(shè)置掛起線程的時間,從而達到精準的獲取異步消息。
通俗的來講就是說,當前所有普通消息按照預(yù)約的執(zhí)行時間的先后來排隊,這樣可基本上既可以達到按照預(yù)約時間執(zhí)行消息,也可以最大效率的在一定時間段內(nèi)執(zhí)行最多的消息,但是這忽略了每個消息的執(zhí)行消耗的時間,比如A消息是隊列內(nèi)的No.1,A消息預(yù)約執(zhí)行時間是1s之后,整個隊列是等待狀態(tài)的,這個時候來了B消息,B消息預(yù)約的時間是0.999s之后,按照預(yù)約時間的排隊機制,B消息要插隊到A消息之前,B成了這個隊列的No.1,A成了No.2,整個隊列的等待時間還是1s(因為之前設(shè)置了等待時間,所以不用喚醒),但是B消息的執(zhí)行過程長達0.5s,已經(jīng)超過了之后的很多消息的預(yù)約執(zhí)行時間點了,這樣就不能保證某些重要的消息按時執(zhí)行。
于是就有了異步消息同步屏障的機制,這相當于普通消息排隊時來了一個VIP消息,先按照預(yù)約時間找到自己的位置,然后大喝一聲:“都把腳給我挪開,我的前面不允許有人”,這個時候排在他之前的普通消息就只能全部挪到隊列的一邊,然后隊列重新按照這位VIP消息,設(shè)置等待時間,期間新來的普通消息也插到隊邊等待,保證精準按時執(zhí)行VIP消息。等VIP消息執(zhí)行完,之后再把之前等待普通消息的隊列合并執(zhí)行。當然之前等待的消息全耽誤了,但畢竟是普通消息不重要。
// 同步屏障的方法,此方法只在 ViewRootImpl類中調(diào)用
private int postSyncBarrier(long when) {
// Enqueue a new sync barrier token.
// We don't need to wake the queue because the purpose of a barrier is to stall it.
synchronized (this) {
final int token = mNextBarrierToken++;
final Message msg = Message.obtain();
msg.markInUse();
msg.when = when;
msg.arg1 = token;
//沒有設(shè)置target
Message prev = null;
Message p = mMessages;
if (when != 0) {
while (p != null && p.when <= when) {
prev = p;
p = p.next;
}
}
if (prev != null) { // invariant: p == prev.next
msg.next = p;
prev.next = msg;
} else {
msg.next = p;
//mMessages變?yōu)橥狡琳舷?,next()下一次循環(huán),首先獲取到的是同步屏障
mMessages = msg;
}
return token;
}
// ViewRootImpl
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
//設(shè)置同步屏障之后,通過設(shè)置了Aysnc標記位的Handler發(fā)送的Msg都是異步消息,
//MessageQueue也優(yōu)先處理此類異步消息,直到移除同步屏障標記位,再恢復到普通消息隊列。
由此可見,同步屏障的設(shè)置和View刷新機制有關(guān),因為要保證Vsync信號按時完成刷新操作,具體分析待續(xù)…
綜述,異步消息可以保證精準的執(zhí)行,但也因此消息事件的先后順序被打亂,有可能在代碼執(zhí)行中執(zhí)行了Handler.sendMsg(1,time0.2)->AsyncHandler.sendMsg(2,time0.5),但是實際執(zhí)行的是 2->1。
再看Handler
Handler的成員變量如下
mLooper :初始化時獲取當前線程的Looper對象引用 mQueue :通過Looper.mQueue 獲取到的MessageQueue隊列引用mAsynchronous :標記當前Handler是否發(fā)送異步消息 mCallback : Handler自身的callback接口,此callback調(diào)用在Message.callback之前mMessenger :IMessager 和進程通信相關(guān)
以上成員變量大都是final類型,表示Handler也是在其使用上也是final類型,也就是說,沒有辦法通過將Handler與context的生命周期相剝離來避免內(nèi)存泄漏
Handler的方法如下
//Handler 發(fā)送Message第一種方法,設(shè)置Message的what,data
//不設(shè)置 runnable:Callback
boolean sendMessage(Message msg) -> boolean sendMessageDelayed(Message msg,long delayTime)
-> boolean sendMessageAtTime(Message msg,SystemClock.uptimeMillis()+delayTime)
-> mQueue.enqueueMessage(msg,uptime)
//第二種方法,Message只設(shè)置runnable:Callback
boolean postAtTime(Runnable r,Object token,long uptime)
-> sendMessageAtTime(getPostMessage(r,token),uptime)
--> Message getPostMessage(Runnable r,Object token){
Message.obtain().callback=r
...
}
//移除Message和檢驗Message
removeMessages()
hasMessages()
...
//Message 回調(diào)執(zhí)行
void dispatchMessage(Message msg){
if(msg.callback!=null){
handleCallback(msg) ->
}else{
if(mCallback!=null){
mCallback.handleMessage(msg)
}
handleMessage(msg)
}
//可以看到 Message的回調(diào)分為三個等級
//No.1 msg自身的callback
//No.2 Handler自身的mCallback成員變量,mCallback是final類型
//No.3 Handler的子類重載的handleMessage方法
Message
Message 實現(xiàn)了Parcelable接口,也就是說可以作為進程間通信的載體
Message成員變量如下
int what //Handler發(fā)送主體下的Message的消息碼
int arg1 //低成本的參數(shù)傳遞
int arg2
Object obj //可以為空的token對象,一般在進程通信中用到
Bundle data //線程通信中常用的參數(shù)容器
Handler target //發(fā)送主體
Runnable callback //Message自身回調(diào)
Messenger replyTo //進程通信,一般在AMS中用到
------
// Message緩存池相關(guān)
Object sPoolSync = new Object() // 同步標記
Messsage next
static Message sPool
static int sPoolSize
Message方法如下
//可以看出這是一個非常巧妙的方法
static Message obtain(){
synchronized(sPoolsSync){
if(sPools!=null){
Message m= sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
//主體上是一個帶緩存池鏈表的同步工廠模式,同時也考慮到較多線程阻塞時
//可以直接聲明初始化對象
//回收Message對象到緩存池鏈表
void recycleUnchecked(){
...參數(shù)=null
synchronized(sPoolSync){
if(sPoolSize < MAX_SIZE){
next = sPool;
sPools = this;
sPoolSize++;
}
}
}
到此這篇關(guān)于Android Handler機制詳解原理的文章就介紹到這了,更多相關(guān)Android Handler內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android?TextView的maxEms和maxLength屬性區(qū)別
這篇文章主要為大家介紹了Android?TextView的maxEms和maxLength屬性區(qū)別,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-03-03
詳解Android Material Design自定義動畫的編寫
這篇文章主要介紹了詳解Android Material Design自定義動畫的編寫,其中對Activity的過渡動畫進行了重點講解,需要的朋友可以參考下2016-04-04
Android 開發(fā)音頻組件(Vitamio FAQ)詳細介紹
本文主要介紹Android開發(fā)音頻播放器,Vitamio是Android播放器組件,支持幾乎所有視頻格式和網(wǎng)絡(luò)視頻流,希望能幫助開發(fā)Android 音頻播放的小伙伴2016-07-07
Android使用MediaCodec將攝像頭采集的視頻編碼為h264
這篇文章主要為大家詳細介紹了Android使用MediaCodec將攝像頭采集的視頻編碼為h264,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-10-10
Android位圖(圖片)加載引入的內(nèi)存溢出問題詳細解析
Android在加載大背景圖或者大量圖片時,常常致使內(nèi)存溢出,下面這篇文章主要給大家介紹了關(guān)于Android位圖(圖片)加載引入的內(nèi)存溢出問題的相關(guān)資料,需要的朋友可以參考下2022-12-12
協(xié)程作用域概念迭代RxTask?實現(xiàn)自主控制
這篇文章主要為大家介紹了協(xié)程作用域概念迭代RxTask實現(xiàn)自主控制詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
Android圖片選擇器ImageEditContainer
這篇文章主要為大家詳細介紹了Android圖片選擇器ImageEditContainer的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-07-07
實例講解Android多線程應(yīng)用開發(fā)中Handler的使用
這篇文章主要介紹了Android多線程應(yīng)用開發(fā)中Handler的使用,Handle主要被用來更新UI和處理消息,需要的朋友可以參考下2016-01-01

