老師講的真棒!如何在Android-Studio下進行NDK開發,滿滿乾貨指導
開頭
相信大多數網際網路的從業者都有著這樣一個夢想:進大廠,獲得豐厚的薪酬,和更優秀的人一起共事,在技術上獲得更快的成長。
**然而部分人其實一直都陷入了“窮忙”的困局,覺得自己每天白天黑夜都在工作,高強度輸出,但是卻並沒有獲得機會的眷顧。**久而久之,既不知道自己忙什麼,也不知道怎麼能停下來。
這並不是時間的過錯,而是因為把解決方式過多押注在技術上,然後繼續在工作上不斷迴圈,這樣的狀態讓你極度缺少另一個層面的思考。
如何去打破這種僵局呢?很多人建議多讀書,但是從哪種型別的書開始看又該看誰的書呢?說實話,很多技術書寫到最後大同小異。但是萬變不離其宗,原始碼以及參考手冊需要多些鑽研,紮根底層是程式設計師應有的素養。
現在網際網路訊息如此便捷,學習資料從來不缺。硬碟裡都是各種學習資源,上下班坐地鐵,還要刷技術視訊。但是泛看不如精看、精讀。
這裡我總結了一些Android核心知識點,以及一些最新的大廠面試題、知識腦圖和視訊資料解析。
需要的**小夥伴私信【學習】**我免費分享給你,以後的路也希望我們能一起走下去。
重要概念
1、主執行緒(UI執行緒、MainThread)
當應用程式第一次啟動時,會同時自動開啟1條主執行緒,用於處理UI相關的事件(如更新、操作等)
2、子執行緒(工作執行緒)
人為手動開啟的執行緒,執行耗時操作(如網路請求、資料載入等)
3、訊息(Message)
執行緒間通訊的資料單元(即Handler接受 & 處理的訊息物件),用於儲存需要操作的通訊資訊
4、訊息佇列(Message Queue)
一種資料結構(先進先出),儲存Handler傳送過來的訊息(Message)
5、處理者(Handler)
Handler為主執行緒與子執行緒的通訊媒介,是執行緒訊息的主要處理者。用於新增訊息(Message)到訊息佇列(Message Queue),處理迴圈器(Looper)分派過來的訊息(Message)
6、迴圈器(Looper)
訊息佇列(Message Queue)與處理者(Handler)的通訊媒介,用於訊息迴圈,即
(1)訊息獲取:迴圈取出訊息佇列(Message Queue)的訊息(Message)
(2)訊息分發:將取出的訊息(Message)傳送給對應的處理者(Handler)
每個執行緒只能擁有1個Looper,1個Looper可繫結多個執行緒的Handler,即多個執行緒可往1個Looper所持有的MessageQueue中傳送訊息,提供執行緒間通訊的可能
(三)使用方式
3.1)Handler.sendMessage()
方式1:新建Handler子類(內部類)
// 步驟1:自定義Handler子類(繼承Handler類) & 複寫handleMessage()方法
class mHandler extends Handler {
// 通過複寫handlerMessage() 從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需執行的UI操作
}
}
// 步驟2:在主執行緒中建立Handler例項
private Handler mhandler = new mHandler();
// 步驟3:建立所需的訊息物件
Message msg = Message.obtain(); // 例項化訊息物件
msg.what = 1; // 訊息標識
msg.obj = "AA"; // 訊息內容存放
// 步驟4:在工作執行緒中 通過Handler傳送訊息到訊息佇列中
// 可通過sendMessage() / post()
// 多執行緒可採用AsyncTask、繼承Thread類、實現Runnable
mHandler.sendMessage(msg);
// 步驟5:開啟工作執行緒(同時啟動了Handler)
// 多執行緒可採用AsyncTask、繼承Thread類、實現Runnable
方式2:匿名內部類
// 步驟1:在主執行緒中 通過匿名內部類 建立Handler類物件
private Handler mhandler = new Handler(){
// 通過複寫handlerMessage()從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需執行的UI操作
}
};
// 步驟2:建立訊息物件
Message msg = Message.obtain(); // 例項化訊息物件
msg.what = 1; // 訊息標識
msg.obj = "AA"; // 訊息內容存放
// 步驟3:在工作執行緒中 通過Handler傳送訊息到訊息佇列中
// 多執行緒可採用AsyncTask、繼承Thread類、實現Runnable
mHandler.sendMessage(msg);
// 步驟4:開啟工作執行緒(同時啟動了Handler)
// 多執行緒可採用AsyncTask、繼承Thread類、實現Runnable
3.2)Handler.post()
// 步驟1:在主執行緒中建立Handler例項
private Handler mhandler = new mHandler();
// 步驟2:在工作執行緒中 傳送訊息到訊息佇列中 & 指定操作UI內容
// 需傳入1個Runnable物件
mHandler.post(new Runnable() {
@Override
public void run() {
... // 需執行的UI操作
}
});
// 步驟3:開啟工作執行緒(同時啟動了Handler)
// 多執行緒可採用AsyncTask、繼承Thread類、實現Runnable
(四)工作原理
4.1)工作流程解析
步驟一:非同步通訊準備
在主執行緒中建立
(1)迴圈器 物件(Looper)
(2)訊息佇列 物件(Message Queue)
(3)Handler物件
Looper、Message Queue均屬於主執行緒,建立Message Queue後,Looper自動進入訊息迴圈。此時,Handler自動繫結了主執行緒的Looper、Message Queue
步驟二:訊息入隊
工作執行緒通過Handler傳送訊息(Message)到訊息佇列(Message Queue)中,該訊息內容=工作執行緒對UI的操作
步驟三:訊息迴圈
訊息出隊:Looper迴圈取出訊息佇列(Message Queue)中的訊息(Message)
訊息分發:Looper將去除的訊息(Message)傳送給建立該訊息的處理者(Handler)
在訊息迴圈過程中,若訊息佇列為空,則執行緒阻塞。
步驟四:訊息處理
處理者Handler接受迴圈器Looper傳送過來的訊息(Message)
處理者Handler根據訊息(Message)進行UI操作
4.2)工作流程圖
4.3)示意圖
4.4)執行緒Thread、迴圈器Looper、處理者Handler對應關係
(1)1個執行緒(Thread)只能繫結1個迴圈器(Looper),但可以有多個處理者
(2)1個迴圈器(Looper)可繫結多個處理者(Handler)
(3)1個處理者(Handler)只能繫結1個迴圈器(Looper)
(五)原始碼分析
5.1)核心類
Handler機制包括3個重要類:1、處理者 Handler2、迴圈器 Looper3、訊息佇列 MessageQueue
1、類圖
2、核心方法
5.2)原始碼分析
記錄一次Handler使用步驟
方式1:使用Handler.sendMessage()
準備步驟1:建立迴圈器物件Looper&訊息佇列物件MessageQueue
Looper.prepareMainLooper()
為主執行緒(UI執行緒)建立1個迴圈器物件(Looper),同時也會自動建立1個對應的訊息佇列物件(MessageQueue)
該方法在主執行緒(UI執行緒)建立時自動呼叫,不需手動生成。在Android應用程式啟動時,會預設建立1個主執行緒(ActiviyThread,也叫UI執行緒),建立時,會自動呼叫ActivityThread的1個靜態main方法=應用程式的入口main()內則會呼叫Looper.prepareMainLooper()為主執行緒生成1個Looper物件
public static void main(String[] args) {
... // 僅貼出關鍵程式碼
Looper.prepareMainLooper();
// 1\. 為主執行緒建立1個Looper物件,同時生成1個訊息佇列物件(MessageQueue)
// 方法邏輯類似Looper.prepare()
// 注:prepare():為子執行緒中建立1個Looper物件
ActivityThread thread = new ActivityThread();
// 2\. 建立主執行緒
Looper.loop();
// 3\. 自動開啟 訊息迴圈 ->>下面將詳細分析
}
Looper.prepare()
為當前執行緒(子執行緒)建立1個迴圈器物件(Looper),同時也會自動建立1個對應的訊息佇列物件(MessageQueue)
需要在子執行緒中手動呼叫改方法
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// 1\. 判斷sThreadLocal是否為null,否則丟擲異常
//即 Looper.prepare()方法不能被呼叫兩次 = 1個執行緒中只能對應1個Looper例項
// 注:sThreadLocal = 1個ThreadLocal物件,用於儲存執行緒的變數
sThreadLocal.set(new Looper(true));
// 2\. 若為初次Looper.prepare(),則建立Looper物件 & 存放在ThreadLocal變數中
// 注:Looper物件是存放在Thread執行緒裡的
// 原始碼分析Looper的構造方法->>分析a
}
/**
* 分析a:Looper的構造方法
**/
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
// 1\. 建立1個訊息佇列物件(MessageQueue)
// 即 當建立1個Looper例項時,會自動建立一個與之配對的訊息佇列物件(MessageQueue)
mRun = true;
mThread = Thread.currentThread();
}
建立主執行緒時,會自動呼叫ActivityThread的1個靜態的main();而main()內則會呼叫Looper.prepareMainLooper()為主執行緒生成1個Looper物件,同時也會生成其對應的MessageQueue物件,即 主執行緒的Looper物件自動生成,不需手動生成;
而子執行緒的Looper物件則需手動通過Looper.prepare()建立,在子執行緒若不手動建立Looper物件 則無法生成Handler物件;
根據Handler的作用(在主執行緒更新UI),故Handler例項的建立場景 主要在主執行緒
生成Looper & MessageQueue物件後,則會自動進入訊息迴圈:Looper.loop()
準備步驟2:訊息迴圈
/**
* 原始碼分析: Looper.loop()
* 作用:訊息迴圈,即從訊息佇列中獲取訊息、分發訊息到Handler
* 特別注意:
* a. 主執行緒的訊息迴圈不允許退出,即無限迴圈
* b. 子執行緒的訊息迴圈允許退出:呼叫訊息佇列MessageQueue的quit()
*/
public static void loop() {
...// 僅貼出關鍵程式碼
// 1\. 獲取當前Looper的訊息佇列
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
// myLooper()作用:返回sThreadLocal儲存的Looper例項;若me為null 則丟擲異常
// 即loop()執行前必須執行prepare(),從而建立1個Looper例項
final MessageQueue queue = me.mQueue;
// 獲取Looper例項中的訊息佇列物件(MessageQueue)
// 2\. 訊息迴圈(通過for迴圈)
for (;;) {
// 2.1 從訊息佇列中取出訊息
Message msg = queue.next();
if (msg == null) {
return;
}
// next():取出訊息佇列裡的訊息
// 若取出的訊息為空,則執行緒阻塞
// ->> 分析1
// 2.2 派發訊息到對應的Handler
msg.target.dispatchMessage(msg);
// 把訊息Message派發給訊息物件msg的target屬性
// target屬性實際是1個handler物件
// ->>分析2
// 3\. 釋放訊息佔據的資源
msg.recycle();
}
}
/**
* 分析1:queue.next()
* 定義:屬於訊息佇列類(MessageQueue)中的方法
* 作用:出隊訊息,即從 訊息佇列中 移出該訊息
*/
Message next() {
...// 僅貼出關鍵程式碼
// 該引數用於確定訊息佇列中是否還有訊息
// 從而決定訊息佇列應處於出隊訊息狀態 or 等待狀態
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// nativePollOnce方法在native層,若是nextPollTimeoutMillis為-1,此時訊息佇列處於等待狀態
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
// 出隊訊息,即 從訊息佇列中取出訊息:按建立Message物件的時間順序
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 取出了訊息
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 {
// 若 訊息佇列中已無訊息,則將nextPollTimeoutMillis引數設為-1
// 下次迴圈時,訊息佇列則處於等待狀態
nextPollTimeoutMillis = -1;
}
......
}
.....
}
}// 回到分析原處
/**
* 分析2:dispatchMessage(msg)
* 定義:屬於處理者類(Handler)中的方法
* 作用:派發訊息到對應的Handler例項 & 根據傳入的msg作出對應的操作
*/
public void dispatchMessage(Message msg) {
// 1\. 若msg.callback屬性不為空,則代表使用了post(Runnable r)傳送訊息
// 則執行handleCallback(msg),即回撥Runnable物件裡複寫的run()
// 上述結論會在講解使用“post(Runnable r)”方式時講解
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
// 2\. 若msg.callback屬性為空,則代表使用了sendMessage(Message msg)傳送訊息(即此處需討論的)
// 則執行handleMessage(msg),即回撥複寫的handleMessage(msg) ->> 分析3
handleMessage(msg);
}
}
/**
* 分析3:handleMessage(msg)
* 注:該方法 = 空方法,在建立Handler例項時複寫 = 自定義訊息處理方式
**/
public void handleMessage(Message msg) {
... // 建立Handler例項時複寫
}
總結:
(1)訊息迴圈的操作 = 訊息出隊 + 分發給對應的Handler例項
(2)分發給對應的Handler的過程:根據出隊訊息的歸屬者通過dispatchMessage(msg)進行分發,最終回撥複寫的handleMessage(Message msg),從而實現 訊息處理 的操作
(3)特別注意:在進行訊息分發時(dispatchMessage(msg)),會進行1次傳送方式的判斷:
若msg.callback屬性不為空,則代表使用了post(Runnable r)傳送訊息,則直接回撥Runnable物件裡複寫的run()若msg.callback屬性為空,則代表使用了sendMessage(Message msg)傳送訊息,則回撥複寫的handleMessage(msg)
步驟1:在主執行緒中 通過匿名內部類 建立Handler類物件
/**
* 具體使用
*/
private Handler mhandler = new Handler(){
// 通過複寫handlerMessage()從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
...// 需執行的UI操作
}
};
/**
* 原始碼分析:Handler的構造方法
* 作用:初始化Handler物件 & 繫結執行緒
* 注:
* a. Handler需繫結 執行緒才能使用;繫結後,Handler的訊息處理會在繫結的執行緒中執行
* b. 繫結方式 = 先指定Looper物件,從而繫結了 Looper物件所繫結的執行緒(因為Looper物件本已繫結了對應執行緒)
* c. 即:指定了Handler物件的 Looper物件 = 繫結到了Looper物件所在的執行緒
*/
public Handler() {
this(null, false);
// ->>分析1
}
/**
* 分析1:this(null, false) = Handler(null,false)
*/
public Handler(Callback callback, boolean async) {
...// 僅貼出關鍵程式碼
// 1\. 指定Looper物件
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// Looper.myLooper()作用:獲取當前執行緒的Looper物件;若執行緒無Looper物件則丟擲異常
// 即 :若執行緒中無建立Looper物件,則也無法建立Handler物件
// 故 若需在子執行緒中建立Handler物件,則需先建立Looper物件
// 注:可通過Loop.getMainLooper()可以獲得當前程式的主執行緒的Looper物件
// 2\. 繫結訊息佇列物件(MessageQueue)
mQueue = mLooper.mQueue;
// 獲取該Looper物件中儲存的訊息佇列物件(MessageQueue)
// 至此,保證了handler物件 關聯上 Looper物件中MessageQueue
}
當建立Handler物件時,則通過 構造方法 自動關聯當前執行緒的Looper物件 & 對應的訊息佇列物件(MessageQueue),從而 自動繫結了 實現建立Handler物件操作的執行緒
總結:
步驟2:建立訊息物件
具體使用
Message msg = Message.obtain(); // 例項化訊息物件
msg.what = 1; // 訊息標識
msg.obj = "AA"; // 訊息內容存放
原始碼分析
/**
* 原始碼分析:Message.obtain()
* 作用:建立訊息物件
* 注:建立Message物件可用關鍵字new 或 Message.obtain(),建議使用obtain()建立訊息物件,避免每次都使用new重新分配記憶體。(當池內無訊息物件可複用,則用關鍵詞new建立)
*/
public static Message obtain() {
// Message內部維護了1個Message池,用於Message訊息物件的複用
// 使用obtain()則是直接從池內獲取
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
// 建議:使用obtain()”建立“訊息物件,避免每次都使用new重新分配記憶體
}
// 若池內無訊息物件可複用,則還是用關鍵字new建立
return new Message();
}
步驟3:在工作執行緒中傳送訊息到訊息佇列
具體使用
mHandler.sendMessage(msg);
原始碼分析
/**
* 原始碼分析:mHandler.sendMessage(msg)
* 定義:屬於處理器類(Handler)的方法
* 作用:將訊息 傳送 到訊息佇列中(Message ->> MessageQueue)
*/
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
// ->>分析1
}
/**
* 分析1:sendMessageDelayed(msg, 0)
**/
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
// ->> 分析2
}
/**
* 分析2:sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
**/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 1\. 獲取對應的訊息佇列物件(MessageQueue)
MessageQueue queue = mQueue;
// 2\. 呼叫了enqueueMessage方法 ->>分析3
return enqueueMessage(queue, msg, uptimeMillis);
}
/**
* 分析3:enqueueMessage(queue, msg, uptimeMillis)
**/
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 1\. 將msg.target賦值為this
// 即 :把 當前的Handler例項物件作為msg的target屬性
msg.target = this;
// 請回憶起上面說的Looper的loop()中訊息迴圈時,會從訊息佇列中取出每個訊息msg,然後執行msg.target.dispatchMessage(msg)去處理訊息
// 實際上則是將該訊息派發給對應的Handler例項
// 2\. 呼叫訊息佇列的enqueueMessage()
// 即:Handler傳送的訊息,最終是儲存到訊息佇列->>分析4
return queue.enqueueMessage(msg, uptimeMillis);
}
/**
* 分析4:queue.enqueueMessage(msg, uptimeMillis)
* 定義:屬於訊息佇列類(MessageQueue)的方法
* 作用:入隊,即 將訊息 根據時間 放入到訊息佇列中(Message ->> MessageQueue)
* 採用單連結串列實現:提高插入訊息、刪除訊息的效率
*/
boolean enqueueMessage(Message msg, long when) {
...// 僅貼出關鍵程式碼
synchronized (this) {
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
// 判斷訊息佇列裡有無訊息
// a. 若無,則將當前插入的訊息 作為隊頭 & 若此時訊息佇列處於等待狀態,則喚醒
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
// b. 判斷訊息佇列裡有訊息,則根據 訊息(Message)建立的時間 插入到佇列中
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
// 之後,隨著Looper物件的無限訊息迴圈
// 不斷從訊息佇列中取出Handler傳送的訊息 & 分發到對應Handler
// 最終回撥Handler.handleMessage()處理訊息
總結
Handler傳送訊息的本質 =
將訊息物件的target屬性設定為當前Handler例項(將Message繫結到Handler,使執行訊息迴圈時將訊息派發給對應的Handler例項)
獲取對應的訊息佇列物件MessageQueue,呼叫MessageQueue.enqueueMessage(),將Handler需傳送訊息入隊到繫結執行緒的訊息佇列中。
之後,隨著Looper物件的無限訊息迴圈,不斷從訊息佇列中取出Handler傳送的訊息&根據target分發到對應Handler,最終回撥Handler.handleMessage()處理訊息
原始碼總結
工作流程總結
方式2:使用 Handler.post()
步驟1:在主執行緒中建立Handler例項
具體使用
private Handler mhandler = new Handler();
// 與方式1的使用不同:此處無複寫Handler.handleMessage()
原始碼分析
/**
* 原始碼分析:Handler的構造方法
* 作用:
* a. 在此之前,主執行緒建立時隱式建立Looper物件、MessageQueue物件
* b. 初始化Handler物件、繫結執行緒 & 進入訊息迴圈
* 此處的原始碼分析類似方式1,此處不作過多描述
*/
步驟2:在工作執行緒中 傳送訊息到訊息佇列中
具體使用
mHandler.post(new Runnable() {
@Override
public void run() {
//傳入1個Ruunable物件,複寫run()從而指定UI操作
... // 需執行的UI操作
}
});
原始碼分析
/**
* 原始碼分析:Handler.post(Runnable r)
* 定義:屬於處理者類(Handler)中的方法
* 作用:定義UI操作、將Runnable物件封裝成訊息物件 & 傳送 到訊息佇列中(Message ->> MessageQueue)
* 注:
* a. 相比sendMessage(),post()最大的不同在於,更新的UI操作可直接在重寫的run()中定義
* b. 實際上,Runnable並無建立新執行緒,而是傳送 訊息 到訊息佇列中
*/
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
// getPostMessage(r) 的原始碼分析->>分析1
// sendMessageDelayed()的原始碼分析 ->>分析2
}
/**
* 分析1:getPostMessage(r)
* 作用:將傳入的Runable物件封裝成1個訊息物件
**/
private static Message getPostMessage(Runnable r) {
// 1\. 建立1個訊息物件(Message)
Message m = Message.obtain();
// 注:建立Message物件可用關鍵字new 或 Message.obtain()
// 建議:使用Message.obtain()建立,
// 原因:因為Message內部維護了1個Message池,用於Message的複用,使用obtain()直接從池內獲取,從而避免使用new重新分配記憶體
// 2\. 將 Runable物件 賦值給訊息物件(message)的callback屬性
m.callback = r;
// 3\. 返回該訊息物件
return m;
} // 回到呼叫原處
/**
* 分析2:sendMessageDelayed(msg, 0)
* 作用:實際上,從此處開始,則類似方式1 = 將訊息入隊到訊息佇列,
* 即 最終是呼叫MessageQueue.enqueueMessage()
**/
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
// 請看分析3
}
/**
* 分析3:sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis)
**/
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
// 1\. 獲取對應的訊息佇列物件(MessageQueue)
MessageQueue queue = mQueue;
// 2\. 呼叫了enqueueMessage方法 ->>分析3
return enqueueMessage(queue, msg, uptimeMillis);
}
/**
* 分析4:enqueueMessage(queue, msg, uptimeMillis)
**/
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
// 1\. 將msg.target賦值為this
// 即 :把 當前的Handler例項物件作為msg的target屬性
msg.target = this;
// 請回憶起上面說的Looper的loop()中訊息迴圈時,會從訊息佇列中取出每個訊息msg,然後執行msg.target.dispatchMessage(msg)去處理訊息
// 實際上則是將該訊息派發給對應的Handler例項
// 2\. 呼叫訊息佇列的enqueueMessage()
// 即:Handler傳送的訊息,最終是儲存到訊息佇列
return queue.enqueueMessage(msg, uptimeMillis);
}
// 注:實際上從分析2開始,原始碼 與 sendMessage(Message msg)傳送方式相同
訊息物件的建立 = 內部 根據Runnable物件而封裝
傳送到訊息佇列的邏輯 = 方式1中sendMessage(Message msg)
原始碼總結
工作流程總結
Handler.sendMessage與Handler.post比較
工作流程類似,區別在於
1、Handler.post不需外部建立訊息物件,而是內部根據傳入的Runnable物件封裝訊息物件
2、回撥的訊息處理方法是:複寫Runnable物件的run()
(六)記憶體洩露
6.1)問題描述
Handler的一般用法 = 新建Handler子類(內部類) 、匿名Handler內部類
/**
* 方式1:新建Handler子類(內部類)
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主執行緒建立時便自動建立Looper & 對應的MessageQueue
// 之後執行Loop()進入訊息迴圈
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1\. 例項化自定義的Handler類物件->>分析1
//注:此處並無指定Looper,故自動繫結當前執行緒(主執行緒)的Looper、MessageQueue
showhandler = new FHandler();
// 2\. 啟動子執行緒1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 1;// 訊息標識
msg.obj = "AA";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
// 3\. 啟動子執行緒2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 2;// 訊息標識
msg.obj = "BB";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
}
// 分析1:自定義Handler子類
class FHandler extends Handler {
// 通過複寫handlerMessage() 從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到執行緒1的訊息");
break;
case 2:
Log.d(TAG, " 收到執行緒2的訊息");
break;
}
}
}
}
/**
* 方式2:匿名Handler內部類
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主執行緒建立時便自動建立Looper & 對應的MessageQueue
// 之後執行Loop()進入訊息迴圈
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1\. 通過匿名內部類例項化的Handler類物件
//注:此處並無指定Looper,故自動繫結當前執行緒(主執行緒)的Looper、MessageQueue
showhandler = new Handler(){
// 通過複寫handlerMessage()從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到執行緒1的訊息");
break;
case 2:
Log.d(TAG, " 收到執行緒2的訊息");
break;
}
}
};
// 2\. 啟動子執行緒1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 1;// 訊息標識
msg.obj = "AA";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
// 3\. 啟動子執行緒2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 2;// 訊息標識
msg.obj = "BB";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
}
嚴重警告:This Handler class should be static or leaks might occur(null)
警告原因=該Handler類由於沒有設定為靜態類,可能會導致記憶體洩露。
6.2)原因講解
1、儲備知識
主執行緒的Looper物件的生命週期 = 該應用程式的生命週期
在Java中,非靜態內部類 & 匿名內部類都預設持有 外部類的引用
2、洩露原因描述
/**
* 方式1:新建Handler子類(內部類)
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主執行緒建立時便自動建立Looper & 對應的MessageQueue
// 之後執行Loop()進入訊息迴圈
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1\. 例項化自定義的Handler類物件->>分析1
//注:此處並無指定Looper,故自動繫結當前執行緒(主執行緒)的Looper、MessageQueue
showhandler = new FHandler();
// 2\. 啟動子執行緒1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 1;// 訊息標識
msg.obj = "AA";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
// 3\. 啟動子執行緒2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 2;// 訊息標識
msg.obj = "BB";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
}
// 分析1:自定義Handler子類
class FHandler extends Handler {
// 通過複寫handlerMessage() 從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到執行緒1的訊息");
break;
case 2:
Log.d(TAG, " 收到執行緒2的訊息");
break;
}
}
}
}
/**
* 方式2:匿名Handler內部類
*/
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主執行緒建立時便自動建立Looper & 對應的MessageQueue
// 之後執行Loop()進入訊息迴圈
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1\. 通過匿名內部類例項化的Handler類物件
//注:此處並無指定Looper,故自動繫結當前執行緒(主執行緒)的Looper、MessageQueue
showhandler = new Handler(){
// 通過複寫handlerMessage()從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到執行緒1的訊息");
break;
case 2:
Log.d(TAG, " 收到執行緒2的訊息");
break;
}
}
};
// 2\. 啟動子執行緒1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 1;// 訊息標識
msg.obj = "AA";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
// 3\. 啟動子執行緒2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 2;// 訊息標識
msg.obj = "BB";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
}
}
從上述示例程式碼可知:
上述的Handler例項的訊息佇列有2個分別來自執行緒1、2的訊息(分別 為延遲1s、6s)
在Handler訊息佇列 還有未處理的訊息 / 正在處理訊息時,訊息佇列中的Message持有Handler例項的引用
由於Handler = 非靜態內部類 / 匿名內部類(2種使用方式),故又預設持有外部類的引用(即MainActivity例項),引用關係如下圖
上述的引用關係會一直保持,直到Handler訊息佇列中的所有訊息被處理完畢
在Handler訊息佇列 還有未處理的訊息 / 正在處理訊息時,此時若需銷燬外部類MainActivity,但由於上述引用關係,垃圾回收器(GC)無法回收MainActivity,從而造成記憶體洩漏。如下圖:
3、總結
(1)當Handler訊息佇列 還有未處理的訊息 / 正在處理訊息時,存在引用關係: “未被處理 / 正處理的訊息 -> Handler例項 -> 外部類”
(2)若出現 Handler的生命週期 > 外部類的生命週期 時(即 Handler訊息佇列 還有未處理的訊息 / 正在處理訊息 而 外部類需銷燬時),將使得外部類無法被垃圾回收器(GC)回收,從而造成 記憶體洩露
6.3)解決方案
從上面可看出,造成記憶體洩露的原因有2個關鍵條件:
1、存在“未被處理 / 正處理的訊息 -> Handler例項 -> 外部類” 的引用關係
2、Handler的生命週期 > 外部類的生命週期
解決方案1:靜態內部類+弱引用(推薦:保證訊息佇列中所有訊息都能執行)
(1)原理
1、將Handler的子類設定成 靜態內部類:預設不持有外部類的引用,從而使得 “未被處理 / 正處理的訊息 -> Handler例項 -> 外部類” 的引用關係 的引用關係 不復存在。
2、使用WeakReference弱引用持有Activity例項:弱引用的物件擁有短暫的生命週期。在垃圾回收器執行緒掃描時,一旦發現了只具有弱引用的物件,不管當前記憶體空間足夠與否,都會回收它的記憶體
(2)解決程式碼
public class MainActivity extends AppCompatActivity {
public static final String TAG = "carson:";
private Handler showhandler;
// 主執行緒建立時便自動建立Looper & 對應的MessageQueue
// 之後執行Loop()進入訊息迴圈
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//1\. 例項化自定義的Handler類物件->>分析1
//注:
// a. 此處並無指定Looper,故自動繫結當前執行緒(主執行緒)的Looper、MessageQueue;
// b. 定義時需傳入持有的Activity例項(弱引用)
showhandler = new FHandler(this);
// 2\. 啟動子執行緒1
new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 1;// 訊息標識
msg.obj = "AA";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
// 3\. 啟動子執行緒2
new Thread() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// a. 定義要傳送的訊息
Message msg = Message.obtain();
msg.what = 2;// 訊息標識
msg.obj = "BB";// 訊息存放
// b. 傳入主執行緒的Handler & 向其MessageQueue傳送訊息
showhandler.sendMessage(msg);
}
}.start();
}
// 分析1:自定義Handler子類
// 設定為:靜態內部類
private static class FHandler extends Handler{
// 定義 弱引用例項
private WeakReference<Activity> reference;
// 在構造方法中傳入需持有的Activity例項
public FHandler(Activity activity) {
// 使用WeakReference弱引用持有Activity例項
reference = new WeakReference<Activity>(activity); }
// 通過複寫handlerMessage() 從而確定更新UI的操作
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
Log.d(TAG, "收到執行緒1的訊息");
break;
case 2:
Log.d(TAG, " 收到執行緒2的訊息");
break;
}
}
}
}
解決方案2:當外部類結束生命週期時,清空Handler內訊息佇列
(1)原理
當 外部類(此處以Activity為例) 結束生命週期時(此時系統會呼叫onDestroy()),清除 Handler訊息佇列裡的所有訊息(呼叫removeCallbacksAndMessages(null))
不僅使得 “未被處理 / 正處理的訊息 -> Handler例項 -> 外部類” 的引用關係 不復存在,同時 使得 Handler的生命週期(即 訊息存在的時期) 與 外部類的生命週期 同步
(2)解決程式碼
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部類Activity生命週期結束時,同時清空訊息佇列 & 結束Handler生命週期
}
(七)執行緒安全
通過建立一個Handler子類的物件,每個acvivity只需一個Handler物件。後臺程式可通過兩種方式Handler進行通訊:message和Runnable物件,其結果實質都是將在Handler的佇列中放入內容,message是放置資訊,可以傳遞一些引數,Handler獲取這些資訊並將判度如何處理,而Runnable則是直接給出處理的方法。佇列就是依次執行,Handler會處理完一個訊息或者執行完某個處理在進行下一步,這樣不會出現多個執行緒同時要求進行UI處理而引發的混亂現象。
這些佇列中的內容(無論Message還是Runnable)可以要求馬上執行,延遲一定時間執行或者指定某個時刻執行,如果將他們放置在佇列頭,則表示具有最高有限級別,立即執行。這些函式包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用於在佇列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。
最後
作為一個有進取心的 Android 開發者,應該擁有自己的技術棧和規劃非常重要,技術棧確保你有足夠的市場競爭力,從而形成護城河;技術規劃則可以給你一個明確的學習目標。解除安裝抖音、微博、鬥魚、遊戲吧,做好規劃,共勉!
如果你苦於沒有好的時間管理方法,可以點選我的GitHub。這個是我熟悉的一個大佬的工作學習方法實踐,推薦給大家
veCallbacksAndMessages(null))
不僅使得 “未被處理 / 正處理的訊息 -> Handler例項 -> 外部類” 的引用關係 不復存在,同時 使得 Handler的生命週期(即 訊息存在的時期) 與 外部類的生命週期 同步
(2)解決程式碼
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
// 外部類Activity生命週期結束時,同時清空訊息佇列 & 結束Handler生命週期
}
(七)執行緒安全
通過建立一個Handler子類的物件,每個acvivity只需一個Handler物件。後臺程式可通過兩種方式Handler進行通訊:message和Runnable物件,其結果實質都是將在Handler的佇列中放入內容,message是放置資訊,可以傳遞一些引數,Handler獲取這些資訊並將判度如何處理,而Runnable則是直接給出處理的方法。佇列就是依次執行,Handler會處理完一個訊息或者執行完某個處理在進行下一步,這樣不會出現多個執行緒同時要求進行UI處理而引發的混亂現象。
這些佇列中的內容(無論Message還是Runnable)可以要求馬上執行,延遲一定時間執行或者指定某個時刻執行,如果將他們放置在佇列頭,則表示具有最高有限級別,立即執行。這些函式包括有:sendMessage(), sendMessageAtFrontOfQueue(), sendMessageAtTime(), sendMessageDelayed()以及用於在佇列中加入Runnable的post(), postAtFrontOfQueue(), postAtTime(),postDelay()。
最後
作為一個有進取心的 Android 開發者,應該擁有自己的技術棧和規劃非常重要,技術棧確保你有足夠的市場競爭力,從而形成護城河;技術規劃則可以給你一個明確的學習目標。解除安裝抖音、微博、鬥魚、遊戲吧,做好規劃,共勉!
如果你苦於沒有好的時間管理方法,可以點選我的GitHub。這個是我熟悉的一個大佬的工作學習方法實踐,推薦給大家
相關文章
- ES6的概念以及執行環境~滿滿的乾貨
- Golang進階,揉碎資料庫中介軟體,乾貨滿滿!Golang資料庫
- 乾貨滿滿的 Go Modules 和 goproxy.cnGo
- GopherChina 2021 定了,乾貨滿滿的來了Go
- 一份資料工程師必備的學習資源,乾貨滿滿(附連結)工程師
- 乾貨滿滿!深入解析增強分析的概念及其優勢
- 乾貨滿滿,騰訊雲+社群技術沙龍 Kafka Meetup 深圳站圓滿結束Kafka
- 乾貨滿滿!10分鐘看懂Docker和K8SDockerK8S
- 乾貨滿滿!!!面試必備OJ題:連結串列篇(一)面試
- 最乾貨 Go 進階講義搶先披露——毛老師培訓課堂開課了!Go
- 【乾貨滿滿】貝塞爾曲線(Bézier curve)——什麼神仙操作
- Mac下安裝NDK,進行JNI開發Mac
- Dell-Windows10下裝Ubuntu 16.04 雙系統,Ubuntu引導開啟-經驗貼,滿乾貨!WindowsUbuntu
- 推薦系統演算法合集,滿滿都是乾貨(建議收藏)演算法
- 乾貨滿滿 | 微服務化的資料庫設計與讀寫分離微服務資料庫
- Power BI進階秘籍,乾貨滿滿!如何將度量值轉化為切片器(動態切換分析指標),實操指南來了!指標
- 資料庫週刊第十六期丨滿滿乾貨,快來墨天輪下載吧!資料庫
- 檔案上傳下載攻略,斷點續傳等等那些事兒,滿滿乾貨(react&node)斷點React
- Jeff Dean執筆谷歌團隊2017年終總結,乾貨滿滿谷歌
- 【乾貨滿滿】1.5w字初中級前端面試複習總結前端面試
- 手把手教你實現 Tree 元件搜尋過濾功能,乾貨滿滿!元件
- 滿滿乾貨!手把手教你實現基於eTS的分散式計算器分散式
- 乾貨滿滿 | 美團資料庫運維自動化系統構建之路資料庫運維
- 乾貨滿滿2017中國軟體生態大會亮點搶先看
- 知識圖譜論文大合集,這份乾貨滿滿的筆記解讀值得收藏筆記
- Android NDK開發(二) 使用ndk-build構建工具進行NDK開發AndroidUI
- 乾貨滿滿!2020版好程式設計師新電商大資料平臺全套學習資料程式設計師大資料
- 按著這些視訊路線走,你絕對能成為前端大佬(乾貨滿滿)前端
- 又跳槽!3年Java經驗收割成都大廠的面試心得(乾貨滿滿&文末有福利)Java面試
- 六個良心軟體,滿足你對乾貨的任何需求!
- 乾貨滿滿!解密阿里雲RPA (機器人流程自動化)的產品架構和商業化發展解密阿里機器人架構
- 驚了!安信竟然出了Windows自動下載安裝或更新廠商驅動軟體的教程!細節乾貨福利滿滿Windows
- 乾貨滿滿!2024紫光同芯合作伙伴大會安全晶片創新應用論壇即將開啟晶片
- Android NDK開發之旅15 NDK Eclipse下NDK開發流程AndroidEclipse
- 乾貨 | Java8 新特性指導手冊Java
- 直擊DTCC2022第二天,六場技術論壇乾貨滿滿
- NDK 知識梳理(1) 使用 CMake 進行 NDK 開發之初體驗
- 《夢幻西遊三維版》520釋出會重磅爆料!新角色新賽事新玩法乾貨滿滿