理解Handler和Looper,并学会使用它们

1、关联主线程的Handler

1)关联主线程的Handler,如果你当前处于主线程中,如Activity的onCreate()方法中,直接new一个Handler即可

代码如下:
Handler mHandler = new Handler();

2) 如果你当前没有处于主线程中,那么,你需要传入一个Looper参数进行new一个Handler,代码如下:

Handler mHandler = new Handler(Looper.getMainLooper());
此处调用了Looper中的静态方法getMainLooper()获取与主线程关联的Looper对象,为什么可以获取主线程的Looper呢?请带着疑问,继续阅读下去,后面会解答此问题。

2、关联子线程的Handler

1)前提:创建子线程的Looper,构建子线程的消息机制

既然讲到了消息机制的创建,我们就来研究一下,主线程的消息机制是如何创建的
大家都知道,我们的APP都是从ActivityThread.java中的main() 方法开始运行的
public static void main(String[] args){
	//省略与本文篇无关的代码
	//初始化Looper
    Looper.prepareMainLooper();
	
	//启动Looper.loop(),用于保持心跳,使得main()函数一直在运行
    Looper.loop();
}
上面调用了Looper的两个方法,一个是初始化,一个是启动Looper
- > 我们转到Looper.java中看看prepareMainLooper()这个方法,是如何初始化主线程的Looper的
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }
提前透露一下,sMainLooper 是Looper的一个静态属性,用于保存主线程Looper,从上面的判空及抛异常的内容可以看出来,此处也是防止主线程Looper被修改或者重复创建
- - > 我们一起看看prepare(false)方法
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {	//此处是为了确保主线程的ThreadLocalMap是没有保存Looper类的静态常量属性sThreadLocal和Looper对象的
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));	//此时此处调用,传入了false
    }
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();	//Looper类的静态常量属性
可以看到,上面有一个判断,确保在此之前,是没有将Looper的静态属性sThreadLocal和主线程的 Looper保存进主线程的ThreadLocalMap中,因为sThreadLocal是静态常量,有这个判断在,就确保了一个线程只有一个Looper,一个Looper也只有一个消息队列MessageQueue
我们进入ThreadLocal的get()方法看一看
public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }
它是通过getMap()方法拿到当前线程的ThreadLocalMap对象
ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
ThreadLocal.ThreadLocalMap threadLocals = null;		//Thread.java的属性定义语句
那ThreadLocal.ThreadLocalMap又是什么呢,可以根据Map看出,它是一个Map的数据结构,存储数据用的
//ThreadLocal.ThreadLocalMap的内部静态类,可以看出,此处是对ThreadLocal的弱引用结构
static class Entry extends WeakReference<ThreadLocal<?>> {	
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
        
private Entry[] table;	//ThreadLocal.ThreadLocalMap的私有属性
以上我们可以看出,此对象结构,是ThreadLocal的弱引用派生类,存储了一个对象Object,在此分析流程,我们存储的是Looper对象,为什么我们知道是Looper对象呢?留着这个疑问,我们继续看prepare(false)方法
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {	//此处是为了确保主线程的Looper是sThreadLocal的第一个元素
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));	//此时此处调用,传入了false
    }
ThreadLocal的get()方法,我们上面分析了,我们来分析一下set()方法
public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
此处依旧是获取当前线程,拿到当前线程的ThreadLocalMap,调用ThreadLocalMap的set()方法
private void set(ThreadLocal<?> key, Object value) {
            Entry[] tab = table;
           	//省略部分代码
            tab[i] = new Entry(key, value);
        }
以上可以看出,我们将threadLocal和Looper进行了关联,对ThreadLocal是一个弱引用,为什么使用弱引用呢,我们后面进行分析
总结一下:在ActvityThread.java中main()方法开始,调用了Looper.java的prepareMainLooper()方法来初始化主线程的Looper,以下几点:
1、在prepare()方法中if (sThreadLocal.get() != null) 确保了一个线程只有一个Looper,不赘述了,看上文。
2、主线程的Looper的属性MessageQueue对象,是不能退出的,其他所有创建的MessageQueue都是可以退出的
3、Looper.java类中,保存了主线程的Looper,确保了可以随时随地拿到主线程的Looper,除非APP进程关闭
4、在线程的ThreadLocalMap,对Looper的静态常量属性sThreadLocal是一个弱引用,避免了内存泄漏,线程的生命周期肯定是长的,有的时候可能还要复用,所以,为了避免Looper的内存泄露,对线程和sThreadLocal绑定采用了弱引用的方式。这样既可以通过线程拿到Looper,又可以避免Looper的内存泄露,一举两得。
***************************************************************************************************************************

以上都是对主线程创建Looper的分析,我们回归正传,创建子线程的Looper

创建子线程的Looper非常简单,代码如下:
Looper.prepare();
Looper looper = Looper.myLooper();
在此有一个问题,我们使用Looper就是为了跨线程发送消息,主线程的Looper可以使用Looper.getMainLooper()方法获取,那我们的子线程怎么办?
哈哈,很简单嘛,主线程使用“类+静态变量”来实现,我们一样可以使用同样的方法,如:
class ChildThreadLooper{
	private static Looper oneLooper;
    
    public static void setLooper(Looper looper){
    	synchronized (ChildThreadLooper.class) {
        	ChildThreadLooper.oneLooper = looper;
        }
    }
    
    public static Looper getLooper(){
    	synchronized (Looper.class) {
            return oneLooper;
        }
    }
}
在初始化Looper的时候,调用setLooper()方法,将创建Looper备份一下,让其他线程可以轻松获取到Looper,实现跨线程通信。
***************************************************************************************************************************

以上是如何创建子线程的Looper,我们接下来看一下,如何创建Handler,进行发送消息

1)、如果你当前处于需要关联的子线程中,直接new一个Handler即可,因为Handler的无参构造函数,其中流程,会通过Thread.currentThread()方法获得当前线程,并与之Handler进行绑定,(主线程亦是如此)代码如下:
Handler mHandler = new Handler();
为什么可以直接new,不需要传入Looper,本文前面讲述分析了,得到一个结论:在创建Looper的时候,就会将Looper.sThreadLocal静态常量属性和创建的Looper保存进该线程的threadLocals(ThreadLocalMap的类型)属性中,并且一个线程只能创建也仅有一个Looper。
所以我们可以直接通过Looper的sThreadLocal静态常量属性,调用其get()方法,拿到Looper对象,记住,这一切的前提都是在需要获取的Looper关联的线程中运行。
2)、如果你当前没有处于关联的子线程中,那么,你需要传入一个Looper参数进行new一个Handler,代码如下:
Handler mHandler = new Handler(ChildThreadLooper.getLooper());

3、结束语

通过本篇文章,我们了解了Handler的创建,和Looper的创建,以及Looper的获取,大家现在可以轻松拿到Handler,那发送消息,也是小菜一碟,但我需要说的是,Handler、Looper这个两个知识点,不仅仅这么点东西,比如,还有Looper中的loop()方法,还有Looper的成员属性messageQueue,还有Message,学无止境,希望这篇文章可以帮助到大家,有上面不对的地方,欢迎大佬评论,有疑问的地方,也欢迎评论。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值