下血本買的!萬字Android技術類校招面試題彙總,Android崗
簡介
35歲左右對工程師而言是個不同尋常的年齡段。技術人有可能面臨人生中的轉型:從純技術崗轉向管理崗。也將面臨諸多新的挑戰,關於組建團隊、領導以及KPI設定等。本文將講述阿里資深技術leader張榮從去年1月底接手CRO線NLP演算法團隊以來,在團隊組建、能力建設、以及管理上的一些思考。這些思考從實踐中來,總結出一套方法論,希望能給予轉型中的技術人一些啟發。
重要概念
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()。
作者2013年從java開發,轉做Android開發,在小廠待過,也去過華為,OPPO等大廠待過,18年四月份進了阿里一直到現在。
參與過不少面試,也當面試官 面試過很多人。深知大多數初中級Android工程師,想要提升技能,往往是自己摸索成長,不成體系的學習效果低效漫長,而且極易碰到天花板技術停滯不前!
我整理了一份阿里P7級別的最系統的Android開發主流技術,特別適合有3-5年以上經驗的小夥伴深入學習提升。
主要包括阿里,以及位元組跳動,騰訊,華為,小米,等一線網際網路公司主流架構技術。如果你想深入系統學習Android開發,成為一名合格的高階工程師,可以收藏一下這些Android進階技術選型
我搜集整理過這幾年阿里,以及騰訊,位元組跳動,華為,小米等公司的面試題,把面試的要求和技術點梳理成一份大而全的“ Android架構師”面試 Xmind(實際上比預期多花了不少精力),包含知識脈絡 + 分支細節。
Java語言與原理;
大廠,小廠。Android面試先看你熟不熟悉Java語言
高階UI與自定義view;
自定義view,Android開發的基本功。
效能調優;
資料結構演算法,設計模式。都是這裡面的關鍵基礎和重點需要熟練的。
NDK開發;
未來的方向,高薪必會。
前沿技術;
元件化,熱升級,熱修復,框架設計
網上學習 Android的資料一大堆,但如果學到的知識不成體系,遇到問題時只是淺嘗輒止,不再深入研究,那麼很難做到真正的技術提升。希望這份系統化的技術體系對大家有一個方向參考。
我在搭建這些技術框架的時候,還整理了系統的高階進階教程,會比自己碎片化學習效果強太多,GitHub可見;《Android架構視訊+學習筆記》
當然,想要深入學習並掌握這些能力,並不簡單。關於如何學習,做程式設計師這一行什麼工作強度大家都懂,但是不管工作多忙,每週也要雷打不動的抽出 2 小時用來學習。
知大多數初中級Android工程師,想要提升技能,往往是自己摸索成長,不成體系的學習效果低效漫長,而且極易碰到天花板技術停滯不前!
我整理了一份阿里P7級別的最系統的Android開發主流技術,特別適合有3-5年以上經驗的小夥伴深入學習提升。
主要包括阿里,以及位元組跳動,騰訊,華為,小米,等一線網際網路公司主流架構技術。如果你想深入系統學習Android開發,成為一名合格的高階工程師,可以收藏一下這些Android進階技術選型
我搜集整理過這幾年阿里,以及騰訊,位元組跳動,華為,小米等公司的面試題,把面試的要求和技術點梳理成一份大而全的“ Android架構師”面試 Xmind(實際上比預期多花了不少精力),包含知識脈絡 + 分支細節。
[外鏈圖片轉存中…(img-M7rpzHKx-1609080471406)]
Java語言與原理;
大廠,小廠。Android面試先看你熟不熟悉Java語言
[外鏈圖片轉存中…(img-oYtBxMv7-1609080471407)]
高階UI與自定義view;
自定義view,Android開發的基本功。
[外鏈圖片轉存中…(img-f0ej9aPP-1609080471408)]
效能調優;
資料結構演算法,設計模式。都是這裡面的關鍵基礎和重點需要熟練的。
[外鏈圖片轉存中…(img-akeF8Hth-1609080471409)]
NDK開發;
未來的方向,高薪必會。
[外鏈圖片轉存中…(img-lDNdS2pE-1609080471410)]
前沿技術;
元件化,熱升級,熱修復,框架設計
[外鏈圖片轉存中…(img-AWEYfQEj-1609080471411)]
網上學習 Android的資料一大堆,但如果學到的知識不成體系,遇到問題時只是淺嘗輒止,不再深入研究,那麼很難做到真正的技術提升。希望這份系統化的技術體系對大家有一個方向參考。
我在搭建這些技術框架的時候,還整理了系統的高階進階教程,會比自己碎片化學習效果強太多,GitHub可見;《Android架構視訊+學習筆記》
當然,想要深入學習並掌握這些能力,並不簡單。關於如何學習,做程式設計師這一行什麼工作強度大家都懂,但是不管工作多忙,每週也要雷打不動的抽出 2 小時用來學習。
不出半年,你就能看出變化!
相關文章
- 2016校招,Android開發,常見面試問題彙總Android面試
- Android 面試題彙總Android面試題
- UI 類面試題彙總 | 掘金技術徵文UI面試題
- 2019校招Android面試題解1.0Android面試題
- 如何準備校招技術面試面試
- 愛奇藝校招面試題面試題
- 現場拆題+直通面試 | 美團技術校招直播來了!面試
- 阿里校招Android崗面經分享,Offer入手但委婉拒收阿里Android
- Android 遊戲引擎分類彙總Android遊戲引擎
- 騰訊啟動最大規模校招,技術崗佔比65% :技術在手高薪不愁!高薪
- 技術乾貨:Tomcat面試題彙總及答案Tomcat面試題
- 技術乾貨:Kotlin面試題彙總及答案Kotlin面試題
- 華為面試Android崗;群面+技術面+綜合面+英語面面試Android
- 技術面試 android部分面試Android
- Android 面試題(附答案) | 掘金技術徵文Android面試題
- 技術崗-常見筆試面試題筆試面試題
- 收割騰訊等十幾個Offer後,揭秘進大廠的秘訣和Android技術面試題彙總!Android面試題
- 2017 Android秋招面試總結Android面試
- 【同行說技術】Android圖片處理技術資料彙總(一)Android
- android 面試題總結Android面試題
- 技術乾貨:spring cloud面試題彙總及答案SpringCloud面試題
- 技術乾貨:Kubernetes面試題彙總及答案面試題
- Android中handler問題彙總Android
- Android開發問題彙總Android
- Android校招面經乾貨分享(頭條、快手、小米、美團)|掘金技術徵文Android
- 面試官會問到的專案中的技術問題總彙面試
- Java秋招校招面試Java面試
- 面試斬獲貓眼Android崗Offer,我是怎樣準備Android技術面的知否?面試Android
- 2017上半年技術文章集合【Android】—184篇文章分類彙總Android
- 面試題總結-Android部分面試題Android
- iOS面試題一(技術類)iOS面試題
- 大廠OPPO面試— Android 開發技術面總結面試Android
- 工作快兩年了!斗膽談談校招社招技術面試那些事面試
- 【秋招】京東_資料分析崗_面試題整理面試題
- 校招演算法崗麵筋演算法
- 2018屆android校招面試總結:百度,大疆,樂視,知乎(逐步更新答案)Android面試
- 2017 校招常考演算法題歸納 & 典型題目彙總演算法
- Android複習資料——常見面試演算法題彙總(二)Android面試演算法