Handler、Thread和Runnable在開發中頻繁使用,很多新手都因為概念不清而頭緒全無,在這我來簡單得縷縷這三者的聯系與區別。
Runnable是最簡單的,它並沒有什么包裝,Android源碼如下:
1 /** 2 * Represents a command that can be executed. Often used to run code in a 3 * different {@link Thread}. 4 */ 5 public interface Runnable { 6 7 /** 8 * Starts executing the active part of the class' code. This method is 9 * called when a thread is started that has been created with a class which 10 * implements {@code Runnable}. 11 */ 12 public void run(); 13 }
Runnable就是一個非常簡單的接口,注釋上說的是“代表一個能被執行的命令,總是用來在新的線程中運行”。
我們再來看看Runnable的子類Thread,我們經常使用Thread來新建一個線程脫離原線程來單獨跑,也經常把Runnable的實現類用Thread來包裝成線程的主要執行內容:Thread thread = new Thread(Runnable)。我們就先來屢屢Thread thread = new Thread(Runnable)的過程。
1 /** 2 * Constructs a new {@code Thread} with a {@code Runnable} object and a 3 * newly generated name. The new {@code Thread} will belong to the same 4 * {@code ThreadGroup} as the {@code Thread} calling this constructor. 5 * 6 * @param runnable 7 * a {@code Runnable} whose method <code>run</code> will be 8 * executed by the new {@code Thread} 9 * 10 * @see java.lang.ThreadGroup 11 * @see java.lang.Runnable 12 */ 13 public Thread(Runnable runnable) { 14 create(null, runnable, null, 0); 15 }
注釋說用Runnable來構造一個線程實例,且創建的線程屬於相同的ThreadGroup(是一種線程容器,create方法的第一個參數代表這個),我們來看看create方法都做了什么。
/** * Initializes a new, existing Thread object with a runnable object, * the given name and belonging to the ThreadGroup passed as parameter. * This is the method that the several public constructors delegate their * work to. * * @param group ThreadGroup to which the new Thread will belong * @param runnable a java.lang.Runnable whose method <code>run</code> will * be executed by the new Thread * @param threadName Name for the Thread being created * @param stackSize Platform dependent stack size * @throws IllegalThreadStateException if <code>group.destroy()</code> has * already been done * @see java.lang.ThreadGroup * @see java.lang.Runnable */ private void create(ThreadGroup group, Runnable runnable, String threadName, long stackSize) { Thread currentThread = Thread.currentThread(); if (group == null) { //前面我們說過了,用Runnable新建的線程和原線程屬於同一線程容器 group = currentThread.getThreadGroup(); } if (group.isDestroyed()) { throw new IllegalThreadStateException("Group already destroyed"); } this.group = group; //此處用synchronized來保證新建的線程id+1並且使唯一的 synchronized (Thread.class) { id = ++Thread.count; } //threadName if (threadName == null) { this.name = "Thread-" + id; } else { this.name = threadName; } //相當於目標任務 this.target = runnable; //線程開辟棧的大小,為0就是默認值8M this.stackSize = stackSize; //新線程的優先級和父線程是一樣的 this.priority = currentThread.getPriority(); this.contextClassLoader = currentThread.contextClassLoader; // Transfer over InheritableThreadLocals. if (currentThread.inheritableValues != null) { inheritableValues = new ThreadLocal.Values(currentThread.inheritableValues); } // add ourselves to our ThreadGroup of choice this.group.addThread(this); }
在新建完線程之后,我們有兩種方法來啟動線程任務:thread.run() ; thread.start()。這兩者有啥區別嘞?都是這么說的:
thread.run() ,實際上只是在UI線程執行了Runnable的任務方法並沒有實現多線程,系統也沒有新開辟一個線程。
thread.start(),才是新開一個多線程,並且在新開的線程執行Thread你們的run()方法。
對於thread.run(),由簡單的java繼承機制也知道,它只是執行了Runnable的run方法,我們來看看源碼吧!
1 /** 2 * Calls the <code>run()</code> method of the Runnable object the receiver 3 * holds. If no Runnable is set, does nothing. 4 * 5 * @see Thread#start 6 */ 7 public void run() { 8 if (target != null) { 9 target.run(); 10 } 11 }
由上面的create方法我們知道target就是Runnable包裝在thread中的實例,還沒有做任何事情,我們知道線程的新建需要請求CPU,所以直接調用run方法確實沒有新建線程,只是在currentThread中直接執行了一個方法而已。我們再來看看thread.start()方法的流程又是怎么樣的。
1 /** 2 * Starts the new Thread of execution. The <code>run()</code> method of 3 * the receiver will be called by the receiver Thread itself (and not the 4 * Thread calling <code>start()</code>). 5 * 6 * @throws IllegalThreadStateException - if this thread has already started. 7 * @see Thread#run 8 */ 9 public synchronized void start() { 10 checkNotStarted(); 11 12 hasBeenStarted = true; 13 14 nativeCreate(this, stackSize, daemon); 15 } 16 17 private native static void nativeCreate(Thread t, long stackSize, boolean daemon);
方法同樣用synchronized關鍵字修飾,用來防止同一個線程阻塞。而方法的執行交給了nativeCreate方法,並且把當前Thread的實例自己傳了進去,而this中就我們所知的,帶了這么幾個參數:
1 volatile ThreadGroup group; 2 volatile boolean daemon; 3 volatile String name; 4 volatile int priority; 5 volatile long stackSize; 6 Runnable target; 7 private static int count = 0; 8 9 /** 10 * Holds the thread's ID. We simply count upwards, so 11 * each Thread has a unique ID. 12 */ 13 private long id; 14 15 /** 16 * Normal thread local values. 17 */ 18 ThreadLocal.Values localValues; 19 20 /** 21 * Inheritable thread local values. 22 */ 23 ThreadLocal.Values inheritableValues;
那我們只好跟過去,看看在native中到底是怎么新建的新建的線程,資源又是如何請求的(新建線程的關鍵)。
順着nativeCreate方法,我們發現它換了方法名調用的/android/art/runtime/native/java_lang_Thread.cc里面方法,名字換成了CreateNativeThread:
1 static JNINativeMethod gMethods[] = { 2 NATIVE_METHOD(Thread, currentThread, "!()Ljava/lang/Thread;"), 3 NATIVE_METHOD(Thread, interrupted, "!()Z"), 4 NATIVE_METHOD(Thread, isInterrupted, "!()Z"), 5 NATIVE_METHOD(Thread, nativeCreate, "(Ljava/lang/Thread;JZ)V"), 6 NATIVE_METHOD(Thread, nativeGetStatus, "(Z)I"), 7 NATIVE_METHOD(Thread, nativeHoldsLock, "(Ljava/lang/Object;)Z"), 8 NATIVE_METHOD(Thread, nativeInterrupt, "!()V"), 9 NATIVE_METHOD(Thread, nativeSetName, "(Ljava/lang/String;)V"), 10 NATIVE_METHOD(Thread, nativeSetPriority, "(I)V"), 11 NATIVE_METHOD(Thread, sleep, "!(Ljava/lang/Object;JI)V"), 12 NATIVE_METHOD(Thread, yield, "()V"), 13 };
我們可以看到關於線程管理的一些方法全在里面,包括sleep、interrupted等,繼續dig到CreateNativeThread的實現
1 void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_size, bool is_daemon) { 2 CHECK(java_peer != nullptr);//即為java層的thread實例,包裹着run方法的具體實現 3 Thread* self = static_cast<JNIEnvExt*>(env)->self; 4 Runtime* runtime = Runtime::Current(); 5 6 // Atomically start the birth of the thread ensuring the runtime isn't shutting down. 7 bool thread_start_during_shutdown = false;//這段代碼用來檢測thread是否在runtime宕機時start的 8 { 9 MutexLock mu(self, *Locks::runtime_shutdown_lock_); 10 if (runtime->IsShuttingDownLocked()) { 11 thread_start_during_shutdown = true; 12 } else { 13 runtime->StartThreadBirth(); 14 } 15 } 16 if (thread_start_during_shutdown) {//若runtime宕機了就拋出異常 17 ScopedLocalRef<jclass> error_class(env, env->FindClass("java/lang/InternalError")); 18 env->ThrowNew(error_class.get(), "Thread starting during runtime shutdown"); 19 return; 20 } 21 22 Thread* child_thread = new Thread(is_daemon);//新建子線程 23 // Use global JNI ref to hold peer live while child thread starts. 24 child_thread->tlsPtr_.jpeer = env->NewGlobalRef(java_peer);//把java層的run方法實體傳遞給子線程 25 stack_size = FixStackSize(stack_size); 26 27 // Thread.start is synchronized, so we know that nativePeer is 0, and know that we're not racing to 28 // assign it. 29 env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 30 reinterpret_cast<jlong>(child_thread)); 31 32 pthread_t new_pthread; 33 pthread_attr_t attr; 34 CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread"); 35 CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED), "PTHREAD_CREATE_DETACHED"); 36 CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size); 37 //創建新線程的方法,返回一個標志 38 int pthread_create_result = pthread_create(&new_pthread, &attr, Thread::CreateCallback, child_thread); 39 CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread"); 40 41 //線程創建如果失敗則清除子線程信息,釋放空間 42 if (pthread_create_result != 0) { 43 // pthread_create(3) failed, so clean up. 44 { 45 MutexLock mu(self, *Locks::runtime_shutdown_lock_); 46 runtime->EndThreadBirth(); 47 } 48 // Manually delete the global reference since Thread::Init will not have been run. 49 env->DeleteGlobalRef(child_thread->tlsPtr_.jpeer); 50 child_thread->tlsPtr_.jpeer = nullptr; 51 delete child_thread; 52 child_thread = nullptr; 53 // TODO: remove from thread group? 54 env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0); 55 { 56 std::string msg(StringPrintf("pthread_create (%s stack) failed: %s", 57 PrettySize(stack_size).c_str(), strerror(pthread_create_result))); 58 ScopedObjectAccess soa(env); 59 soa.Self()->ThrowOutOfMemoryError(msg.c_str()); 60 } 61 } 62 }
創建新線程在pthread_create(&new_pthread, &attr, Thread::CreateCallback, child_thread)方法里由java Runtime實現,由於那里過於深入,我們就不往下挖了(往下我已經看不懂了)。但是我們有個一個創新線程過程的概念,進一步的了解了thread的id號,父線程等概念。也知道了只有通過thread.start()方法才會創建一個新的線程。
說清了Thread和Runnable的關系,我們再來說說Handler,Handler是啥呢?當我們需要進行線程間通信的時候,我們就需要用到Handler,我們把Handler認為是一個維護消息循環隊列的東西,書上都這么說,那么Handler是如何維護消息隊列呢?要弄清楚這個還是得看源碼。
Google給出的關於Handler簡介,大家感受下:
1 /** 2 * A Handler allows you to send and process {@link Message} and Runnable 3 * objects associated with a thread's {@link MessageQueue}. Each Handler 4 * instance is associated with a single thread and that thread's message 5 * queue. When you create a new Handler, it is bound to the thread / 6 * message queue of the thread that is creating it -- from that point on, 7 * it will deliver messages and runnables to that message queue and execute 8 * them as they come out of the message queue. 9 * 10 **/
大意是三點:
1:Handler可以在線程的幫助下用來發送和處理Message和Runnable實例;
2:每個Handler實例都是和單個線程和此線程的消息隊列綁定的;
3:Handler負責的工作是傳送message到消息隊列中且在它們從隊列中出來的時候對它們進行處理。
那么Handler是怎么維護消息隊列呢?在處理消息過程中,我們常用到的幾個方法:sendMessage、sendMessageAtFrontOfQueue、sendMessageAtTime、sendMessageDelayed 以及 handleMessage 。我們來看看這幾個方法的源碼:
1 public final boolean sendMessage(Message msg) 2 { 3 return sendMessageDelayed(msg, 0); 4 } 5 public final boolean sendMessageDelayed(Message msg, long delayMillis) 6 { 7 if (delayMillis < 0) { 8 delayMillis = 0; 9 } 10 return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); 11 } 12 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 13 MessageQueue queue = mQueue; 14 if (queue == null) { 15 RuntimeException e = new RuntimeException( 16 this + " sendMessageAtTime() called with no mQueue"); 17 Log.w("Looper", e.getMessage(), e); 18 return false; 19 } 20 return enqueueMessage(queue, msg, uptimeMillis); 21 } 22 public final boolean sendMessageAtFrontOfQueue(Message msg) { 23 MessageQueue queue = mQueue; 24 if (queue == null) { 25 RuntimeException e = new RuntimeException( 26 this + " sendMessageAtTime() called with no mQueue"); 27 Log.w("Looper", e.getMessage(), e); 28 return false; 29 } 30 return enqueueMessage(queue, msg, 0); 31 }
我們可以看到除了設置的不同,發送message的每個方法都實現了enqueueMessage()方法,我們看看enqueueMessage()的源碼:
1 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { 2 msg.target = this; 3 if (mAsynchronous) { 4 msg.setAsynchronous(true); 5 } 6 return queue.enqueueMessage(msg, uptimeMillis); 7 }
追到MessageQueue里面,我們可以看到一個經典的隊列add的操作:
1 boolean enqueueMessage(Message msg, long when) { 2 if (msg.target == null) { 3 throw new IllegalArgumentException("Message must have a target."); 4 } 5 if (msg.isInUse()) { 6 throw new IllegalStateException(msg + " This message is already in use."); 7 } 8 9 synchronized (this) { 10 if (mQuitting) { 11 IllegalStateException e = new IllegalStateException( 12 msg.target + " sending message to a Handler on a dead thread"); 13 Log.w("MessageQueue", e.getMessage(), e); 14 msg.recycle(); 15 return false; 16 } 17 18 msg.markInUse(); 19 msg.when = when; 20 Message p = mMessages; 21 boolean needWake; 22 if (p == null || when == 0 || when < p.when) { 23 // New head, wake up the event queue if blocked. 24 msg.next = p; 25 mMessages = msg; 26 needWake = mBlocked; 27 } else { 28 // Inserted within the middle of the queue. Usually we don't have to wake 29 // up the event queue unless there is a barrier at the head of the queue 30 // and the message is the earliest asynchronous message in the queue. 31 needWake = mBlocked && p.target == null && msg.isAsynchronous(); 32 Message prev; 33 //經典的隊列add操作 34 for (;;) { 35 prev = p; 36 p = p.next; 37 if (p == null || when < p.when) { 38 break; 39 } 40 if (needWake && p.isAsynchronous()) { 41 needWake = false; 42 } 43 } 44 msg.next = p; // invariant: p == prev.next 45 prev.next = msg; 46 } 47 48 // We can assume mPtr != 0 because mQuitting is false. 49 if (needWake) { 50 nativeWake(mPtr); 51 } 52 } 53 return true; 54 }
可以看到Handler每次sendmessage時其實都是一個把message實例加到MessageQueue中的過程,無論延時還是不延時。
發送message到消息隊列的過程我們清楚了,那么Handler是怎么從消息隊列中往外拿message的呢?這時候就是Looper出場的時候了,Looper 也是一個對應着一個線程,當程序運行時,系統會給主線程創建一個Looper,Looper也維護着一個MessageQueue,沒錯,這個messagequeue就是Handler發送的那個,Looper的責任就是一方面接收消息,另一方面不停地檢查messagequeue里有沒有message,如果有就調用Handler中的dispatchMessage方法進行處理,我們來看看源碼是怎么寫的。
1 /** 2 * Run the message queue in this thread. Be sure to call 3 * {@link #quit()} to end the loop. 4 */ 5 public static void loop() { 6 final Looper me = myLooper(); 7 if (me == null) { 8 throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); 9 } 10 final MessageQueue queue = me.mQueue; 11 12 // Make sure the identity of this thread is that of the local process, 13 // and keep track of what that identity token actually is. 14 Binder.clearCallingIdentity(); 15 final long ident = Binder.clearCallingIdentity(); 16 17 for (;;) { 18 Message msg = queue.next(); // might block 19 if (msg == null) { 20 // No message indicates that the message queue is quitting. 21 return; 22 } 23 24 // This must be in a local variable, in case a UI event sets the logger 25 Printer logging = me.mLogging; 26 if (logging != null) { 27 logging.println(">>>>> Dispatching to " + msg.target + " " + 28 msg.callback + ": " + msg.what); 29 } 30 31 msg.target.dispatchMessage(msg); 32 33 if (logging != null) { 34 logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); 35 } 36 37 // Make sure that during the course of dispatching the 38 // identity of the thread wasn't corrupted. 39 final long newIdent = Binder.clearCallingIdentity(); 40 if (ident != newIdent) { 41 Log.wtf(TAG, "Thread identity changed from 0x" 42 + Long.toHexString(ident) + " to 0x" 43 + Long.toHexString(newIdent) + " while dispatching to " 44 + msg.target.getClass().getName() + " " 45 + msg.callback + " what=" + msg.what); 46 } 47 48 msg.recycleUnchecked(); 49 } 50 }
我們能看到有一句處理消息的代碼: msg.target.dispatchMessage(msg);
再看看enqueueMessage方法里:msg.target = this;
對的,message的target就是handler:
1 /** 2 * Handle system messages here. 3 */ 4 public void dispatchMessage(Message msg) { 5 if (msg.callback != null) { 6 handleCallback(msg); 7 } else { 8 if (mCallback != null) { 9 if (mCallback.handleMessage(msg)) { 10 return; 11 } 12 } 13 handleMessage(msg); 14 } 15 }
一切的一切都回到了handleMessage()方法中,我們最熟悉的方法。
我們用一張流程圖來總結一下Handler、Looper和Message三者的關系。
說了足夠多的基礎知識了,那么線程間如何進行通信的呢?其實我們只要稍微想想就知道了,根據上面的流程圖,只要我們的Looper里面的MessageQueue是一致的,那么就能夠通過message進行線程通信了。
上面我們已經說過了Looper也是和線程對應的,當程序運行時,主線程會自動創建一個Looper對象,這時我們通過Looper.getMainLooper()或者Looper.myLooper()會得到屬於主線程的Looper對象。但如果我們開啟一個新線程,在新線程里面用Looper.myLooper()去獲取屬於當前線程的Looper對象,但是返回結果為null。起初很不理解,Looper對象里有個靜態變量ThreadLocal,大家都知道靜態變量存放於常量池里面,只有一份,按道理應該是能獲取的到。
1 // sThreadLocal.get() will return null unless you've called prepare(). 2 static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
后來查了一下這個TheadLocal對象,當開啟多個線程去訪問數據庫時,這時候共享數據庫鏈接是不安全的,ThreadLocal保證了每個線程都有自己的session(數據庫鏈接),首次訪問為null,會創建自己的鏈接。Looper對象也一樣,ThreadLocal保證了每個線程都有自己的Looper對象。在android中主線程啟動會創建屬於自己的looper對象,這時候如果我們在去調用prepare就會拋Only one Looper may be created per thread。
1 private static void prepare(boolean quitAllowed) { 2 if (sThreadLocal.get() != null) { 3 throw new RuntimeException("Only one Looper may be created per thread"); 4 } 5 sThreadLocal.set(new Looper(quitAllowed)); 6 }
但是子線程並不會自己創建looper對象,需要我們手動調用prepare創建屬於該線程的looper對象。當然如果只是線程間通信我們完全可以在拿到主線程的Looper基礎上直接通過message傳遞消息,下面是一個簡單實例:
1 public class Activity extends Activity implements OnClickListener{ 2 Button button = null ; 3 TextView text = null ; 4 MyHandler mHandler = null ; 5 Thread thread ; 6 @Override 7 protected void onCreate(Bundle savedInstanceState) { 8 super .onCreate(savedInstanceState); 9 setContentView(R.layout. activity); 10 button = (Button)findViewById(R.id. btn ); 11 button .setOnClickListener( this ); 12 text = (TextView)findViewById(R.id. content ); 13 } 14 public void onClick(View v) { 15 switch (v.getId()) { 16 case R.id. btn : 17 thread = new MyThread(); 18 thread .start(); 19 break ; 20 } 21 } 22 private class MyHandler extends Handler{ 23 public MyHandler(Looper looper){ 24 super (looper); 25 } 26 @Override 27 public void handleMessage(Message msg) { // 處理消息 28 text .setText(msg. obj .toString()); 29 } 30 } 31 private class MyThread extends Thread{ 32 @Override 33 public void run() { 34 //拿到主線程的Looper,構造子線程的Handler 35 Looper curLooper = Looper.myLooper (); 36 Looper mainLooper = Looper.getMainLooper (); 37 String msg ; 38 if (curLooper== null ){ 39 mHandler = new MyHandler(mainLooper); 40 msg = "curLooper is null" ; 41 } else { 42 mHandler = new MyHandler(curLooper); 43 msg = "This is curLooper" ; 44 } 45 mHandler .removeMessages(0); 46 //通過message進行線程間通信 47 Message m = mHandler .obtainMessage(1, 1, 1, msg); 48 mHandler .sendMessage(m); 49 } 50 } 51 }
我們來看看另外一種情況:通過子線程在UI線程中更新UI。利用上面這種方法當然是可以的,我們還可以通過往MessageQueue中post Runnable對象來做,要講清楚這個,我們得先了解一個概念:有消息循環的線程。在 Android,線程分為有消息循環的線程和沒有消息循環的線程,有消息循環的線程一般都會有一個Looper,通過Looper我們來定義線程中需要完成的任務。我們的主線程(UI線程)就是一個消息循環的線程。Looper所維護的消息隊列里面不僅僅包括邏輯處理,同時也包含了UI任務更新。線程的運行是這樣的,按照Looper維護的消息隊列,從前往后遍歷消息和任務,一一完成,我們之所以不能直接在子線程中更新UI,是因為我們無法知道主線程Looper中消息隊列中的完成度,如果消息不為空,這時子線程更新UI,就會發生阻塞,所以我們要把需要更新的任務放到Looper中,讓子線程的請求也加入到主線程的任務隊列里面,一一完成。
前面我們說了,Handler不僅可以發送message,也何以發送Runnable對象,我們也可以不同message的傳遞方式,直接把任務(run方法)post到Looper中,如下示例:
1 1.在onCreate中創建Handler 2 public class HandlerTestApp extends Activity { 3 Handler mHandler; 4 TextView mText; 5 /** Called when the activity is first created. */ 6 @Override 7 public void onCreate(Bundle savedInstanceState) { 8 super.onCreate(savedInstanceState); 9 setContentView(R.layout.main); 10 mHandler = new Handler();//創建Handler 11 mText = (TextView) findViewById(R.id.text0);//一個TextView 12 } 13 /** 構建Runnable對象,在runnable中更新界面,此處,我們修改了TextView的文字.此處需要說明的是,Runnable對象可以再主線程中創建,也可以再子線程中創建。我們此處是在子線程中創建的。 14 **/ 15 Runnable mRunnable0 = new Runnable() 16 { 17 @Override 18 public void run() { 19 mText.setText("This is Update from ohter thread, Mouse DOWN"); 20 } 21 }; 22 2.創建子線程,在線程的run函數中,我們向主線程的消息隊列發送了一個runnable來更新界面。 23 24 private void updateUIByRunnable(){ 25 new Thread() 26 { 27 //Message msg = mHandler.obtainMessage(); 28 public void run() 29 { 30 31 //mText.setText("This is Update from ohter thread, Mouse DOWN");//這句將拋出異常 32 mHandler.post(mRunnable0); 33 } 34 }.start(); 35 36 }
理解了有消息循環線程就很容易理解這個,把Runnable看做是Message也行。