下血本買的!萬字Android技術類校招面試題彙總,Android崗

安卓開發程式設計師發表於2020-12-27

簡介

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 小時用來學習。

不出半年,你就能看出變化!

相關文章