Handler機制詳解

相關類

1. ThreadLocal

image

ThreadLocal存取的變量只能被當前線程訪問,其餘線程則沒法訪問和修改。html

使用場景

數據以線程爲做用域,不一樣的線程有不一樣的數據副本。
各個線程往***同一個***ThreadLocal中填充的變量屬於當前線程,該變量對其餘線程而言是隔離的android

原理
  1. Thread中有變量ThreadLocalMap
  2. threadLocal調用set/get方法,實際是調用的各個線程的threadLocalMap.set/getEntry,並以該local實例爲索引

雖然是在不一樣線程調用的local
但由於存取的map變量是在Thread內部,而且local是同一個
因此,能實現存儲的變量屬於當前線程,對其它線程隔離數組

使用

<!--#:ThreadLocal-->
/**
* 實際上調用的是:各個線程中的threadLocalMap.set/getEntry
*/
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}
//Thead中的ThreadLocalMap是在ThreadLocal中被建立的
void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

--------------------

<!--#:Thread-->
ThreadLocal.ThreadLocalMap threadLocals = null;//在ThreadLocal中被初始化
//以local實例爲索引
private void set(ThreadLocal<?> key, Object value) {}
private Entry getEntry(ThreadLocal<?> key) {}


/**
  雖然是在不一樣線程調用的local   
  但由於存取的map變量是在Thread內部,而且local是同一個    
  因此,能實現存儲的變量屬於當前線程,對其它線程隔離
*/
ThreadLocal<String> local = new ThreadLocal<>();
//#Thread$1
local.set("strValue");
String value = local.get();
//#Thread$2
local.set("strValue");
String value = local.get();

複製代碼

2. HandlerThread

Android多線程:HandlerThread詳細使用手冊
Android多線程:一步步帶你源碼解析HandlerThread安全

原理: Handler機制 + Thread
  • 是Thread
  • 經過繼承Thread類,快速地建立1個帶有Looper對象的新工做線程
  • 經過新Handler使用該線程的Looper,在該Handler中處理任務,使工做任務在子線程中執行
HandlerThread實現
# HandlerThread:
@Override
public void run() {
    Looper.prepare();
    synchronized (this) {
        //這步纔是最主要的,讓處理耗時操做的workHandler使用mLooper,這樣就能在當前的子線程中處理workHandler的耗時操做了。
        mLooper = Looper.myLooper();
        notifyAll();
    }
    
    //空方法,loop前的準備
    onLooperPrepared();
    
    Looper.loop();
}
複製代碼
使用
protected void onCreate() {
    HandlerThread handlerThread = new HandlerThread("word_handler");
    handlerThread.start();
    //使用子線程的Looper,因此workHandler中的耗時操做在子線程中執行
    WorkHandler workHandler = new WorkHandler(handlerThread.getLooper());
    
    mainHandler = new MainHandler();
    
    Message msg = Message.obtain();
    msg.what = 1;
    workHandler.sendMessage(msg);//work發送消息
}

//和HandlerThread同線程,處理工做任務 
public class WorkHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        Message messag = Message.obtain();
        messag.obj = "from word Handler";
        mainHandler.sendMessage(messag);//通知主線程
    } 
}

class MainHandler extends Handler{
    public void handleMessage(Message msg) {
        tvTest.setText((String)msg.obj);
    }
}
複製代碼
封裝HandlerThread
protected void initData() {
    mainHandler = new MainHandler();

    myHandlerThread = new WorkHandlerThread("my_handler_thread");
    myHandlerThread.start();
}

@OnClick({R.id.btn_start})
public void onViewClicked(View view){
    Message msg = Message.obtain();
    msg.what = 1;
    myHandlerThread.getWorkHandler().sendMessage(msg);
}

/**
 * 當前線程
 */
class MainHandler extends Handler{
    @Override
    public void handleMessage(Message msg) {
        tvTest.setText((String)msg.obj);
    }
}

/**
 * 工做:處理耗時任務
 */
class WorkHandlerThread extends MyHandlerThread{
    public WorkHandlerThread(String name) {super(name);}

    @Override
    public void workMessage(Message msg) {
        Thread.sleep(2000);
        Message messag = Message.obtain();
        messag.obj = "from word My WordHandler";
        mainHandler.sendMessage(messag);
    }
}

 /**
  * 封裝基類
  */
 abstract class MyHandlerThread extends HandlerThread{
     private WorkHandler mWorkHandler;

     @Override
     protected void onLooperPrepared() {
         mWorkHandler = new WorkHandler(getLooper());
     }

     public WorkHandler getWorkHandler(){return mWorkHandler;}
     public abstract void workMessage(Message msg);

     class WorkHandler extends Handler{
         public WorkHandler(Looper looper) {
             super(looper);
         }

         @Override
         public void handleMessage(Message msg) {
             super.handleMessage(msg);
             workMessage(msg);
         }
     }

 }
