一文搞懂Handler线程切换奥妙

网上关于Handler的使用及原理文章很多,都讲得不错,但关于主线程和子线程切换方面都是一笔带过,不够清晰易懂。回顾一下Handler消息机制,其组成元素:HandlerLooperMessageQueueMessage,主要作用:发送和处理消息,Looper会在一个无限循环中不断从MessageQueue中获取Message(它的target参数持有是发送它的Handler对象),交给对应的Handler去处理,最终回调Handler。

其实Handler主线程和子线程切换主要依靠ThreadLocal,Looper使用了ThreadLocal,代码如下:

  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));//重点
    }

关于ThreadLocal的原理这里不多说,官方解释如下:

Implements a thread-local storage, that is, a variable for which each thread
has its own value. All threads share the same {@code ThreadLocal} object,
but each sees a different value when accessing it, and changes made by one
thread do not affect the other threads. The implementation supports

这句话大概意思是:不同线程访问时取得不同的值,任意线程对它的改变不影响其他线程的值。类实现是支持null值的。

ThreadLocal在Handler消息机制中可以这么理解:一个线程内部的存储类,可以在指定线程内存储数据,数据存储以后,只有指定线程可以得到存储数据。简单来说就是使用ThreadLocal,在主线程中创建对象,就与主线程有关,且实例不变,在不同线程创建对象属于不同线程。 Looper在主线程创建那么与主线程有关,处理消息的时候当然是回调在主线程,反之亦然。

举个例子:

package com.sjl.test.designpatterns.singleton;

/**
 *使用ThreadLocal实现单例模式,不支持在不同线程使用
 * @author Kelly
 * @version 1.0.0
 * @filename Signleton9.java
 * @time 2020/5/13 9:47
 * @copyright(C) 2020 song
 */
public class Singleton9 {
    private static final ThreadLocal<Singleton9> signleton = new ThreadLocal(){
        @Override
        protected Object initialValue() {
            return new Singleton9();
        }
    };

    private Singleton9() {

    }

    public static Singleton9 getInstance(){
        return signleton.get();
    }
}

测试程序:

public class SingletonTest {
    public static void main(String[] args) {
        System.out.println("Singleton9:"+ Singleton9.getInstance());
        System.out.println("Singleton9:"+Singleton9.getInstance());
        System.out.println("=======");
        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                Singleton9 instance = Singleton9.getInstance();
                System.out.println("Singleton9:"+instance);

            }).start();
        }
    }
}

输出结果:
在这里插入图片描述
从上图可以看出第1-2行是在主线程,实例不变。第4-5行是在不同子线程创建,实例不同,只要子线程创建了,一直没有终止,Singleton9.getInstance()的实例都是相同的。

下面在手写个Handler消息机制例子,模拟Android中如何切换主线程。

package com.sjl.view.activity;

import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

import com.sjl.view.R;
import com.sjl.view.widget.MyDialog;

import java.util.LinkedList;

/**
 * TODO
 *
 * @author Kelly
 * @version 1.0.0
 * @filename EqActivity.java
 * @time 2020/5/12 10:26
 * @copyright(C) 2020 song
 */
public class EqActivity extends AppCompatActivity {
    private static final String TAG = "EqActivity";
    final ThreadLocal<Looper> testThreadLocal = new ThreadLocal<Looper>() {
        @Override
        protected Looper initialValue() {
            return new Looper();
        }
    };

    private MyHandler myHandler;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.eq_activity);


        //Looper在主线程创建,故返回的消息在主线程
        final Looper looper = testThreadLocal.get();
        //创建Handler
        myHandler = new MyHandler(looper) {
            @Override
            public void handleMsg(String msg) {
                super.handleMsg(msg);
                Toast.makeText(EqActivity.this, msg, Toast.LENGTH_LONG).show();
            }
        };
        //子线程网络请求,模拟业务数据
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                myHandler.sendMsg("hello");//子线程发送消息到主线程
            }
        }).start();
        //下面为了方便演示,省掉了循环,假设是Looper循环取消息,模拟消息返回
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String msg = looper.getMsg();
        myHandler.handleMsg(msg);

       /* //Looper循环取消息,模拟消息返回,这里在public static void main(String[] args) 测试
        looper.loop();*/
    }


    public class MessageQueue {
        /**
         * 消息队列对应Android 中的类MessageQueue,android使用native方法,nativePollOnce(阻塞),nativeWake(唤醒)
         */
        private BlockingQueue<String> msgList = new ArrayBlockingQueue<>(100);

        /**
         * 取消息
         *
         * @return
         */
        public String next() {
            try {
                return msgList.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return null;
        }

        /**
         * 消息入队
         *
         * @param msg
         */
        public void enqueueMessage(String msg) {
            try {
                msgList.put(msg);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }

    public class MyHandler {

        Looper looper;

        public MyHandler(Looper looper) {
            this.looper = looper;
        }

        /**
         * 发送消息
         *
         * @param msg
         */
        public void sendMsg(String msg) {
            this.looper.messageQueue.enqueueMessage(msg);
        }

        /**
         * 消息回调
         *
         * @param msg
         */
        public void handleMsg(String msg) {

        }

    }

    /**
     * 循环器
     */
    public class Looper {

        private MessageQueue messageQueue = new MessageQueue();

        public void loop() {
            for (; ; ) {
                // might block
                String msg = messageQueue.next();
                if (msg == null) {
                    return;
                }
                myHandler.handleMsg(msg);
            }
        }
        public String getMsg() {
            String msg = messageQueue.next();
            return msg;
        }
    }
}


上面只是个简单例子,来进一步说明Handler如何线程切换。

另外再补充一点,我们平时在经常在主线程使用 Handler,所以导致了很少用到 Looper.prepare() 方法,是因为ActivityThread主线程中默认调用Looper.prepareMainLooper()
如下代码:

	frameworks/base/core/java/android/app/ActivityThread.java
    public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        ...
        Looper.prepareMainLooper();

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if (args != null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

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

竟然主线程可以创建Handler,反之亦然,但是在子线程不能直接创建Handler,会空指针异常,因为Looper没有实例化),正常这样写:

new Thread(new Runnable() {
		            @Override
		            public void run() {
		                // 在当前线程创建Looper对象
		                Looper.prepare();
		                // 创建Handler对象传入当前线程的Looper
		                Handler handler = new Handler(Looper.myLooper()){
				            @Override
				            public void handleMessage(Message msg) {//运行在子线程
				                super.handleMessage(msg);
				            }
				        };
		                // 开始循环消息队列
		                Looper.loop();
		            }
		}).start();

如果我们要在子线程创建Handler,推荐使用封装好的HandlerThread创建,如下:

 	    HandlerThread handlerThread = new HandlerThread("hello");
        handlerThread.start();
        Handler handler = new Handler(handlerThread.getLooper()){
            @Override
            public void handleMessage(Message msg) {//运行在子线程
                super.handleMessage(msg);
            }
        };

下面说下应用场景:
主线程创建Handler,可以在子线程或主线程使用Handler 发送消息并回调handleMessage(运行在主线程,可更新UI),比如:网络请求,文件处理等;
子线程创建Handler,可以在子线程或主线程使用Handler 发送消息并回调handleMessage(运行在子线程,不可更新UI),比如:处理相机预览帧的数据,子线程间数据通讯

小结

上面通过主要讲解了Handler如何进行主子线程切换,如有写得不对,请指出不足。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值