主執行緒Thread和子執行緒Thread的區別
眾所周知,在android中,非ui執行緒中是不能更新ui,如果在子執行緒中做ui相關操作,可能會出現程式崩潰。一般的做法是,建立一個Message物件,然後Handler傳送該message,然後在Handler的handleMessage()方法中做ui相關操作,這樣就成功實現了子執行緒切換到主執行緒。
初始化Handler有兩個地方,一個在主執行緒中,一個在子執行緒中,具體有什麼區別呢,接下來從原始碼角度來講解。
1)主執行緒中初始化handler
handler1= new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder1",Toast.LENGTH_SHORT).show(); } super.handleMessage(msg); } }; new Thread(new Runnable() { @Override public void run() { Message message = handler1.obtainMessage(); message.arg1 = 1; handler1.sendMessage(message); } }).start();
這個是大家用得很多的場景,這邊不再多說了。
2)子執行緒中初始化handler
new Thread(new Runnable() { @Override public void run() { handler2 = new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show(); } super.handleMessage(msg); } }; Message message = handler2.obtainMessage(); message.arg1 = 1; handler2.sendMessage(message); } }).start();
如果是上面的程式碼,執行程式後,程式會crash,崩潰資訊如下所示:
E/AndroidRuntime: FATAL EXCEPTION: Thread-50003 Process: com.example.cyf.myapplication, PID: 2223 java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() at android.os.Handler.<init>(Handler.java:200) at android.os.Handler.<init>(Handler.java:114) at com.example.cyf.myapplication.MainActivity$3$1.<init>(MainActivity.java:0) at com.example.cyf.myapplication.MainActivity$3.run(MainActivity.java:56) at java.lang.Thread.run(Thread.java:818)
從錯誤的解釋可以看出:沒有呼叫Looper.prepare(),不能建立handler。所以很簡單,我們在建立handler前面加上Looper.prepare(),再執行程式,果然沒有錯誤了。
程式碼如下所示:
new Thread(new Runnable() { @Override public void run() { Looper.prepare(); handler2 = new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show(); } super.handleMessage(msg); } }; Message message = handler2.obtainMessage(); message.arg1 = 1; handler2.sendMessage(message); Looper.loop(); } }).start();
這種呼叫方式相信很多人也明白,面試的時候,也經常提到,但是為什麼這樣寫就可以呢?
3.原始碼講解1)我們先看下handler的原始碼中的建構函式。
public Handler() { 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(); if (mLooper == null) { throw new RuntimeException( "Can't create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = null; }
可以看到第12行出現了剛剛上述的錯誤資訊,很明顯mLooper為空的時候,就會丟擲如下異常。
Can't create handler inside thread that has not called Looper.prepare()
2)Looper物件什麼時候為空,我們看看Looper.myLooper()中的程式碼了,如下所示:
public static final Looper myLooper() { return (Looper)sThreadLocal.get(); }
程式碼非常少,很容易理解,就是從sThreadLocal物件中取出Looper。sThreadLocal原始碼其實就是個陣列,原始碼不貼了,把他想成陣列就好了。sThreadLocal什麼時候存在Looper物件呢,及什麼時候會set一個Looper到該陣列中呢,不用想,肯定是Looper.prepare()方法,我們來看下它的原始碼:
3)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()); }
從上面的程式碼可以看出,sThreadLocal如果沒有Looper,則新建Looper進去,如果存在,則丟擲異常,一個執行緒最多隻能建立一個Looper物件。
4)所以一開始呼叫Looper.prepare()方法,其實相當於為執行緒新建了一個Looper放到sThreadLocal中,這樣mLooper = Looper.myLooper();則可以從sThreadLocal中獲取剛剛建立的Looper,不會導致程式崩潰。
4.其他:
可能會有人說,為什麼我在主執行緒中初始化handler的時候,沒有new Looper,為什麼沒有報異常,相信很多人會聽到別人說,主執行緒預設給我們建立了Looper物件,沒有錯。
我們看下ActivityThread的原始碼中的main()方法
public static void main(String[] args) { SamplingProfilerIntegration.start(); CloseGuard.setEnabled(false); Environment.initForCurrentUser(); EventLogger.setReporter(new EventLoggingReporter()); Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); ActivityThread thread = new ActivityThread(); thread.attach(false); if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } AsyncTask.init(); if (false) { Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread")); } Looper.loop(); throw new RuntimeException("Main thread loop unexpectedly exited"); }
我們可以看到呼叫Looper.prepareMainLooper();繼續看Looper.prepareMainLooper();原始碼
public static final void prepareMainLooper() { prepare(); setMainLooper(myLooper()); if (Process.supportsProcesses()) { myLooper().mQueue.mQuitAllowed = false; } }
從上面程式碼可以看到,會有prepare()方法,及主執行緒中會預設為我們初始化一個Looper物件,從而不需要再手動去呼叫Looper.prepare()方法了。
5.結論:
1)主執行緒中可以直接建立Handler物件。
2)子執行緒中需要先呼叫Looper.prepare(),然後建立Handler物件。
package com.example.cyf.myapplication; import android.app.Activity; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.Toast; public class MainActivity extends Activity { private Handler handler1; private Handler handler2; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); initHandler1(); initHandler2(); } /** * 初始化handler(主執行緒) */ private void initHandler1() { handler1= new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder1",Toast.LENGTH_SHORT).show(); } super.handleMessage(msg); } }; new Thread(new Runnable() { @Override public void run() { Message message = handler1.obtainMessage(); message.arg1 = 1; handler1.sendMessage(message); } }).start(); } /** * 初始化handler(子執行緒) */ private void initHandler2() { new Thread(new Runnable() { @Override public void run() { Looper.prepare(); handler2 = new Handler(){ @Override public void handleMessage(Message msg) { if (msg.arg1==1) { Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show(); } super.handleMessage(msg); } }; Message message = handler2.obtainMessage(); message.arg1 = 1; handler2.sendMessage(message); Looper.loop(); } }).start(); } }