【Handler】Looper 原理 詳解 示例 總結


核心知識點

1、相關名詞
  • UI線程:就是我們的主線程,系統在創建UI線程的時候會初始化一個Looper對象,同時也會創建一個與其關聯的MessageQueue
  • Handler:作用就是發送與處理信息,如果希望Handler正常工作,在當前線程中必須要有一個Looper對象
  • Message:Handler接收與處理的對象。Handler也能接收與處理Runnable對象
  • MessageQueue:消息隊列,先進先出管理Message,在初始化Looper對象時會創建一個與之關聯的MessageQueue
  • Looper:每個線程只能夠有一個Looper,Looper負責創建並管理當前線程中的MessageQueue,調用loop方法后就會在一個無限循環體中不斷地從MessageQueue中取出Message並分發給對應的Handler,最后回調handleMessage()方法處理此消息。Looper才是整個機制的核心!

2、基本使用過程:
  • 在主線程中創建Handler並重寫handleMessage()方法
  • 在任何線程中都可以利用此Handler發送消息,消息會被發送到主線程的MessageQueue中
  • 一旦MessageQueue中有新消息,主線程中的 Looper 就會發現此消息,然后就會調用Handler的handleMessage()方法處理此消息

3、一些細節:
  • 調用Looper.prepare()后首先會在本線程中保存唯一的一個Looper實例,然后會在該實例中創建並保存一個MessageQueue對象;Looper.prepare()在一個線程中只能調用一次,MessageQueue在一個線程中也只存在一個
  • 調用Looper.loop()方法后會讓當前線程進入一個無限循環中,Looper不斷從MessageQueue中讀取消息,然后回調msg.target.dispatchMessage(msg)方法。
  • Handler的構造方法中,會首先得到當前線程中保存的Looper實例,進而與Looper實例中的MessageQueue相關聯
  • Handler的sendMessage方法,會給msg.target賦值為handler自身,然后加入MessageQueue中。
  • 在構造Handler實例時,我們會重寫handleMessage方法,也就是msg.target.dispatchMessage(msg)最終調用的方法。

Handler面試點

1、Handler的產生背景
Handler是線程間通訊的機制,Android中,網絡訪問、文件處理等耗時操作必須放到子線程中去執行,否則將會造成ANR異常。
ANR異常:Application Not Response 應用程序無響應
產生ANR異常的原因:在主線程執行了耗時操作,對Activity來說,主線程阻塞5秒將造成ANR異常,對BroadcastReceiver來說,主線程阻塞10秒將會造成ANR異常。
解決ANR異常的方法:耗時操作都在子線程中去執行
但是,Android不允許在子線程去修改UI,可我們又有在子線程去修改UI的需求,因此需要借助Handler(但是,一定要明白, Handler的作用不是為了在子線程去修改UI,而是為了實現線程間通訊!一定要明白理念與表現的區別)。

2、Handler機制相關概念
  • Looper:一個線程可以產生一個Looper對象,由它來管理此線程里的MessageQueue(消息隊列)。
  • Handler:你可以構造Handler對象來與Looper溝通,以便push新消息到MessageQueue里;或者接收Looper從Message Queue取出發送來的消息。
  • Message Queue(消息隊列):用來存放線程放入的消息。
  • 線程:UIthread 通常就是main thread,而Android啟動程序時會替它建立一個MessageQueue。

3、基本過程:
UI線程創建時,就創建了一個Looper,Looper內部維護這一個MessageQueue。
Looper通過開啟一個while(true)死循環來輪詢MessageQueue中的Message。
當Looper輪詢到Message時,就分發此Message。

4、Looper和Handler之間的關聯
Handler在子線程發送消息到MessageQueue,Message被Looper取出來后,分發給handler的handleMessage方法來處理。
那么,為什么說一個Looper可以對應多個Handler,Looper如何保證哪個Handler發出去的Message將交由哪個handler來處理?
因為Handler在發送Message的時候,在Message的成員變量target上標記了當前handler的引用:
message.target = this;//this即這個發送此Message的handler對象
當Looper取到message時,通過下面的方法分發Message:
message.target.dispatchMessage(message);  //message.target即發送此Message的handler對象
因此,哪個handler發送的Message,將由哪個Handler來處理此Message。


