1,今天和你們一塊兒從底層看看Handle的工做機制是什麼樣的,那麼在引入以前咱們先來了解Handle是用來幹什麼的java
handler通俗一點講就是用來在各個線程之間發送數據的處理對象。在任何線程中,只要得到了另外一個線程的handler,則能夠經過 handler.sendMessage(message)方法向那個線程發送數據。基於這個機制,咱們在處理多線程的時候能夠新建一個thread,這個thread擁有UI線程中的一個handler。當thread處理完一些耗時的操做後經過傳遞過來的handler向UI線程發送數據,由UI線程去更新界面。 主線程:運行全部UI組件,它經過一個消息隊列來完成此任務。設備會將用戶的每項操做轉換爲消息,並將它們放入正在運行的消息隊列中。主線程位於一個循環中,並處理每條消息。若是任何一個消息用時超過5秒,Android將拋出ANR。因此一個任務用時超過5秒,應該在一個獨立線程中完成它,或者延遲處理它,當主線程空閒下來再返回來處理它。
上面的總結成一句話來講就是,因爲主線程不能作耗時操做而子線程不能更新UI,解決線程間通訊問題。網絡
2,這裏防止有些同窗壓根沒有了解過Handle,因此這裏仍是帶着你們簡單的從使用入手,先知道怎麼使用以後,再來經過源碼來理解它的使用多線程
先看一下效果:異步
很簡單的功能,就是相似於一個定時器的東西,TextView不停的去更新UI,要實現這個功能很簡單,在java中咱們能夠經過Timer或者ScheduledExecutorService來實現,而如今咱們打算使用Android的一些技術來實現,這裏有可能有同窗已經想到了一種簡單的方法就是相似於一下的代碼:async
public class FourActivity extends AppCompatActivity{ private TextView tv_textview; private int count = 0;// 用於顯示到textView上的數據 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_four); // 子線程中產生數據,而後須要將數據傳遞到主線程中,設置到Textview。 tv_textview = (TextView) findViewById(R.id.tv_main_show); // 數據產生自子線程 new Thread() { public void run() { // 子線程中執行的內容: while (true) { if (count < 100) { count++;// 反覆的設置到textivew上 tv_textview.setText(count + "");// (錯誤代碼)。 try { Thread.sleep(1000);// 模擬網絡延遲。睡眠。 } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } }; }.start(); } }
是的上面的代碼貌似看上去沒什麼問題,邏輯也很清楚,可是運行以後咱們會發現報錯了,報錯的緣由也很簡單,就是子線程中不能去更新UI,那麼咱們又想更新UI該怎麼辦呢?這裏就要使用到咱們的Handle,具體實現代碼以下:ide
public class FourActivity extends AppCompatActivity{ private TextView tv_textview; private int count = 0;// 用於顯示到textView上的數據 // 在主線程中建立handler對象,用於子線程發送消息,主線程處理消息。 private Handler handler = new Handler() { // 重寫handler處理消息的方法。 public void handleMessage(Message msg) { switch (msg.what) { case 0: int count = msg.arg1; int num = msg.arg2; String str = (String) msg.obj; tv_textview.setText(count + "," + str); // Log.i("tag", "===num:" + num + ",what:" + what); } }; }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_four); // 子線程中產生數據,而後須要將數據傳遞到主線程中,設置到Textview。 tv_textview = (TextView) findViewById(R.id.tv_main_show); // 數據產生自子線程 new Thread() { public void run() { // 子線程中執行的內容: while (true) { if (count < 100) { count++;// 反覆的設置到textivew上 // tv_textview.setText(count + "");// 錯誤代碼。 // 將子線程中產生的數據count,傳遞到UI主線程中。 // 寫法一:Message從消息池中取數據。由handler發送給主線程。 Message msg = Message.obtain();// // 從消息池中獲取一個Message對象,用於存儲數據,若是消息池中沒有Message,會自動建立一個,以供使用。不要本身new // // Message,爲了提升效率。 msg.arg1 = count;// arg1,arg2,用於存儲int類型的數據的。 msg.arg2 = 10; msg.obj = "麼麼噠";// 用於存儲Objct類型的數據 // // Bundle,用於存儲大量的數據。 msg.what = 0;// 用於標記該Mesage,用於區分的。 // handler.sendMessage(msg);// // 在子線程中使用主線程的handler對象,發送消息到主線程。 try { Thread.sleep(1000);// 模擬網絡延遲。睡眠。 } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } }; }.start(); } }
咱們能夠看到要實現這個功能,首先要建立一個全局變量的handle,重寫它的handleMessage方法,在裏面進行一些收到消息的處理,而後在子線程中發送消息。oop
這裏咱們須要注意到的屬性和方法是:post
1,獲取Message對象的話使用 Message msg = Message.obtain(); 在消息池中去拿消息對象,而不要動手建立 2,Message對象中有幾個屬性和方法 ,具體代碼以下: msg.arg1 = count;// arg1,arg2,用於存儲int類型的數據的。 msg.arg2 = 10; msg.obj = "麼麼噠";// 用於存儲Objct類型的數據 // // Bundle,用於存儲大量的數據。 msg.what = 0;// 用於標記該Mesage,用於區分的。 首先咱們想傳遞的若是是int類型的數據的話咱們能夠直接使用Message中的arg1和arg2變量,若是咱們想傳遞object對象的話咱們可使用message中的obj變量,若是想傳遞大的變量的話,咱們可使用bundle對象,而後能夠用what來區分Message的分類
咱們能夠一看到當咱們調用handler.sendMessage(msg)的時候,執行的是咱們handleMessage()方法中的邏輯。ok,這是handle的一種實現方法,咱們再看下一種方法ui
new Thread() { public void run() { // 子線程中執行的內容: while (true) { if (count < 100) { count++;// 反覆的設置到textivew上 // 寫法二: Message msg = handler.obtainMessage();// // 由handler來獲取的消息,天然就歸handler來處理 msg.arg1 = count; msg.sendToTarget();// 將msg發送給對應的目標:target:就是handler try { Thread.sleep(1000);// 模擬網絡延遲。睡眠。 } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } }; }.start();
這時候咱們是經過handle來拿到對應的Message對象的,而後直接經過message對象的sendToTarget方法發送消息,而不須要像咱們以前的handler.sendMessage()方法,而後咱們還有下面一種方法this
new Thread() { public void run() { // 子線程中執行的內容: while (true) { if (count < 100) { count++;// 反覆的設置到textivew上 // 寫法三: Message msg = Message.obtain(); msg.arg1 = count; msg.setTarget(handler); msg.sendToTarget(); try { Thread.sleep(1000);// 模擬網絡延遲。睡眠。 } catch (InterruptedException e) { e.printStackTrace(); } } else { break; } } }; }.start();
這裏咱們仍是經過消息池拿到咱們的Message對象,可是調用了msg.setTarget()來綁定handle,因此也能夠直接調用msg.sendToTarget()方法
先看一下的基本使用
/** * step1:啓動子線程獲取數據 * * step2:在主線程中建立Handler對象 * * step3:在子線程中直接操做handler.post(Runnable r):重寫該接口的是實現類。。 * * step4:run()---》執行在main線程中。 * * @author wangjitao * */
再看看具體的實現
public class MainActivity extends Activity { private TextView tv_show; private Handler handler = new Handler(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv_show = (TextView) findViewById(R.id.tv_show); } // 點擊按鈕,由子線程向主線程發送數據 public void click(View v) { new Thread() { @Override public void run() { // 產生數據,發送到主線程中 final int num = 100; final String str = "HelloWorld"; ArrayList<String> list = new ArrayList<String>(); Collections.addAll(list, "abc", "aa", "bb"); Bundle bundle = new Bundle(); bundle.putString("str", str); bundle.putSerializable("list", list); // msg.setData(bundle); handler.post(new Runnable() {// handler將來要作的事,寫在run裏。 @Override public void run() { // 執行在主線程中的。 Log.i("tag", "==線程id:" + Thread.currentThread().getId() + ",線程名稱:" + Thread.currentThread().getName()); tv_show.setText("str," + str + ",num:" + num); } }); } }.start(); } }
這裏關鍵的是調用handle.post方法,且在方法裏面直接執行了更新UI操做,可是你們運行一下,不報錯,因此這裏的和你們說一下,這裏的post方法是將運行在主線程中,因此就能夠更新ui,咱們過一下看看源碼它是怎麼實現的。
咱們上面實現了從子線程中發送消息,在主線線程中獲取消息,那麼咱們如今想在主線程中發送消息,在子線程中處理消息,這個咱們該怎麼實現呢?
public class MainActivity extends Activity { private Handler handler = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } // 點擊按鈕,建立子線程 public void click_start(View v) { new MyThread().start(); } // 點擊該按鈕,數據從主線程傳遞到子線程中 public void click_send(View v) { // 由handler發送數據 Message msg = Message.obtain();// msg.what = 1; msg.arg1 = 100; msg.obj = "我是從主線程中傳遞來的數據"; handler.sendMessage(msg);// msg:target:handler } // 自定義類,繼承Thread,表示用於子線程 class MyThread extends Thread { @Override public void run() { // 子線程中要執行的代碼: // 用於接收主線程傳來的數據 // 由handler接收處理數據就能夠。 /** * 報錯信息: java.lang.RuntimeException: Can't create handler inside * thread that has not called Looper.prepare() */ // 應該先執行:Looper.prepare(); /** * 表示將當前的線程升級爲:Looper線程 * * 1.建立一個Looper對象。注意:一個線程中只能有一個Looper對象。用ThreadLocal<T> * * 2.一個Looper對象,負責維護一個消息隊列:new MessageQueue */ Looper.prepare();// 將子線程升級,成Looper線程。就能夠操做handler對象。不然一個普通的子線程,不能操做handler。 handler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case 1: int num = msg.arg1; String str = (String) msg.obj; Log.i("tag", "===子線程:" + Thread.currentThread().getId() + ",線程名字:" + Thread.currentThread().getName() + ",數據:" + num + ",str:" + str); break; default: break; } } }; Looper.loop();// 用於循環處理消息。 } } }
這裏要注意,要想子線程收到messsage ,首先要將子線程升級成Looper線程,主要調用下面這兩個方法
Looper.prepare();// 將子線程升級,成Looper線程。就能夠操做handler對象。不然一個普通的子線程,不能操做handler。 Looper.loop();// 用於循環處理消息。
既然上面的先調用的是Looper.prepare();方法,那麼咱們來看看prepare方法吧
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(); }
再看一下它的一些重要的成員變量
//ThreadLocal線程本地變量,用於爲該線程中只有一個Looper對象。 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); //Looper內部維護的消息隊列 final MessageQueue mQueue; //當前線程對象自己 final Thread mThread;
能夠看到,這裏咱們調用Looper.prepare()方法方法的時候,首先判斷sThreadLocal 是否已經存在Looper對象,若是存在則拋異常,這裏是由於每一個線程中只能包含一個Looper對象去維護,若是不存在,則new Looper對象,且初始化內部消息維護隊列 mQueue、線程自己mThread。
看完prepare()方法以後咱們繼續往下看,Looper.loop()。先看一下源碼
public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } final MessageQueue queue = me.mQueue; Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger final Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } final long traceTag = me.mTraceTag; if (traceTag != 0 && Trace.isTagEnabled(traceTag)) { Trace.traceBegin(traceTag, msg.target.getTraceName(msg)); } try { msg.target.dispatchMessage(msg); } finally { if (traceTag != 0) { Trace.traceEnd(traceTag); } } if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn't corrupted. final long newIdent = Binder.clearCallingIdentity(); 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.recycleUnchecked(); } } public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
而後我將上面的代碼提取出重要的
//循環工做 public static void loop() { //獲取當前的looper對象 final Looper me = myLooper(); //獲取消息隊列 final MessageQueue queue = me.mQueue; //循環處理消息 for (;;) { Message msg = queue.next(); // 從消息隊列中獲取消息 msg.target.dispatchMessage(msg);//將msg交給對應的handler去分發。msg.target:就是對應的handler //handler.dispatchMessage(msg); msg.recycle();//回收消息到消息池,便於下次使用。 } } //從線程的本地變量中獲取Looper對象 public static Looper myLooper() { return sThreadLocal.get(); } }
首先咱們經過調用自己的 myLooper()來獲取當前的Looper對象,而後獲取mQueue消息隊列,而後就是for循環了,一直獲取mQueue消息隊列中的Massage對象,知道消息隊列中沒有消息而後退出循環,而後在循環中最關鍵的是一下這句代碼
msg.target.dispatchMessage(msg);
首先msg.target,這個你們有沒有很熟悉,咱們上面使用的時候曾經使用過msg.sendToTarget()和msg.setTarget(handler),看裏面的參數!是一個handle,爲了驗證咱們的猜測咱們看看message源碼中的target屬性
public final class Message implements Parcelable { ...........省略代碼 /*package*/ int flags; /*package*/ long when; /*package*/ Bundle data; /*package*/ Handler target; /*package*/ Runnable callback; // sometimes we store linked lists of these things ...........省略代碼 }
果真,這裏target是Handle對象,也就是說是調用的handle.dispatchMessage(msg)的對象,咱們來看看handle源碼
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } public interface Callback { public boolean handleMessage(Message msg); }
看到沒 這裏咱們mCallback.handleMessage仍是最終調用的是handle中的handleMessage方法!!!,這樣整個Looper源碼的邏輯就通了,首先建立Looper對象用來維護本線程中的消息隊列,而後for循環,一直將消息隊列中的message分發到對應的handle上去。
這裏咱們的Looper中有如下注意事項
總結:Looper的注意事項 1.每個線程只能有最多一個Looper對象。 2.當建立Looper的同時,MessageQueue一同被建立。 3.調用loop()方法,循環從消息隊列上獲取消息,分發給對應的handler。
下面咱們來看看Handle的源碼
首先先看一下全局變量
//與當前的handler對象關聯的消息隊列 final MessageQueue mQueue; //與handler關聯的Looper對象 final Looper mLooper; final Callback mCallback;
在構造方法中進行一些賦值
//建立Handler對象 public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue;//將Looper維護的消息隊列,賦值給當前handler所關聯的消息隊列。 mCallback = callback; }
這裏的構造和咱們的Thread構造很像,可經過直接匿名內部內的方式也可使用建立CallBack子類的方式獲得Handle對象這裏調用 Looper.myLooper()拿到,而後設置一下消息隊列,其實還有一個構造方法,以下:
public Handler(Looper looper, Callback callback, boolean async) { mLooper = looper; mQueue = looper.mQueue; mCallback = callback; mAsynchronous = async; }
這裏因爲咱們不怎麼使用,因此只說一下幾個參數的含義傳第一個參數進來一個looper來代替默認的looper,第二個參數callback接口用於處理handler傳遞的Message,第三個是說是否要異步
ok,咱們繼續往下看方法,咱們handle常使用sendMessage方法,來看看它裏面是怎麼使用的
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } 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); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
最後其實調用的是handle類中的enqueueMessage方法,而後 msg.target = this;很重要 ,綁定發送message消息對象和當前的handle對象,把message插入到消息隊列中。
下面咱們繼續看handle.dispatchMessage()因爲在Looper中已經講過了的,因此在這裏不在和你們廢話了。
再看看咱們的HandleMessage()方法
//由接收數據的線程, 重寫的方法,表示處理msg public void handleMessage(Message msg) { }
咱們通常會重寫該方法,進行一些接收消息的操做。
繼續往看handle.post()方法
public final boolean post(Runnable r) { return sendMessageDelayed(getPostMessage(r), 0); } private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
能夠看到這裏最終仍是調用了sendMessageDelayed方法,和咱們的sendMessage方法同樣,可是不一樣的區別是什麼呢?這裏調用了getPostMessage()中,咱們建立了一個Message對象,並將傳入的Runnable對象賦值給Message的callback成員字段,而後返回該Message,而後在post方法中該攜帶有Runnable信息的Message傳入到sendMessageDelayed方法中。由此咱們能夠看到全部的postXXX方法內部都須要藉助sendMessageXXX方法來實現,因此postXXX與sendMessageXXX並非對立關係,而是postXXX依賴sendMessageXXX,因此postXXX方法能夠經過sendMessageXXX方法向消息隊列中傳入消息,只不過經過postXXX方法向消息隊列中傳入的消息都攜帶有Runnable對象(Message.callback),這時候要要給你們貼上咱們以前看到的一些代碼。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } } private static void handleCallback(Message message) { message.callback.run(); }
這裏咱們首先判斷msg的callback是否爲空,顯然在調用post方法的時候咱們不爲空,因此走的是handleCallback方法,而在這個裏面直接調用的是咱們Runnable中的run
方法,也就是說直接執行Runnable中的run方法。這裏你們可能會有疑問了,以前不是說handle.post方法是運行在主線程中嗎,我這裏明明new了一個Runnable,爲何還運行在主線程中啊,首先咱們要明白一個誤區,不是建立一個Runnable對象就是標明是新建一個子線程,在咱們上例的狀況中咱們的Handle是建立在Activity中的,Handler是綁定到建立它的主線程中的,Handler跟UI主線程是同一個線程,因此它原本就是在主線程中,再看看源碼google給的註釋
* Causes the Runnable r to be added to the message queue. * The runnable will be run on the thread to which this handler is * attached.
註釋的意思是runnable會在handler所在的線程中執行。按照註釋的意思以及咱們常用的狀況來看,runnable的邏輯無疑是在主線程中執行的。這也解決了咱們爲何post方法是執行在主線程中的。
咱們來總結一下Handle相關的知識點
Handler的屬性和方法: 屬性: final MessageQueue mQueue; final Looper mLooper; final Callback mCallback; 方法:5個重要方法 sendMessage(msg); handleMessage(); post(); sendEmptyMessage(); obtainMessage(); dispatchMessage(); sendMessageDelayed(); sendMessageAtTime();
public final class Message implements Parcelable { public int what; public int arg1; public int arg2; public Object obj; } what arg1 arg2 obj 方法: obtain(); setTarget(Handler target) setData() sendToTarget();
Message中的源碼超級簡單,這裏就不給你們廢話了,只須要知道這幾個重要的方法和屬性
到這裏咱們基本上就分析完了。其實很簡單有沒有.......................