複製代碼

IdleHandler

IdleHandler-乾貨集中營
你知道android的MessageQueue.IdleHandler嗎?
IdleHandler,頁面啓動優化神器bash

是 Handler 機制提供的一種,能夠在 Looper 事件循環的過程當中,當出現空閒的時候,容許咱們執行任務的一種機制多線程

Message next() {

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null) {
                    if (now < msg.when) {
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }


                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                //第一次時知足<0條件
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                //執行下面的for循環一次後,置爲0,後面再到此處再也不往下執行
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
            }

            // We only ever reach this code block during the first iteration.
            //只有第一次執行能到達
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {}

                if (!keep) {
                    synchronized (this) {
                        mIdleHandlers.remove(idler);
                    }
                }
            }

            // Reset the idle handler count to 0 so we do not run them again.
            //置爲0,後面再也不知足上面條件進入for循環
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }
複製代碼
  • 調用時機:消息隊列empty或延遲消息還未執行
  • 若是queueIdle返回true,會一直有idleHandler,爲何不會致使空閒時next在for循環的過程當中,一直重複執行
    • 在最後pendingIdleHandlerCount=0,再也不知足條件
使用場景

IdleHandler,頁面啓動優化神器app

  • 啓動頁面優化
    • onResume在wm.addView以前執行,能夠將沒必要要的操做在頁面繪製完成以後執行
    • 減小onResume中執行時間,達到頁面啓動優化

消息機制

Android Handler機制之總目錄
Message中obtain()與recycle()的前因後果-系列異步

1、概述

什麼是消息機制

Android的消息機制主要是指Handler的運行機制
即:消息的發送、入隊、出隊、分發過程。async

爲何須要消息機制

Android規定訪問UI只能在主線程中進行,在子線程中訪問UI就會拋異常。可是Android又不建議在主線程中作耗時操做,會可能致使ANR。因此,咱們須要,能在子線程中作完耗時操做,而後去到主線程更新UI的辦法。
Hander的主要做用是將一個任務切換到指定的線程中去執行。所以,系統提供Handler主要是***爲了解決在子線程中沒法訪問UI的問題***(而不是把耗時操做放到子線程中的問題)。ide

單線程模型準則

在單線程模型中始終要記住兩條法則:
一、不要阻塞UI線程
二、確保只在UI線程中訪問UI

2、原理

分發機制
分發機制:

  • Handler經過sendMessage()發送Message到MessageQueue隊列;
  • Looper經過loop(),不斷提取出達到觸發條件的Message,並將Message交給target來處理;
  • 通過dispatchMessage()後,交回給Handler的handleMessage()或者runnable來進行相應地處理。

2.1 消息的發送、入隊:

handler中有衆多的send方法,時間點的區別而已,到最後都會調用下面的方法,把message放入消息隊列
MessageQueue是鏈表結構

//消息發送
<!--#Handler-->
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    return enqueueMessage(queue, msg, uptimeMillis);
}

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        //注意此處,this指的是handler本身
        msg.target = this;
        //把消息插入隊列
        return queue.enqueueMessage(msg, uptimeMillis);
    }

//入隊
<!--#MessageQueue-->
boolean enqueueMessage(Message msg, long when) {
    synchronized (this) {
        msg.markInUse();
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        //若是隊列爲空或者時間最小,插入到頭部
        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;
            //循環隊列,根據時間找到合適的位置插入
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
} 
複製代碼

2.2 消息的出隊、分發:

loop將隊列中的msg一個一個取出,分發到各自的handler中處理。
handler根據是否有callback選擇不一樣的分發方式。

//出隊
<!--#Looper-->
public static void loop() {
    final Looper me = myLooper();
    final MessageQueue queue = me.mQueue;
    //真正決定阻塞的是queue.next()中的for循環
    for (;;) {
        //next方法會阻塞線程,有消息就取出,沒消息就等待。退出返回null結束當前循環。
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            //Looper.quit退出循環
            return;
        }
        //加入消息隊列時,target == handler,分發的時候就能夠找到原來的handler,讓其本身處理。
        //因此,一個線程多個handler發送消息,雖然都在一個隊列裏,可是仍是會分發到原來的handler處理消息。
        msg.target.dispatchMessage(msg);
    }
}

