欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Handler消息机制二——子线程下如何使用Handler

程序员文章站 2022-07-14 19:28:04
...

关于Handler,在哪个线程里面new一个Handler,那么这个Handler对象就运行在哪个线程下,比如,我们通常在主线程里面new一个Handler,那么这个Handler对象就运行在主线程中。同理的如果我们在子线程里面new一个Handler,那么Handler对象就运行在子线程中。不同的是,我们不能直接在子线程里面new一个Handler,因为:

在应用App启动的时候,会在执行程序的入口ActivityThread.class类中主函数public static void main(String[] args)里面创建一个Looper对象:Looper.prepareMainLooper(),然后Looper.loop();完成Looper对象的创建。实际上Looper.prepareMainLooper()方法还是调用了Looper的prepare()方法完成Looper对象的创建。因此在主线程中通过关键字new创建的Handler对象之前,Looper对象已经存在并始终存在。

在子线程中,直接new Handler:

new Thread(new Runnable() {
    @Override
    public void run() {
        //Looper.prepare();//Looper初始化
        //Handler初始化 需要注意, Handler初始化传入Looper对象是子线程中缓存的Looper对象
        mHandler = new Handler(Looper.myLooper());
        //Looper.loop();//死循环
        //注意: Looper.loop()之后的位置代码在Looper退出之前不会执行,(并非永远不执行)
    }
}).start();

则运行时会报错,因为在子线程中通过关键字new创建的Handler对象时,子线程里面的Looper对象是没有被创建的,不存在的,直接使用的话,会报异常:

java.lang.NullPointerException: Attempt to read from field 'android.os.MessageQueue android.os.Looper.mQueue' on a null object reference
    at android.os.Handler.<init>(Handler.java:233)
    at android.os.Handler.<init>(Handler.java:137)
    at com.haoyue.demolist.base.MainActivity$2.run(MainActivity.java:52)
    at java.lang.Thread.run(Thread.java:833)

大致意思是说,Looper.mQueue对象为空,而第52行:

mHandler = new Handler(Looper.myLooper());

正是我们在创建Handler对象的地方。

如果要在子线程中创建Handler对象,那么就需要在创建前添加代码:Looper.prepare();来解决问题:

    new Thread(new Runnable() {
        @Override
        public void run() {
            Looper.prepare();//Looper初始化
            //Handler初始化 需要注意, Handler初始化传入Looper对象是子线程中缓存的Looper对象
            mHandler = new Handler(Looper.myLooper());
            Looper.loop();//死循环
            //注意: Looper.loop()之后的位置代码在Looper退出之前不会执行,(并非永远不执行)
        }
    }).start();

完整的测试代码:

public class MainActivity extends BaseActivity {
    Handler mHandler;
    int count = 5;

    Handler mainHandler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {

            return false;
        }
    });

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//Looper初始化
                //Handler初始化 需要注意, Handler初始化传入Looper对象是子线程中缓存的Looper对象
                mHandler = new Handler(Looper.myLooper());
                Looper.loop();//死循环
                //注意: Looper.loop()之后的位置代码在Looper退出之前不会执行,(并非永远不执行)
            }
        }).start();

    childThread();
}

    private void childThread() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();//Looper初始化
                //Handler初始化 需要注意, Handler初始化传入Looper对象是子线程中缓存的Looper对象
                mHandler = new Handler(Looper.myLooper());
                Looper.loop();//死循环
                //注意: Looper.loop()之后的位置代码在Looper退出之前不会执行,(并非永远不执行)
            }
        }).start();

        //主线程 注意此处在主线程延时一秒才给子线程发消息是因为子线程创建需要时间, 立刻发消息Handler还么没准备好, 会抛出异常.
        Log.i(TAG, "count: ---> " + " ThreadName: " + Thread.currentThread().getName());
        //MainThread是我自定义的一个类, 内部缓存了一主线程Handler
        mainHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //使用子线程中的mHandler给子线程发一个延时消息
                mHandler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        if (count >= 0) {
                            //打印当前线程名
                            Log.i(TAG, "count: ---> " + count + " ThreadName: " + Thread.currentThread().getName());
                            count--;
                            //继续给子线程发消息
                            mHandler.postDelayed(this, 1000);
                        } else if (count >= -10) {
                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
                                mHandler.getLooper().quitSafely();
                            } else {
                                mHandler.getLooper().quit();
                            }
                            Log.i(TAG, "quit: count: ---> " + count + " ThreadName: " + Thread.currentThread().getName());
                            count--;
                            //继续给子线程发消息
                            mHandler.postDelayed(this, 1000);
                        }
                    }
                }, 1000);
            }
        }, 1000);
    }
}

同理的,要在子线程中使用Toast,也需要这样处理:

    newThread(new Runnable() {
       @Override
       public void run() {
           Looper.prepare();
           Toast.makeText(MainActivity.this, "子线程显示", Toast.LENGTH_SHORT).show();
           Looper.loop();
       }
    }).start();

否则,会报异常:

Can't create handler inside thread that has not called Looper.prepare()

注:在SDK27版本下:

newThread(new Runnable() {
    @Override
    public void run() {
        Toast.makeText(MainActivity.this, "子线程显示", Toast.LENGTH_SHORT).show();
    }
 }).start();

上述Toast没有反应,但是不抛异常。不知道是否新版的SDK做了更改,后续再阅读源码分析这个问题。