Looper詳解

prepare()方法

public static final void prepare() {
        if (sThreadLocal.get() != null) throw new RuntimeException("Only one Looper may be created per thread");
        sThreadLocal.set(new Looper(true));
}

ThreadLocal可以在一個線程中存儲變量,可以看到,我們將一個Looper的實例放入了ThreadLocal,並且判斷了sThreadLocal是否存儲過Looper對象,存儲過(即.get()不為null)則拋出異常。這也就說明了Looper.prepare()方法不能被調用兩次,同時也保證了一個線程中只有一個Looper實例

下面看其構造方法:
private Looper(boolean quitAllowed) {
	mQueue = new MessageQueue(quitAllowed);
	mRun = true;
	mThread = Thread.currentThread();
}
在構造方法中,創建了一個消息隊列MessageQueue。

loop()方法

public static void loop() {
	final Looper me = myLooper();//方法體為 return sThreadLocal.get();  即此方法直接返回了sThreadLocal存儲的Looper實例
	if (me == null) throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");//如果me為null則拋出異常,也就是說looper方法必須在prepar()之后運行。
	final MessageQueue queue = me.mQueue;//拿到該looper實例中的消息隊列
	Binder.clearCallingIdentity(); // Make sure the identity of this thread is that of the local process, and keep track of what that identity token actually is.  
	final long ident = Binder.clearCallingIdentity();
	//無限循環
	for (;;) {
		Message msg = queue.next(); // might block,取出一條消息,如果沒有消息則阻塞等待
		if (msg == null) return;
		Printer logging = me.mLogging; // This must be in a local variable, in case a UI event sets the logger  
		if (logging != null) logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what);
		msg.target.dispatchMessage(msg);//Msg的target其實就是handler對象
		if (logging != null) logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
		final long newIdent = Binder.clearCallingIdentity();// Make sure that during the course of dispatching the identity of the thread wasn't corrupted.  
		if (ident != newIdent) {
			Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent)
					+ " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what);
		}
		msg.recycle();//釋放消息占據的資源
	}
}
Looper主要作用:
  • 1、與當前線程綁定,保證一個線程只會有一個Looper實例,同時一個Looper實例也只有一個MessageQueue。
  • 2、執行loop()方法,不斷從MessageQueue中取消息,交給消息的target屬性的dispatchMessage方法去處理。

大家可能還會問,在Activity中,我們並沒有顯示的調用Looper.prepare()和Looper.loop()方法,為啥Handler可以成功創建呢?
這是因為在Activity的啟動代碼中,已經默默的在當前UI線程中調用了Looper.prepare()和Looper.loop()方法。
而如果我們要想在子線程中創建Handler,則必須先調用Looper.prepare()方法,Handler創建后再調用Looper.loop()方法。

Handler詳解

使用Handler之前,我們都是初始化一個實例,我們可以在聲明的時候直接初始化,或者在onCreate中初始化。
我們首先看Handler的構造方法,看其如何與MessageQueue聯系上的,它在子線程中發送的消息(當然可以在任何線程)是怎么發送到MessageQueue中的。
public Handler() {//我們一般都是使用此無參的構造方法
	this(null, false);
}
public Handler(Callback callback, boolean async) {
	if (FIND_POTENTIAL_LEAKS) {
		final Class<? extends Handler> klass = getClass();
		if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) {
			Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName());
		}
	}
	mLooper = Looper.myLooper();//獲取當前線程保存的Looper實例
	if (mLooper == null) throw new RuntimeException("Can't create handler inside thread that has not called Looper.prepare()");//在初始化Handler之前必須先通過Looper.prepare()方法創建Looper的實例
	mQueue = mLooper.mQueue;//獲取這個Looper實例中保存的MessageQueue(消息隊列)
	mCallback = callback;
	mAsynchronous = async;
}
然后看我們最常用的發送消息相關的幾個方法
public final boolean sendMessage(Message msg) {
	return sendMessageDelayed(msg, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
	Message msg = Message.obtain();
	msg.what = what;
	return sendMessageDelayed(msg, delayMillis);
}

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);
}
輾轉反側最后都是調用了sendMessageAtTime方法,在此方法內部又直接獲取MessageQueue,然后調用了enqueueMessage方法
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
	msg.target = this;
	if (mAsynchronous) msg.setAsynchronous(true);
	return queue.enqueueMessage(msg, uptimeMillis);
}
enqueueMessage中首先為meg.target賦值為this,最后調用queue的enqueueMessage的方法,也就是說handler發出的消息,最終會保存到消息隊列中去。【上面我們說了,Looper的loop方法會取出每個msg然后交給msg.arget.dispatchMessage(msg)去處理消息】,也就是說Looper的loop方法會把取出的每個msg通過當前的handler的dispatchMessage回調處理

