Android Looper原理分析


實際業務使用場景: 

       某業務場景需要將本地數據傳遞到服務端,服務端再返回傳遞成功或者失敗的信息。

           1、 失敗時: 重傳5次  

           2、設置客戶端請求的最小時間間隔,這個間隔內最多請求1次

       具體邏輯如下:(這里請求的最小時間間隔設置為:80s,防止客戶端由於某種異常頻繁的調用服務端,造成服務端異常)

image

實現:

handler 的實現:

public class BindHandler extends Handler {
private final static String TAG = "BindHandler";
private static final String DEFAULT_HANDLER_THREAD_NAME = "DeviceBindingHandlerThread";
static final int MSG_DEVICE_BIND = 1;
static final int MSG_DEVICE_BIND_RETRY = 2;
private BindManager mManager;
private HandlerThread mHandlerThread;

BindHandler(BindManager manager){
this(manager,createDefaultHandlerThread());
}

private BindHandler(BindManager manager,HandlerThread thread){
super(thread.getLooper());
mManager = manager;
mHandlerThread = thread;
}

private static HandlerThread createDefaultHandlerThread() {
HandlerThread handlerThread = new HandlerThread(DEFAULT_HANDLER_THREAD_NAME, Process.THREAD_PRIORITY_BACKGROUND);
handlerThread.start();
return handlerThread;
}

public void quitHandlerThread(){
if(null != mHandlerThread){
mHandlerThread.getLooper().quit();
}
}

public void startHandlerThread(){
if(null != mHandlerThread){
mHandlerThread.getLooper().loop();
}
}

public boolean isHandlerThreadAlive(){
return mHandlerThread.isAlive();
}
@Override
public void handleMessage(Message msg) {
if(null == msg){
return;
}
switch (msg.what){
case MSG_DEVICE_BIND:
mManager.handleBinding(true);
break;
case MSG_DEVICE_BIND_RETRY:
mManager.handleBinding(false);
break;
default:
break;
}
}
}

上述代碼中 設置了一個HandlerThread 用於開啟一個線程,請求的發送運行在該線程中(獲取該HandlerThread的Looper,用該Looper初始化Handler,這樣就可以將該Handler中handleMessage函數運行在開辟的線程中)。

通過Looper.quit函數結束該線程,否者該線程的Looper一直在循環等待消息的到來,占用資源(在HandlerThread已經失去價值的時候應該及時停掉它)。

 方法1: mHandlerThread.getLooper().quit();
//方法2: mHandlerThread.quit();
  方法3:mHandlerThread.getLooper().quitSafely();
//方法4:mHandlerThread.quitSafely();

1 和 2 結束的方法等同,不管消息隊列中有沒有消息都結束該線程。

3 和 4 中如果消息隊列中有消息,則不會立刻結束,待處理完消息隊列中的消息在結束該線程(處理過程中達到的消息不再處理)

 

請求管理類:

1、 請求管理類中定義自變量: mBindHandler,用於發送消息

2、設置重傳策略

3、設置防止頻繁調用的策略,(檢查消息隊列中是否有消息,如果有則丟棄該消息)

      mBindHandler.hasMessages(BindHandler.MSG_DEVICE_BIND_RETRY)

         具體代碼不再詳細分析

HandlerThread 使用詳解

/**
* 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.
*/
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
private @Nullable Handler mHandler;

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

/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public
HandlerThread(String name, int priority)
 {
super(name);
mPriority = priority;
}

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

@Override
public void run() {

mTid = Process.myTid();
Looper.prepare();

synchronized (this) {
mLooper = Looper.myLooper();
notifyAll(); //
喚醒等待線程

}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();

mTid = -1;
}

/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper
getLooper()
 {   //如果該線程沒有start 則處於等待狀態,當調用start后,notify
if (!isAlive()) {
return null;
}

// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait(); //線程沒有開啟則處於等待狀態
} catch (InterruptedException e) {
}
}
}
return mLooper;
}

/**
* @return a shared {@link Handler} associated with this thread
* @hide
*/
@NonNull
public
Handler getThreadHandler()
 {
if (mHandler == null) {
mHandler = new Handler(getLooper());
}
return mHandler;
}

/**
* Quits the handler thread's looper.
* <p>
* Causes the handler thread's looper to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*
* @see #quitSafely
*/
public boolean
quit()
 {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}

