面試必問的一個知識點,關於HandlerThread的使用場景以及怎樣使用 HandlerThrea

前言

有些同學老問我,快要校招了 今年的金三銀四退成了金四銀五了,我改準備哪些知識點去面試複習,然而要準備的知識點很龐大,今天要講的知識其中之一。一篇關於HandlerThread的解析

文檔筆記~
面試必問的一個知識點,關於HandlerThread的使用場景以及怎樣使用 HandlerThrea

知識彙總的PDF相關內容後續GitHub更新,想衝擊金三銀四的小夥伴可以找找看看,歡迎star
順手留下GitHub鏈接,需要獲取相關面試等內容的可以自己去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)

使用場景

HandlerThread是Google幫我們封裝好的,可以用來執行多個耗時操作,而不需要 多次開啓線程,裏面是採用HandlerLooper實現的。

Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called

怎樣使用HandlerThread

1. 創建HandlerThread的實例對象
  HandlerThread handlerThread = new HandlerThread("myHandlerThread" );

該參數表示線程的名字,可以隨便選擇。

1. 啓動我們創建的HandlerThread線程
  handlerThread.start();

將我們的handlerThread與Handler綁定在一起。 還記得是怎樣將Handler與線 程對象綁定在一起的嗎?其實很簡單,就是將線程的looperHandler綁定在一 起,代碼如下:

  mThreadHandler = new Handler(mHandlerThread.getLooper()) { 
   @Override 
   public void handleMessage(Message msg) { 
     checkForUpdate(); 
     if(isUpdate){ 
        mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO); 
     } 
   }
  };

注意必須按照以上三個步驟來,下面在講解源碼的時候會分析其原因

完整測試代碼如下

  public class MainActivity extends AppCompatActivity { 
     private static final int MSG_UPDATE_INFO = 0x100; 
     Handler mMainHandler = new Handler(); 
     private TextView mTv; 
     private Handler mThreadHandler; 
     private HandlerThread mHandlerThread; 
     private boolean isUpdate = true; 

     @Override 
     protected void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setContentView(R.layout.activity_main); 
        mTv = (TextView) findViewById(R.id.tv); 
        initHandlerThread(); 
     }

     private void initHandlerThread() {
        mHandlerThread = new HandlerThread("xujun"); 
        mHandlerThread.start(); 
        mThreadHandler = new Handler(mHandlerThread.getLooper()) { 

          @Override 
          public void handleMessage(Message msg) { 
             checkForUpdate(); 
             if (isUpdate) { 
                 mThreadHandler.sendEmptyMessage(MSG_UPDATE_I NFO); 
             } 
          } 
      };
   }

   /**
    * 模擬從服務器解析數據 
    */ 
    private void checkForUpdate() { 
       try {
           //模擬耗時 
           Thread.sleep(1200); 
           mMainHandler.post(new Runnable() { 
                @Override 
                public void run() { 
                   String result = "實時更新中,當前股票行情:<font color='red'>%d</font>"; 
                   result = String.format(result, (int) (Math.r andom() * 5000 + 1000)); 
                   mTv.setText(Html.fromHtml(result)); 
                } 
           }); 
    } catch (InterruptedException e) { 
           e.printStackTrace(); 
    } 
  }

   @Override 
   protected void onResume() { 
      isUpdate = true;
      super.onResume(); 
      mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO); 
    }

    @Override 
    protected void onPause() { 
       super.onPause(); 
       isUpdate = false; 
       mThreadHandler.removeMessages(MSG_UPDATE_INFO); 
    }

    @Override 
    protected void onDestroy() { 
       super.onDestroy(); 
       mHandlerThread.quit(); 
       mMainHandler.removeCallbacksAndMessages(null); 
    }
   }

運行以上測試代碼,將可以看到如下效果圖(例子不太恰當,主要使用場景是在 handleMessage中執行耗時操作)
面試必問的一個知識點,關於HandlerThread的使用場景以及怎樣使用 HandlerThrea

HandlerThread源碼分析