下面我們去看一看handler中的這個dispathMessage方法
public void dispatchMessage(Message msg) {
	if (msg.callback != null) handleCallback(msg);
	else {
		if (mCallback != null) {
			if (mCallback.handleMessage(msg)) return;
		}
		handleMessage(msg);
	}
}

可以看到,最后調用了handleMessage方法,可實際上這是一個空方法,為什么呢?因為消息的最終回調是由我們控制的,我們在創建handler的時候都是復寫handleMessage方法,然后根據msg.what進行消息處理。


Handler的全部方法

  
  
  
          
構造方法

其他方法

獲取消息

移除消息和回調

發送消息

    
    
    
            
post方法

有時候為了方便,我們會直接寫如下代碼:

mHandler.post(new Runnable() {
	@Override
	public void run() {
		mTxt.setText("yoxi");//在run方法中可以更新UI
	}
});  
其實這個Runnable並沒有創建什么線程,而是發送了一條消息,下面看源碼:
public final boolean post(Runnable r) {
	return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
	Message m = Message.obtain();//得到了一個Message對象
	m.callback = r;//將我們創建的Runable對象作為callback屬性,賦值給了此Message
	return m;
}  
后面的步驟就和handler.sendMessage一樣了
可以看到,這里msg的callback和target都有值,那么會執行哪個呢?
其實上面已經貼過代碼,就是dispatchMessage方法:如果msg.callback不為null,則執行callback回調,也就是我們定義的Runnable。

演示代碼

public class HandlerTestActivity extends ListActivity {
	private TextView tv_info;
	private Handler uiHandler;
	private StaticThread thread;//一個子線程
	
