日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

什么是对象的消息_这一次,我们用最详细的方式解析Android消息机制的源码

發布時間:2024/9/15 Android 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 什么是对象的消息_这一次,我们用最详细的方式解析Android消息机制的源码 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

Handler源碼解析

一、創建Handler對象

使用handler最簡單的方式:直接new一個Handler的對象

Handler handler = new Handler();

所以我們來看看它的構造函數的源碼:

public Handler() { this(null, false); } 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; }

這段代碼做了幾件事:

1、校驗是否可能內存泄漏

2、初始化一個Looper mLooper

3、初始化一個MessageQueue mQueue

我們一件事一件事的看:

1、校驗是否存在內存泄漏

Handler的構造函數中首先判斷了FIND_POTENTIAL_LEAKS的值,為true時,會獲取該對象的運行時類,如果是匿名類,成員類,局部類的時候判斷修飾符是否為static,不是則提示可能會造成內存泄漏。

問:為什么匿名類,成員類,局部類的修飾符不是static的時候可能會導致內存泄漏呢?

答:因為,匿名類,成員類,局部類都是內部類,內部類持有外部類的引用,如果Activity銷毀了,而Hanlder的任務還沒有完成,那么Handler就會持有activity的引用,導致activity無法回收,則導致內存泄漏;靜態內部類是外部類的一個靜態成員,它不持有內部類的引用,故不會造成內存泄漏

這里我們可以思考為什么非靜態類持有外部類的引用?為什么靜態類不持有外部類的引用?

問:使用Handler如何避免內存泄漏呢?

答:使用靜態內部類的方式

2、初始化初始化一個Looper mLooper

這里獲得一個mLooper,如果為空則跑出異常:

"Can't create handler inside thread that has not called Looper.prepare() "

如果沒有調用Looper.prepare()則不能再線程里創建handler!我們都知道,如果我們在UI線程創建handler,是不需要調用這個方法的,但是如果在其他線程創建handler的時候,則需要調用這個方法。那這個方法到底做了什么呢?我們去看看代碼:

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)); }

先取sThreadLocal.get()的值,結果判斷不為空,則跑出異?!耙粋€線程里只能創建一個Looper”,所以sThreadLocal里存的是Looper;如果結果為空,則創建一個Looper。那我們再看看,myLooper()這個方法的代碼:

public static @Nullable Looper myLooper() { return sThreadLocal.get(); }

總上我們得出一個結論:當我們在UI線程創建Handler的時候,sThreadLocal里已經存了一個Looper對象,所以有個疑問:

當我們在UI線程中創建Handler的時候sThreadLocal里的Looper從哪里來的?

我們知道,我們獲取主線程的Looper需要調用getMainLooper()方法,代碼如下:

public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } }

所以我們跟蹤一下這個變量的賦值,發現在方法prepareMainLooper()中有賦值,我們去看看代碼:

public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
  • 第一步調用了prepare(false),這個方法我們剛才已經看了,是創建一個Looper對象,然后存到sThreadLocal中;
  • 然后判斷sMainLooper是否為空,空則拋出異常
  • sMainLooper不為空,則sMainLooper = myLooper()
  • 至此sMainLooper對象賦值成功,所以,我們需要知道prepareMainLooper()這個方法在哪調用的,跟一下代碼,就發現在ActivityThread的main方法中調用了Looper.prepareMainLooper();?,F在真相大白:
  • 當我們在UI線程中創建Handler的時候sThreadLocal里的Looper是在ActivityThread的main函數中調用了prepareMainLooper()方法時初始化的
  • ActivityThread是一個在一個應用進程中負責管理Android主線程的執行,包括活動,廣播,和其他操作的類

3、初始化一個MessageQueue mQueue

從代碼里我們看出這里直接調用了:mLooper.mQueue來獲取這個對象,那這個對象可能在Looper初始化的時候就產生了。我們去看看Looper的初始化代碼:

private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }

代碼很簡單,就是創建了MessageQueue的對象,并獲得了當前的線程。

至此,Handler的創建已經完成了,本質上就是獲得一個Looper對象和一個MessageQueue對象!

二、使用Handler發送消息

