源碼角度講解子線程建立Handler報錯的緣由

1.前言

衆所周知,在android中,非UI線程中是不能更新UI的,若是在子線程中作UI相關操做,可能會出現程序崩潰。通常的作法是,建立一個Message對象,Handler發送該message,而後在Handler的handleMessage()方法中作ui相關操做,這樣就成功實現了子線程切換到主線程。 其實handler主要有兩個功能: 1.刷新UI,(須要用主線程的looper) 2.不用刷新ui,只是處理消息java

2.使用方法

1.刷新UI 1)主線程中初始化handler,實現子線程切換到主線程,進行刷新UIandroid

handler1= new Handler(){
            @Override
            public void handleMessage(Message msg) {
                if (msg.arg1==1) {
                    Toast.makeText(MainActivity.this,"hanlder1",Toast.LENGTH_SHORT).show();
                }
                System.out.println("handler1========="+Thread.currentThread().getName());
                super.handleMessage(msg);
            }
        };

        new Thread(new Runnable() {
            @Override
            public void run() {
                Message message = handler1.obtainMessage();
                message.arg1 = 1;
                handler1.sendMessage(message);
            }
        }).start();
複製代碼

2)子線程中初始化handler,實現子線程切換到主線程,刷新UI面試

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("handler2 Thread========="+Thread.currentThread().getName());
                handler2 = new Handler(Looper.getMainLooper()){
                    @Override
                    public void handleMessage(Message msg) {
                        if (msg.arg1==1) {
                            Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show();
                        }
                        System.out.println("handler2========="+Thread.currentThread().getName());
                        super.handleMessage(msg);
                    }
                };
                Message message = handler2.obtainMessage();
                message.arg1 = 1;
                handler2.sendMessage(message);
            }
        }).start();
複製代碼

運行上面兩個代碼,打印出線程名稱,能夠看到,handlermessage確實是在main線程(及主線程)中,能夠用來更新UI,運行結果以下所示:數組

09-23 17:06:48.395 15360-15404/com.example.test.myapplication I/System.out: handler2  Thread=========Thread-52863
09-23 17:06:48.410 15360-15360/com.example.test.myapplication I/System.out: handler1=========main
09-23 17:06:48.411 15360-15360/com.example.test.myapplication I/System.out: handler2=========main
複製代碼

結論: 1)若是在主線程調不帶參數的實例化:Handler handler = new Handler();那麼這個會默認用當前線程的looper,從而實現使用主線程Looper,實現刷新UI的功能; 2)若是在其餘線程,也要知足刷新UI的話,要調用Handler handler = new Handler(Looper.getMainLooper()),這樣雖然在子線程中初始化handler,可是仍然用的主線程的Looper對象,實現刷新UI的功能;bash

2.不用刷新ui,只是處理消息 1)若是在主線程中,跟上面同樣,用不帶參數的hanlder構造方法便可;(再也不重複貼代碼) 2)若是在其餘線程,能夠用上面的Looper.getMainLooper()(再也不重複貼代碼)。 3)若是在其餘線程,不用上面的Looper.getMainLooper(),而是在子線程中新建Looper對象,代碼以下所示:app

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("handler2 Thread========="+Thread.currentThread().getName());
                Looper.prepare();
                handler2 = new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        if (msg.arg1==1) {
                            Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show();
                        }
                        System.out.println("handler2========="+Thread.currentThread().getName());
                        super.handleMessage(msg);
                    }
                };
                Message message = handler2.obtainMessage();
                message.arg1 = 1;
                handler2.sendMessage(message);
                Looper.loop();;
            }
        }).start();
複製代碼

運行上面代碼,打印出線程名稱,能夠看到handlermessage在子線程53027中,從而只能用來通知消息,由於不在主線程中,因此不能刷新UI,運行結果以下所示:ide

09-23 17:23:01.298 31434-31523/com.example.test.myapplication I/System.out: handler2  Thread=========Thread-53027
09-23 17:23:01.305 31434-31523/com.example.test.myapplication I/System.out: handler2=========Thread-53027
09-23 17:23:01.318 31434-31434/com.example.test.myapplication I/System.out: handler1=========main
複製代碼

可能有些人在子線程中新建Handler的時候,忘記調用Looper.prepare(),直接跟在主線程中新建Hanlder同樣這樣寫,不少面試官也會問,這樣寫的話會有什麼問題呢:函數

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,崩潰信息以下所示:oop

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();
複製代碼

固然下面這種方式也能夠,直接拿主線程的Looper對象。

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("handler2 Thread========="+Thread.currentThread().getName());
                handler2 = new Handler(Looper.getMainLooper()){
                    @Override
                    public void handleMessage(Message msg) {
                        if (msg.arg1==1) {
                            Toast.makeText(MainActivity.this,"hanlder2",Toast.LENGTH_SHORT).show();
                        }
                        System.out.println("handler2========="+Thread.currentThread().getName());
                        super.handleMessage(msg);
                    }
                };
                Message message = handler2.obtainMessage();
                message.arg1 = 1;
                handler2.sendMessage(message);
            }
        }).start();
複製代碼

直接拿主線程的Looper很簡單,主線程默認自動建立一個Looper,在子線程中獲取到,直接使用。 Looper.prepare();那種方式爲何不報錯了呢,面試的時候,也常常提到,可是爲何這樣寫就能夠呢?下面仔細分析一下。

3.源碼講解

1)剛剛異常報在建立handler 的時候,因此咱們先看下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對象何時爲空,咱們看到第10行有looper的獲取方法mLooper = Looper.myLooper();能夠看出,這個方法獲取到的looper對象爲空,爲啥爲空?咱們看看Looper.myLooper()中的代碼就明白了,以下所示:

public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }
複製代碼

代碼很是少,很容易理解,就是從sThreadLocal對象中取出Looper。(sThreadLocal源碼其實就是個泛型數組,源碼不貼了,把他想成數組就行了。)sThreadLocal何時存在Looper對象呢,及何時會set一個Looper到該數組中呢,根據咱們調用Looper.prepare()方法就不報錯,能夠初步判斷,應該是Looper.prepare()方法中把looper對象放到sThreadLocal中,爲了驗證咱們的猜測,咱們來看下Looper.prepare()的源碼:

3)Looper.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));
    }
複製代碼

從上面的代碼能夠看出,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) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        AndroidKeyStoreProvider.install();

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
複製代碼

咱們能夠看到調用23行出現了Looper.prepareMainLooper();從上面的分析來看,這個方法就是建立主線程的looper對象,咱們來看Looper.prepareMainLooper();源碼

public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
複製代碼

從上面代碼能夠看到,會有prepare(false)方法,又回到建立Looper方法中了,及主線程中會默認爲咱們初始化一個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();
    }
}

複製代碼

有錯誤之處歡迎指正,不斷學習不斷進步。


若有錯誤歡迎指出來,一塊兒學習。

在這裏插入圖片描述
相關文章
相關標籤/搜索