	public static final int MSG_WHAT_1 = 1;
	public static final int MSG_WHAT_2 = 2;
	public static final int MSG_WHAT_3 = 3;
	
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		String[] array = {"開啟子線程,並在子線程中創建一個Handler", //
				"在主線程中,通過子線程的Handler[向子線程]發消息", //
				"演示Handler的post方法"};
		tv_info = new TextView(this);
		tv_info.setText("Handler、Looper、Message、MQ、Thread關系");
		getListView().addFooterView(tv_info);
		setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new ArrayList<String>(Arrays.asList(array))));
		
		uiHandler = new StaticUiHandler(this); //系統啟動時已經為主線程初始化了Looper、MQ等,我們可以直接創建Handler
		thread = new StaticThread(this);
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		if (uiHandler != null) uiHandler.removeCallbacksAndMessages(null);
		if (thread != null && thread.getAnsyHandler() != null) thread.getAnsyHandler().removeCallbacksAndMessages(null);
	}
	
	@Override
	protected void onListItemClick(ListView l, View v, int position, long id) {
		switch (position) {
			case 0://開啟子線程,並在子線程中創建一個Handler
				if (thread != null && !thread.isAlive()) thread.start();//A thread is alive if it has been started and has not yet died.
				break;
			case 1://在主線程中,通過子線程的Handler[向子線程]發消息
				Message msg = Message.obtain(null, MSG_WHAT_1, "消息內容"); //第一個參數Handler的作用是指定msg.target
				//這里設為null的原因是:后面調用sendMessage方法時重新指定了發送此消息的Handler為msg.target
				if (thread != null && thread.getAnsyHandler() != null) thread.getAnsyHandler().sendMessage(msg);
				tv_info.append("\n1、在UI線程中用子線程的Handler發消息,what=" + msg.what);
				break;
			case 2:
				//其實這個Runnable並沒有創建什么線程,而是發送了一條消息,當Handler收到此消息后回調run()方法
				uiHandler.post(() -> tv_info.append("\n演示Handler的post方法"));
				break;
		}
	}
	//***********************************************靜態內部類,防止內存泄漏*******************************************
	
	/**
	 * 主線程使用的Handler
	 */
	private static class StaticUiHandler extends Handler {
		private SoftReference<HandlerTestActivity> mSoftReference;
		
		public StaticUiHandler(HandlerTestActivity activity) {
			mSoftReference = new SoftReference<>(activity);
		}
		
		@Override
		public void handleMessage(Message msg) {
			HandlerTestActivity activity = mSoftReference.get();
			if (activity != null && activity.thread != null && activity.thread.getAnsyHandler() != null) {
				activity.tv_info.append("\n4、UI線程的Handler收到消息,what=" + msg.what);
				Message msg3 = Message.obtain(null, MSG_WHAT_3, msg.obj);
				activity.thread.getAnsyHandler().sendMessageAtTime(msg3, SystemClock.uptimeMillis() + 2000);
				activity.tv_info.append("\n5、在UI線程中用子線程的Handler發消息,what=" + msg3.what);
			}
		}
	}
	
	/**
	 * 異步線程(子線程)使用的Handler
	 */
	private static class StaticAnsyHandler extends Handler {
		private SoftReference<HandlerTestActivity> mSoftReference;
		
		public StaticAnsyHandler(HandlerTestActivity activity) {
			mSoftReference = new SoftReference<>(activity);
		}
		
		@Override
		public void handleMessage(Message msg) {
			HandlerTestActivity activity = mSoftReference.get();
			
			if (activity != null) {
				final Message tempMsg = Message.obtain(msg);//把收到的消息保存起來
				//注意,一定要注意!根據消息池機制,當此消息不再在【此子線程】中使用時,此msg會立即被重置(引用雖在,內容為空)
				//所以,如果想把此消息轉發到其他線程,或者想在其他線程中引用此消息,一定要手動把消息保存起來!
				
				activity.runOnUiThread(() -> {//在子線程中創建Handler的目的是為了和其他線程通訊,絕對不是(也不能)更新UI
					activity.tv_info.append("\n2、子線程的Handler收到消息,what=" + tempMsg.what);
					
					if (activity.uiHandler != null && tempMsg.what == MSG_WHAT_1) {
						Message msg2 = Message.obtain(null, MSG_WHAT_2, tempMsg.obj);
						activity.uiHandler.sendMessageDelayed(msg2, 2000);
						//注意,不能直接把一條還在使用的消息轉發出去,否則IllegalStateException: This message is already in use
						activity.tv_info.append("\n3、在子線程中用UI線程的Handler發消息,what=" + msg2.what);
					}
				});
			}
		}
	}
	
	/**
	 * 一個線程,用於執行耗時的操作
	 */
	private static class StaticThread extends Thread {
		private SoftReference<HandlerTestActivity> mSoftReference;
		
		public StaticThread(HandlerTestActivity activity) {
			mSoftReference = new SoftReference<>(activity);
		}
		
		private Handler ansyHandler;
		
		public Handler getAnsyHandler() {
			return ansyHandler;
		}
		
		public void run() {
			HandlerTestActivity activity = mSoftReference.get();
			if (activity != null) {
				Looper.prepare(); //在創建Handler【前】必須調用此方法初始化Looper,否則直接報否則報RuntimeException崩潰
				//里面做的事情:①為當前線程創建唯一的Looper對象  ②在它的構造方法中會創建一個的MessageQueue對象
				//此方法只能被調用一次,這保證了在一個線程中只有一個Looper實例以及只有一個與其關聯的MessageQueue實例
				ansyHandler = new StaticAnsyHandler(activity);  //任何線程都可通過此Handler發送信息!
				Looper.loop(); //若要能夠接收到消息,創建Handler后,必須調用loop方法。當然此方法必須是在prepar之后執行
				//里面做的事情:啟動一個死循環,不斷從MQ中取消息,沒有則阻塞等待,有則將消息傳給指定的Handler去處理
				activity.runOnUiThread(() -> Toast.makeText(activity, "會一直阻塞在這里", Toast.LENGTH_SHORT).show());
			}
		}
	}
}
2017-8-25


免責聲明!

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



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