//取出消息
<!--#MessageQueue-->
Message next() {
    //阻塞線程
    for (;;) {
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                //異步消息?
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                //還不到取出時間
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 取出msg
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
            
            //Looper.quit(),返回null。
            //此時,Looper中的for循環收到null也return,退出循環
            if (mQuitting) {
                dispose();
                return null;
            }
        }
    }    
}

    
//由target返回到Handler中執行分發
//加入隊列的:有的是Runnable封裝的message,有的是callback,有的是message,在這裏分發   
<!--#Handler-->
public void dispatchMessage(Message msg) {
    //runnable封裝的message。 handler.post/view.post
    if (msg.callback != null) {//msg中的callback 優先級1
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            //Handler(Callback callback)的時候,mCallback !=null
            if (mCallback.handleMessage(msg)) {//handler中的callback,優先級2
                return;
            }
        }
        //正常的message那種形式,也便是new Handler時複寫的方法。
        handleMessage(msg);// 優先級3
    }
}
複製代碼

3、其它過程

3.1 子線程中初始化Handler

<!--#Looper-->
private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));
}

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

<!--#Handler-->
public Handler(Callback callback, boolean async) {
    //驗證當前線程有沒有Looper對象,因此new以前要prepare存入
    mLooper = Looper.myLooper();
    if (mLooper == null) {
        throw new RuntimeException(
            "Can't create handler inside thread " + Thread.currentThread()
                    + " that has not called Looper.prepare()");
    }
}
    
<!--#:Thread1-->
Looper.prepare()//當前線程存
Handler handler = new Handler();//當前線程取
Looper.loop();

<!--#:Thread2-->
Looper.prepare()//當前線程存
Handler handler = new Handler();//當前線程取
Looper.loop();

<!--#:Thread3-->
Looper.prepare()//當前線程存
Handler handler = new Handler();//當前線程取
Looper.loop();
複製代碼
主線程 Handler

爲何主線程 new Handler以前不用prepare,也不用loop?

UI線程是主線程,系統已經自動幫咱們調用了Looper.prepare()和Looper.loop()方法
複製代碼

3.2 優先級:依次下降。

優先級1:

<!--封裝成Message,放入隊列-->
//view.post(runnable)和handler.post(runnable)     
//最後runnable都會分裝成Message。分發的時候,在第一處調用。
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
}
    
<!--分發調用-->
private static void handleCallback(Message message) {
    message.callback.run();
}

注意:雖然是runnable,但並非新的線程。
此處的run()就是正常的方法,Runnable中不要作耗時操做。
複製代碼

優先級2:

public Handler(Callback callback, boolean async) {
    ...
    mCallback = callback;
    ...
}
複製代碼

優先級3:
經常使用的發送Message方式,設置what,obj參數,發送的消息。

3.3 退出

  • loop.quit() --> mQueue.quit(false)
  • loop.quitSafely() --> mQueue.quit(true)
<!--#MessageQueue-->
void quit(boolean safe) {
    //主線程時,quit報錯
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }

    synchronized (this) {
        //mQuitting該標記使next方法中返回null,使loop結束循環。
        mQuitting = true;

        if (safe) {
            removeAllFutureMessagesLocked();
        } else {
            removeAllMessagesLocked();
        }
    }
}

Message next() {
    //MessageQueue的next返回null,就能結束loop循環了
    if (mQuitting) {
         dispose();
         return null;
    }
}

public static void loop() {
    final MessageQueue queue = me.mQueue;
    for (;;) {
        Message msg = queue.next();
        /*
        * 不是指消息隊列裏沒有消息了(沒有消息,隊列也會一直循環)
        * 是調用了Loop的quit()、quitSafely()方法,結束循環
        */
        if (msg == null) {
            //退出循環
            return;
        }
        ...
    }
}
複製代碼
安全退出
<!--#MessageQueue-->
void quit(boolean safe) {
    if (safe) {
        removeAllFutureMessagesLocked();
    } else {
        removeAllMessagesLocked();
    }
}

//回收全部消息
private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

//回收延遲消息
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;
                }
                //找到 <= now的msg ==> p
                p = n;
            }
            //把p之後的消息回收
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
}
複製代碼

quit()方法的做用:
1_標記mQuitting = true,使next方法返回null,結束循環。
2_移除消息隊列中的消息。

3.4 主線程quit報錯:

源碼中能夠看到,子線程中preaper時候,默認設置爲true。
主線程中:prepareMainLooper的時候,默認是false。
因此,子線程的Loop是能夠quit的,而主線程的不能夠。

//app入口 ActivityThread
public static void main(String[] args) {  
    ...
    Looper.prepareMainLooper();  
    ActivityThread thread = new ActivityThread();  
    ...
    Looper.loop();  
}

public static void prepareMainLooper() {
    prepare(false);
    ...
}
//初始化MessageQueue時設置
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

void quit(boolean safe) {
    //這個地方:主線程時,quit報錯
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    ...
}
複製代碼

3.5 runOnUiThread

public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            //非UI線程,到主線程的handler執行
            mHandler.post(action);
        } else {
            //UI線程直接run
            action.run();
        }
    }
複製代碼

3.6 內存泄露

Handler內部類引用Activity,msg.target = this引用handler,形成內存泄漏

  • 1.退出時,remove掉handler(onStop中移除,onDestory不必定會調用)
  • 2.靜態內部類,弱引用
/**
 爲避免handler形成的內存泄漏
 一、使用靜態的handler,對外部類不保持對象的引用
 二、但Handler須要與Activity通訊,因此須要增長一個對Activity的弱引用
*/
private static class MyHandler extends Handler {
  private final WeakReference<Activity> mActivityReference;    

  MyHandler(Activity activity) {
      this.mActivityReference = new WeakReference<Activity>(activity);
  }

  @Override
  public void handleMessage(Message msg) {
  super.handleMessage(msg);
  MainActivity activity = (MainActivity) mActivityReference.get();  //獲取弱引用隊列中的activity
  //若引用,判斷null
  if(activity != null && !activity.isFinish()){
      byte[] data = (byte[]) msg.obj;
      activity.threadIv.setImageBitmap(activity.getBitmap(data));
      break;   
  }
}
複製代碼

3.7 loop()爲何不會阻塞主線程形成ANR?

Android中爲何主線程不會由於Looper.loop()裏的死循環卡死

  • 首先,loop()會致使線程阻塞
class LooperThread extends Thread { 
    public Handler mHandler; 
    public void run() { 
       Looper.prepare(); 
       mHandler = new Handler();
       Looper.loop(); 
       Log.d("loop()是死循環,阻塞線程,後面的代碼不執行");
    } 
} 
複製代碼
  • 其次,主線程確實是阻塞的
    沒有loop的話,程序啓動,執行完代碼就結束APP就退出了
#ActivityThread:
public static void main(String[] args) {  
    ...
    Looper.prepareMainLooper();  
    #1
    ActivityThread thread = new ActivityThread(); 
    #2
    thread.attach(false);  
    if (sMainThreadHandler == null) {  
        #3
        sMainThreadHandler = thread.getHandler();  
    }  
    #4
    Looper.loop();  
    throw new RuntimeException("Main thread loop unexpectedly exited");  
}  
複製代碼
  • 不會由於loop引發ANR
    由於在loop處理消息的過程當中,進行耗時操做纔會引發ANR。

Android的各個生命週期和事件,都是在loop循環中接收、處理消息的。引發ANR是由於處理消息時阻塞了,而不是由於loop循環阻塞的。

主線程的死循環一直運行是否是特別消耗CPU資源呢?

其實否則,這裏就涉及到Linux pipe/epoll機制;
簡單說就是在主線程的MessageQueue沒有消息時,便阻塞在loop的queue.next()中的nativePollOnce()方法裏,詳情見Android消息機制1-Handler(Java層),此時主線程會釋放CPU資源進入休眠狀態,直到下個消息到達或者有事務發生,經過往pipe管道寫端寫入數據來喚醒主線程工做。
這裏採用的epoll機制,是一種IO多路複用機制,能夠同時監控多個描述符,當某個描述符就緒(讀或寫就緒),則馬上通知相應程序進行讀或寫操做,本質同步I/O,即讀寫是阻塞的。
因此說,主線程大多數時候都是處於休眠狀態,並不會消耗大量CPU資源。

3.8 Handler的異步理解

  • Handler沒有處理耗時操做的能力
  • Handler的異步只是體如今,在其它線程操做完之後發送msg,在handler線程處理收到消息後的任務。

3.9 View.post(runnable)

#HandlerActionQueue:
public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        //若是當前View加入到了window中,直接調用UI線程的Handler發送消息
        return attachInfo.mHandler.post(action);
    }

    //View未加入到window,放入HandlerActionQueue的mActions(數組)中
    getRunQueue().post(action);
    return true;
}

#View:
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    //View加入window後,直接執行mActions保存的
    mRunQueue.executeActions(info.mHandler);
}
複製代碼
  • View加入window以前post:
  • View加入window以後post:直接用主線程handler發送消息

3.10 子線程必定不能更新UI嗎?

Android子線程更新UI就會Crash麼

  • 有些控件支持在子線程更新,好比:SurfaceViw * 在Activity的onResume()生命週期函數以前是能夠在子線程中更新UI的
    • ViewRootImpl還未建立
    • 更新時不會checkThread

3.11 Android實現異步的方式

  • 繼承Thread類
  • HandlerThread
  • IntentService
  • AsyncTask
  • 線程池

注意事項

  • 主線程建立Handler不用prepare,是由於系統建立主線程時已調用
  • Handler沒有處理耗時操做的能力,只是能在處理之後通知主線程。
相關文章
相關標籤/搜索