1. 程式人生 > >主執行緒Thread和子執行緒Thread的區別

主執行緒Thread和子執行緒Thread的區別

1. 前言

眾所周知,在android中,非ui執行緒中是不能更新ui,如果在子執行緒中做ui相關操作,可能會出現程式崩潰。一般的做法是,建立一個Message物件,然後Handler傳送該message,然後在Handler的handleMessage()方法中做ui相關操作,這樣就成功實現了子執行緒切換到主執行緒。 
初始化Handler有兩個地方,一個在主執行緒中,一個在子執行緒中,具體有什麼區別呢,接下來從原始碼角度來講解。

2.使用方法

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物件。

6.相關程式碼
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();
    }
}