/**
* Quits the handler thread's looper safely.
* <p>
* Causes the handler thread's looper to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* Pending delayed messages with due times in the future will not be delivered.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p>
* If the thread has not been started or has finished (that is if
* {@link #getLooper} returns null), then false is returned.
* Otherwise the looper is asked to quit and true is returned.
* </p>
*
* @return True if the looper looper has been asked to quit or false if the
* thread had not yet started running.
*/
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;
}
}

 從源碼可以看出HandlerThread繼續自Thread,構造函數的傳遞參數有兩個,一個是name指的是線程的名稱,一個是priority指的是線程優先級,我們根據需要調用即可。其中成員變量mLooper就是HandlerThread自己持有的Looper對象。onLooperPrepared()該方法是一個空實現,是留給我們必要時可以去重寫的,但是注意重寫時機是在Looper循環啟動前,再看看THread的run方法.

       前面我們在HandlerThread的常規使用中分析過,在創建HandlerThread對象后必須調用其start()方法才能進行其他操作,而調用start()方法后相當於啟動了線程,也就是run方法將會被調用,而我們從run源碼中可以看出其執行了Looper.prepare()代碼,這時Looper對象將被創建,當Looper對象被創建后將綁定在當前線程(也就是當前異步線程),這樣我們才可以把Looper對象賦值給Handler對象,進而確保Handler對象中的handleMessage方法是在異步線程執行的。

synchronized (this) {

        mLooper = Looper.myLooper();

         notifyAll(); //喚醒等待線程

}

       這里在Looper對象創建后將其賦值給HandlerThread的內部變量mLooper,並通過notifyAll()方法去喚醒等待線程,最后執行Looper.loop();代碼,開啟looper循環語句。那這里為什么要喚醒等待線程呢?我們來看看,getLooper方法

  • HandlerThread本質上是一個線程類,它繼承了Thread;
  • HandlerThread有自己的內部Looper對象,可以進行looper循環;
  • 通過獲取HandlerThread的looper對象傳遞給Handler對象,可以在handleMessage方法中執行異步任務。
  • 創建HandlerThread后必須先調用HandlerThread.start()方法,Thread會先調用run方法,創建Looper對象。

參考:  http://blog.csdn.net/javazejian/article/details/52426353

 

Lopper.java 源碼分析

Looper的核心代碼:

public static void loop() {

    Looper me = myLooper();

    if (me == null) {

        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");

    }

    MessageQueue queue = me.mQueue;

    // Make sure the identity of this thread is that of the local process, and keep track of what that identity token actually is.

    Binder.clearCallingIdentity();

    final long ident = Binder.clearCallingIdentity();

while (true) {

        Message msg = queue.next(); // might block

        if (msg != null) {

            if (msg.target == null) { // target為null,結束消息,結束循環

                // No target is a magic identifier for the quit message.

                return;

        }

    }

}

private Looper() {

mQueue = new MessageQueue();

    mRun = true;

mThread = Thread.currentThread();

}

public void quit() {

    Message msg = Message.obtain();

    // NOTE: By enqueueing directly into the message queue, the message is left with a null target. This is how we know it is a quit message

// 發送一個target為null的消息,作為結束

    mQueue.enqueueMessage(msg, 0);

}

我看到里面是一個無限循環,退出循環的條件是:msg.target == null;

也就是說,如果我們向此looper的MessageQueue發送一個target為null的message,就可以停止這個線程的遠行。(查看Looper.quit代碼可以驗證這一點)。

使用Looper.prepare()、Looper.loop()為Thread添加消息隊列后,該線程便開啟了。

使用總結

1. Looper類用來為一個線程開啟一個消息循環。
    默認情況下android中新誕生的線程是沒有開啟消息循環的。(主線程除外,主線程系統會自動為其創建Looper對象,開啟消息循環。)
    Looper對象通過MessageQueue來存放消息和事件。一個線程只能有一個Looper,對應一個MessageQueue。(如果對一個已經quit的Looper重新start會出現異常)
2. 通常是通過Handler對象來與Looper進行交互的。Handler可看做是Looper的一個接口,用來向指定的Looper發送消息及定義處理方法。
    默認情況下Handler會與其被定義時所在線程的Looper綁定,比如,Handler在主線程中定義,那么它是與主線程的Looper綁定。
mainHandler = new Handler() 等價於new Handler(Looper.myLooper()).
Looper.myLooper():獲取當前進程的looper對象,類似的 Looper.getMainLooper() 用於獲取主線程的Looper對象。
3. 在非主線程中直接new Handler() 會報如下的錯誤:
E/AndroidRuntime( 6173): Uncaught handler: thread Thread-8 exiting due to uncaught exception
E/AndroidRuntime( 6173): java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
原因是非主線程中默認沒有創建Looper對象,需要先調用Looper.prepare()啟用Looper。
4. Looper.loop(); 讓Looper開始工作,從消息隊列里取消息,處理消息。
    注意:寫在Looper.loop()之后的代碼不會被執行,這個函數內部應該是一個循環,當調用mHandler.getLooper().quit()后,loop才會中止,其后的代碼才能得以運行。
5. 基於以上知識,可實現主線程給子線程(非主線程)發送消息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM