死磕Handler

handler做爲Android開發最中重要的模型之一,須要理解其工做原理包括Handler,Message,MessageQueue,Looper之間的關係,以下是一個網上摘抄的圖片,MessageQueue是一個消息隊列,Looper是一個循環體,Message是消息,而Handler是消息的具柄; java

下面一步一步用代碼來看:linux

Message

Message中有4個變量參數,what ,arg1,arg2,obj;常常使用其中的參數做爲判斷handleMessage的判斷條件,其中通常有以下3種方式建立Message: (1)經過Message的構造器模式建立:緩存

Message msg = new Message();
msg.arg1 = 1;
msg.arg2 = 2;
msg.obj = 3;
msg.what = 4;
handle.sendMessage(msg);
複製代碼

(2)經過Message的obtain函數去操做:bash

Message msg = Message.obtain();
msg.what=1;
msg.arg1=2;
msg.arg2=3;
msg.obj=4;
handler.sendMessage(msg);
複製代碼

(3)經過Obtain(handler)方式去建立 :多線程

Message msg = Message.obtain(handler);
msg.what=1; msg.arg1=2;
msg.arg2=3; msg.obj=4;
msg.sendToTarget();
複製代碼

推薦使用後面2種的obtain方式去建立,由於這種效率會高一些,減小了重複建立Message對象; 看源碼:Message#obtain()併發

public static 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;
            }
        }
        return new Message();
    }
複製代碼

發現其實經過obtain是優先從緩存的Message池中去取,當取不到時候纔會建立一個新的Message,所以效率更高,推薦使用;less

Handler

常見的建立Handler方式以下:異步

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
        }
    };
}
複製代碼

此中方式建立Handler時候會形成內存泄露,緣由是由於非靜態的匿名內部類和非靜態內部類是能夠持有一個外部類的引用的;由於在Java.class編譯時候會將非靜態的內部類和外部類編譯成2個文件存儲,非靜態的內部類能夠訪問外部類的成員變量和成員方法這個是爲何呢?就是由於非靜態的內部類是持有外部類的一個隱式引用,而靜態內部類和外部類之間沒有,兩個幾乎至關於兩個獨立的Class文件;編譯後的代碼應該差很少以下:async

class Outer$Inner{
	final Outer this$0;
	
	public Outer$Inner(Outer outer){
		this.this$0 = outer;
		super();
	}
}
複製代碼

至於這個引用爲何會形成內存泄露呢?先說下根本緣由吧,是由於handler持用Activity的引用,而Handler中若是消息沒有處理完,Activity在銷燬時候是沒有辦法被GC回收的,GC回收是根據對象可達性,這個Handler是綁定在主線程,主線程是應用入口,當Handler處理不完此消息必然形成Activity沒法回收,最終內存泄露;以下這種會形成5分鐘沒法回收Activity:ide

public class HandlerActivity extends Activity{
 
	//可能引入泄露的方法
	private final Handler mLeakyHandler = new Handler(){
	
		@Override
		public void handleMessage(Message msg){
		
			//.....
		
		}
	};
	
	@Override
	protected void onCreate(Bundle savedInstanceState){
		super.onCreate(savedInstanceState);
		
		//延遲5分鐘發送消息
		mLeakyHandler.postDelayed(new Runnable(){
		
			@Override
			public void run(){
				//......
			}
			
		}, 1000*60*5);
		
	
	}
}
複製代碼

知道緣由解決方法也跟着出來了: 1.使用靜態的內部類替代非靜態內部類; 2.在onDestory方法時移除Handler中的消息也能夠解決;

private static class InnerHandler extends Handler{
		
		private final WeakReference<HandlerActivity> mActivity;
		
		public InnerHandler(HandlerActivity activity){
			mActivity = new WeakReference<HandlerActivity>(activity);
		}
		
		@Override
		public void handleMessage(Message msg){
			HandlerActivity activity = mActivity.get();
			if(activity != null){
				//.....
			}
		}
	}
複製代碼
//清空當前Handler隊列全部消息
 @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }
複製代碼

來看下handler的構造方法:

public Handler() {
        this(null, false);
    }
    
public Handler(Looper looper) {
        this(looper, null, false);
    }
    
 public Handler(Callback callback) {
        this(callback, false);
    }
    
public Handler(boolean async) {
        this(null, async);
    }
複製代碼

代碼最多見的是使用1,2兩種方式去建立handler,若是handler在主線程建立,好比不是在new Thread的線程內建立的話,那麼handler的Looper綁定的是主線程的Looper,在子線程中使用時候須要本身Looper.loop<->Looper.prepare配套; public Handler(Callback callback)能夠用戶攔截回調: 舉個例子:

public class MainActivity extends Activity {
	private TextView textView;
	private Handler handler = new Handler(new Handler.Callback() {
		
		@Override
		public boolean handleMessage(Message msg) {
			Toast.makeText(getApplicationContext(), "callback handlemessage", 1000).show();    //代碼1
			return true;   //這裏返回值須要注意 // 代碼3
		}
	}){
		public void handleMessage(Message msg) {
			Toast.makeText(getApplicationContext(), "handler handlemessage", 1000).show();  //代碼2
		};
	};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		textView =(TextView) findViewById(R.id.textView);
	}
	
	public void show(View view){
		handler.sendEmptyMessage(1);
	}
	
}
複製代碼

上面的代碼中使用了public Handler(Callback callback)的構造函數。咱們這裏能夠進行消息傳遞的攔截。當咱們的「代碼3」中return false的時候 「代碼1」 「代碼2」會依次執行,當「代碼3」中return true的時候「代碼1」會先執行 ,可是「代碼2」不會執行,此時有點相似事件傳遞中返回true事件消費,false繼續向上傳遞的意思。咱們可使用Handler的這個構造方法,來進行消息傳遞的攔截。 具體實現源碼Handler#dispatchMessage以下:

/** * Handle system messages here. */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                //若是這裏返回true,那麼handleMessage是不會執行了;
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
複製代碼

還有一個public Handler(boolean async)設置異步的,對於API沒開放,設置後會影響消息是否爲異步消息,不能保證插入的消息有序性; 源碼以下:

public Handler(Callback callback, boolean async) {
        ...
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
  private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    
    
     /** * Returns true if the message is asynchronous, meaning that it is not * subject to {@link Looper} synchronization barriers. * * @return True if the message is asynchronous. * * @see #setAsynchronous(boolean) */
    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }
複製代碼

下面說常見的幾種handler發消息的方法:

public final boolean post(Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
public final boolean postAtTime(Runnable r, long uptimeMillis) {
        return sendMessageAtTime(getPostMessage(r), uptimeMillis);
    }  
    
public final boolean postDelayed(Runnable r, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }
 
 public final boolean postAtFrontOfQueue(Runnable r) {
        return sendMessageAtFrontOfQueue(getPostMessage(r));
    }
    
  public final boolean sendMessageDelayed(Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }  
    
 public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
    
 public final boolean sendMessageAtFrontOfQueue(Message msg) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, 0);
    }
    
   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }   
複製代碼

能夠看出來 postxxx-->sendxxx-->enqueueMessage將消息放入到消息隊列裏面

MessageQueue

消息隊列,消息隊列的實現就是一個隊列從裏面取消息,都知道Loop會讓循環不斷的去取消息隊列裏面消息,實現方式也就是一個死循環,可是光死循環會形成cpu負荷,所以須要休眠控制下減小cpu,那消息隊列如何控制休眠的呢;其實就是經過linux的epoll機制完成的,C++類Looper中的睡眠和喚醒機制是經過pollOnce和wake函數提供的,它們又是利用操做系統(Linux內核)的epoll機制來完成的,這裏深刻很少展開,只從一個地方看mBlocked:

// Indicates whether next() is blocked waiting in pollOnce()     //with a non-zero timeout.
    private boolean mBlocked;
複製代碼

mBlocked這個標識位控制着是否休眠喚醒;

先看喚醒時機enqueueMessage:

boolean enqueueMessage(Message msg, long when) {
        ...
            //這裏處理當消息經過needWake控制是否進入休眠
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                // Inserted within the middle of the queue. Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            //喚醒操做
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }
複製代碼

休眠時機 Message next():

Message next() {
            ...
            //使用pollOnce取消息時候會休眠
            nativePollOnce(ptr, nextPollTimeoutMillis);
            ...
            if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready. Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        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;
                    }
                }
                  if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }
        }

複製代碼

Looper

一個循環處理消息隊列的東東; 使用方式也簡單,以下是將當前Handler和Looper關聯,若是在主線程,關聯是主線程,若是是子線程管理就子線程;

//子線程
        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();
        
        //子線程
        Looper looper = .....;
        //主線程
        Handler handler = new Handler(looper);
複製代碼

子線程建立Handler通常使用HandlerThread方式:

public class HandlerThreadActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
        HandlerThread handlerThread = new HandlerThread("HandlerThread");
        handlerThread.start();

        Handler mHandler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
                Log.d("HandlerThreadActivity.class","uiThread2------"+Thread.currentThread());//子線程
            }
        };

        Log.d("HandlerThreadActivity.class","uiThread1------"+Thread.currentThread());//主線程
        mHandler.sendEmptyMessage(1);
    }
}
複製代碼

看看源碼:

public static void prepare() {
        prepare(true);
    }

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));
    }

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
複製代碼

Looper對象實例化就是在Looper.prepare完成的,而且將當前線程綁定到mThread上,如今回過頭看Handler建立時候就明白爲何沒有單獨開線程建立時候,綁定是主線程上;來看Handler建立方法:

public Handler(Callback callback, boolean async) {
        ...
        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }
    
public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
    
      static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
複製代碼

到這裏仍是沒有直觀的找到綁定的線程;可是咱們忽略了一個ActivityThread,ActivityThread是Android程序的入口,一直運行,看下其main函數:

public static void main(String[] args) {
        ...
        Looper.prepareMainLooper();
        ...
        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();
}


public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
            // MIUI ADD
            sMainLooper.enableAnrMonitor(true);
        }
    }
複製代碼

Handler,Looper在程序啓動的時候就已經開始工做了,也就是說咱們本身的ThreadLocal.get()獲取的是ActivityThread線程即主線程; 在看Looper.loop:

public static void loop() {
        ...
        boolean slowDeliveryDetected = false;
        //死循環,不斷的取消息隊列中消息
        for (;;) {
            //消息隊列處理消息時候由於nativePollOnce阻塞休眠
            Message msg = queue.next(); // might block
            //無消息時候退出
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
            
             try {
                //處理消息
                msg.target.dispatchMessage(msg);
                dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
            } finally {
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            ...
        }
    }
    
    
    public void dispatchMessage(Message msg) {
        //若是是post方式傳入一個runnable執行便可
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            //若是是public Handler(Callback callback)執行此處
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                //事件分發,是否本身消費掉
                    return;
                }
            }
            handleMessage(msg);
        }
    }
    
    private static void handleCallback(Message message) {
        message.callback.run();
    }
    
複製代碼

這裏更加明白了在ondestroy時候removeCallbacksAndMessages的含義了, 就是移除添加的postxxx,sendMessagexxx等消息; 至此整個Looper,Message,Handler,MessageQueue到此應該梳理比較清楚了;

還有一個類:ThreadLocal,在源碼的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();
    }
    
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();  
 
複製代碼

ThreadLocal是一個本地線程存放副本變量的工具,不一樣線程其變量值互不干擾,適用於多線程高併發場景完成多線程調用互不干擾的變量的值,能夠相似理解爲多進程操做同一個靜態變量,其值也是互不干擾;

看看ThreadLocal代碼:

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
 public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
複製代碼

實現原理就是set時候先從ThreadLocalMap去找,取不到再存放,get也是相似原理,看源碼就明白了;

相關文章
相關標籤/搜索