源码角度讲解子线程建立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();
    }
}

复制代码

有错误之处欢迎指正,不断学习不断进步。


若有错误欢迎指出来,一块儿学习。

在这里插入图片描述
相关文章
相关标签/搜索