Handler的發送消息的方式有很多,我們跟蹤一個方法sendMessage方法一直下去,發現最后竟然調用了enqueueMessage(queue, msg, uptimeMillis),那我們看看這個方法的代碼:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }

這段代碼做了三件事:

1、給msg.target賦值,也就是Handler對象

2、給消息設置是否是異步消息。

3、調用MessageQueue 的enqueueMessage(msg, uptimeMillis)方法

我們只關注第三步:這一步把Handler的發送消息轉給了MessageQueue的添加消息的方法。

所以至此,Handler發送消息的任務也已經完成了,本質上就是調用MessageQueue自己的添加消息的方法!

三、MessageQueue添加消息

MessageQueue的構造函數代碼如下:

MessageQueue(boolean quitAllowed) { mQuitAllowed = quitAllowed; mPtr = nativeInit(); }

也沒做什么特別的事情。我們去看看enqueueMessage(msg, uptimeMillis)方法代碼:

boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { throw new IllegalArgumentException("Message must have a target."); } if (msg.isInUse()) { throw new IllegalStateException(msg + " This message is already in use."); } synchronized (this) { if (mQuitting) { IllegalStateException e = new IllegalStateException( msg.target + " sending message to a Handler on a dead thread"); Log.w(TAG, e.getMessage(), e); msg.recycle(); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else { // Inserted within the middle of the queue. Usually we don't have to wake // up the event queue unless there is a barrier at the head of the queue // and the message is the earliest asynchronous message in the queue. needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr != 0 because mQuitting is false. if (needWake) { nativeWake(mPtr); } } return true; }

代碼很長,但是通過觀察這段代碼我們發現這個MessageQueue實際上是個鏈表,添加消息的過程實際上是一個單鏈表的插入過程。

所以我們知道了Handler發送消息的本質其實是把消息添加到MessageQueue中,而MessageQueue其實是一個單鏈表,添加消息的本質是單鏈表的插入

四、從消息隊列里取出消息

我們已經知道消息如何存儲的了,我們還需要知道消息是如何取出的。

所以我們要看一下Looper.loop();這個方法:

public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } 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; } try { msg.target.dispatchMessage(msg); } } }

代碼太長我刪了部分代碼。可以看出這個方法主要的功能是很簡單的。

  • 獲取Looper對象,如果為空,拋異常。
  • 獲取消息隊列MessageQueue queue
  • 遍歷循環從消息隊列里取出消息,當消息為空時,循環結束,消息不為空時,分發出去!
  • 但是實際上當沒有消息的時候queue.next()方法會被阻塞,并標記mBlocked為true,并不會立刻返回null。而這個方法阻塞的原因是nativePollOnce(ptr, nextPollTimeoutMillis);方法阻塞。阻塞就是為了等待有消息的到來。那如果在有消息加入隊列,loop()方法是如何繼續取消息呢?
  • 這得看消息加入隊列的時候有什么操作,我們去看剛才的enqueueMessage(msg, uptimeMillis)方法,發現
if (needWake) { nativeWake(mPtr);}

當needWake的時候會調用一個本地方法喚醒讀取消息。

所以這里看一下消息分發出去之后做了什么?

msg.target.dispatchMessage(msg);

上面講過這個target其實就是個handler。所以我們取handler里面看一下這個方法代碼

public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }

代碼非常簡單,當callback不為空的時候調用callback的handleMessage(msg)方法,當callback為空的時候調用自己的handleMessage(msg)。一般情況下我們不會傳入callback,而是直接復寫Handler的handleMessage(msg)方法來處理我們的消息。

寫在最后

想學習更多Android知識,或者獲取相關資料請關注我,并私信回復【資料】。 有面試資源系統整理分享,Java語言進階和Kotlin語言與Android相關技術內核,APP開發框架知識, 360°Android App全方位性能優化。Android前沿技術,高級UI、Gradle、RxJava、小程序、Hybrid、 移動架構師專題項目實戰環節、React Native、等技術教程!架構師課程、NDK模塊開發、 Flutter等全方面的 Android高級實踐技術講解。還有在線答疑

總結

以上是生活随笔為你收集整理的什么是对象的消息_这一次,我们用最详细的方式解析Android消息机制的源码的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。