官方源代碼如下,是基於sdk23的,可以看到,只有一百多行代碼而已。

  public class HandlerThread extends Thread {
     int mPriority; 
     int mTid = -1; 
     Looper mLooper; 

     public HandlerThread(String name) { 
        super(name); 
        mPriority = Process.THREAD_PRIORITY_DEFAULT; 
     }

     public HandlerThread(String name, int priority) { 
        super(name); 
        mPriority = priority; 
     }

     /**
      * Call back method that can be explicitly overridden if nee ded to execute some 
      * setup before Looper loops. 
      */ 
      protected void onLooperPrepared() { 
      }

      @Override 
      public void run() { 
         mTid = Process.myTid(); 
         Looper.prepare(); 
         //持有鎖機制來獲得當前線程的Looper對象 
         synchronized (this) { 
             mLooper = Looper.myLooper(); 
             //發出通知,當前線程已經創建mLooper對象成功,這裏主要是通知g etLooper方法中的wait notifyAll(); 
         }
         //設置線程的優先級別 
         Process.setThreadPriority(mPriority); 
         //這裏默認是空方法的實現,我們可以重寫這個方法來做一些線程開始之前 的準備,方便擴展 
         onLooperPrepared(); 
         Looper.loop(); 
         mTid = -1;
     }
     public Looper getLooper() { 
        if (!isAlive()) { 
            return null;
        }
       // 直到線程創建完Looper之後才能獲得Looper對象,Looper未創建成功 ,阻塞 synchronized (this) { 
             while (isAlive() && mLooper == null) { 
                try {
                    wait(); 
                } catch (InterruptedException e) { 
                } 
             } 
       }
       return mLooper; 
     }

     public boolean quit() { 
       Looper looper = getLooper(); 
       if (looper != null) { 
           looper.quit(); 
           return true;
       }
       return false; 
     }

     public boolean quitSafely() { 
       Looper looper = getLooper(); 
       if (looper != null) { 
           looper.quitSafely(); 
           return true; 
       }
       return false; 
     }

     /**
      * Returns the identifier of this thread. See Process.myTid( ).
      */ 
      public int getThreadId() { 
        return mTid; 
      } 
  }

1)首先我們先來看一下它的構造方法

  public HandlerThread(String name) { 
     super(name); 
     mPriority = Process.THREAD_PRIORITY_DEFAULT; 
  }

  public HandlerThread(String name, int priority) { 
     super(name); 
     mPriority = priority; 
  }

有兩個構造方法,一個參數的和兩個參數的,name代表當前線程的名稱,priority 爲線程的優先級別

2)接着我們來看一下run()方法,在run方法裏面我們 可以看到我們會初始化一個Looper,並設置線程的優 先級別

  public void run() { 
     mTid = Process.myTid(); 
     Looper.prepare(); 
     //持有鎖機制來獲得當前線程的Looper對象 
     synchronized (this) { 
         mLooper = Looper.myLooper(); 
         //發出通知,當前線程已經創建mLooper對象成功,這裏主要是通知getLo oper方法中的wait notifyAll(); 
     }
     //設置線程的優先級別 
     Process.setThreadPriority(mPriority); 
     //這裏默認是空方法的實現,我們可以重寫這個方法來做一些線程開始之前的準備 ,方便擴展 
     onLooperPrepared(); 
     Looper.loop(); 
     mTid = -1; 
  }
  • 還記得我們前面我們說到使用HandlerThread的時候必須調用 start() 方 法,接着纔可以將我們的HandlerThread和我們的handler綁定在一起嗎?其實 原因就是我們是在 run() 方法纔開始初始化我們的looper,而我們調用 HandlerThreadstart() 方法的時候,線程會交給虛擬機調度,由虛擬機 自動調用run方法
    mHandlerThread.start(); 
    mThreadHandler = new Handler(mHandlerThread.getLooper()) { 
       @Override 
       public void handleMessage(Message msg) { 
          checkForUpdate(); 
          if(isUpdate){ 
             mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
          } 
       } 
    };
  • 這裏我們爲什麼要使用鎖機制和 notifyAll() ;,原因我們可以 從 getLooper() 方法中知道
    public Looper getLooper() { 
     if (!isAlive()) { 
        return null; 
     }
     // 直到線程創建完Looper之後才能獲得Looper對象,Looper未創建成功,阻塞 
     synchronized (this) { 
        while (isAlive() && mLooper == null) { 
             try {
                 wait(); 
             } catch (InterruptedException e) { 
             } 
        } 
     }
     return mLooper; 
    }

    總結: 在獲得mLooper對象的時候存在一個同步的問題,只有當線程創建成功並且 Looper對象也創建成功之後才能獲得mLooper的值。這裏等待方法wait和run方法 中的notifyAll方法共同完成同步問題。

3)接着我們來看一下quit方法和quitSafe方法

  //調用這個方法退出Looper消息循環,及退出線程 
  public boolean quit() { 
     Looper looper = getLooper(); 
     if (looper != null) {
         looper.quit(); 
         return true; 
     }
     return false; 
  }
  //調用這個方法安全地退出線程 
  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) 
  public boolean quitSafely() { 
     Looper looper = getLooper(); 
     if (looper != null) { 
         looper.quitSafely(); 
         return true; 
     }
     return false;
  }

跟蹤這兩個方法容易知道只兩個方法最終都會調用MessageQueuequit(boolean safe) 方法

  void quit(boolean safe) { 
       if (!mQuitAllowed) { throw new IllegalStateException("Main thread not allowed to quit."); 
       }
       synchronized (this) { 
           if (mQuitting) { 
               return; 
           }
           mQuitting = true; 
           //安全退出調用這個方法 
           if (safe) { 
               removeAllFutureMessagesLocked(); 
           } else {//不安全退出調用這個方法 
               removeAllMessagesLocked(); 
           }
           // We can assume mPtr != 0 because mQuitting was previou sly false.nativeWake(mPtr); 
       } 
  }

不安全的會調用 removeAllMessagesLocked(); 這個方法,我們來看這個方法是 怎樣處理的,其實就是遍歷Message鏈表,移除所有信息的回調,並重置爲null。

  private void removeAllMessagesLocked() { 
     Message p = mMessages; 
     while (p != null) { 
           Message n = p.next; 
           p.recycleUnchecked(); 
           p = n; 
     }
     mMessages = null; 
  }

安全地會調用 removeAllFutureMessagesLocked(); 這個方法,它會根據 Message.when這個屬性,判斷我們當前消息隊列是否正在處理消息,沒有正在處 理消息的話,直接移除所有回調,正在處理的話,等待該消息處理處理完畢再退出該循環。因此說 quitSafe() 是安全的,而 quit() 方法是不安全的,因爲quit方 法不管是否正在處理消息,直接移除所有回調。

  private void removeAllFutureMessagesLocked() { 
     final long now = SystemClock.uptimeMillis(); 
     Message p = mMessages; 
     if (p != null) { 
        //判斷當前隊列中的消息是否正在處理這個消息,沒有的話,直接移除所有 回調 
        if (p.when > now) { 
            removeAllMessagesLocked(); 
        } else {//正在處理的話,等待該消息處理處理完畢再退出該循環 
            Message n; 
            for (;;) { 
                n = p.next;
                if (n == null) { 
                    return; 
                }
                if (n.when > now) { 
                    break; 
                }
                p = n; 
           }
           p.next = null; 
           do {
               p = n; 
               n = p.next; 
               p.recycleUnchecked(); 
           } while (n != null); 
        } 
     } 
  }

文檔筆記~
面試必問的一個知識點,關於HandlerThread的使用場景以及怎樣使用 HandlerThrea

知識彙總的PDF相關內容後續GitHub更新,想衝擊金三銀四的小夥伴可以找找看看,歡迎star
順手留下GitHub鏈接,需要獲取相關面試等內容的可以自己去找
https://github.com/xiangjiana/Android-MS
(VX:mm14525201314)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章