带你真正攻克Handler

先来一个自己画的Handler机制整体流程图,本文不会带着你走一遍源码,只会对重点需要注意的地方以及一些细节的处理做出解释,让你更好的了解Handler机制整体的运作
在这里插入图片描述

  • Handler通过sendMessage()发送Message到MessageQueue队列;
  • Looper通过loop(),不断提取出达到触发条件的Message,并将Message交给target来处理;
  • 经过dispatchMessage()后,交回给Handler的handleMessage()来进行相应地处理。
  • 将Message加入MessageQueue时,处往管道写入字符,可以会唤醒loop线程;
  • 如果MessageQueue中没有Message,并处于Idle状态,则会执行IdelHandler接口中的方法,往往用于做一些清理性地工作。

下边放几个需要注意的Handler知识点:

  1. Handler 的背后有 Looper、MessageQueue 支撑,Looper 负责消息分发,MessageQueue 负责消息管理;
  2. 在创建 Handler 之前一定需要先创建 Looper;
  3. Looper 有退出的功能,但是主线程的 Looper 不允许退出;
  4. 异步线程的 Looper 需要自己调用 Looper.quit(); 退出;
  5. Runnable 被封装进了 Message,可以说是一个特殊的 Message;
  6. Handler.handleMessage() 所在的线程是 Looper.loop() 方法被调用的线程,也可以说成 Looper 所在的线程,并不是创建 Handler 的线程,Handler新建时持有的Looper在哪个线程,最后Handler.handleMessage()就在哪个线程执行
  7. 使用内部类的方式使用 Handler 可能会导致内存泄露,即便在 Activity.onDestroy 里移除延时消息,必须要写成静态内部类;

为什么需要使用Handler

因为Android系统不允许在非UI线程更新UI,因为如果多个线程同时改变View的状态会造成最终View状态的不确定性,如果给每个View的操作都上锁的话那么势必会造成性能的损耗,所以干脆规定只能在UI线程去更新UI,而Handler就是用来进行线程切换操作的。
使用方法

class LooperThread extends Thread {
    public Handler mHandler;


    public void run() {
        Looper.prepare();  
        mHandler = new Handler() {  
            public void handleMessage(Message msg) {
                //TODO 定义消息处理逻辑.
            }
        };
        Looper.loop();  
    }
}

主线程中可以使用Handler的原因是在ActivityThread中程序的入口main方法中调用了Looper.prepare();和Looper.loop();

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();

Looper

  • Looper.prepare()
    Looper.prepare()在每个线程只允许执行一次,该方法给当前线程通过TL绑定一个线程所属的唯一一个实例。
private static void prepare(boolean quitAllowed) {
    //看当前线程是否已通过TL绑定对应的实例,有的话抛异常,所以prepare方法只允许调用一次
    if (sThreadLocal.get() != null) {
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    //创建Looper对象,并通过TL建立与线程的绑定关系
    sThreadLocal.set(new Looper(quitAllowed));
}
  • ThreadLocal
    我们看一下ThreadLocal.set方法
public void set(T value) {
   Thread t = Thread.currentThread();//获取当前线程
   ThreadLocalMap map = getMap(t);//获取当前线程所属的ThreadLocalMap实例,键值对结构
   if (map != null)
       map.set(this, value); //以当前ThreadLocal作为键,Looper作为值建立绑定关系
   else
       createMap(t, value);
   }
}

ThreadLocal.get方法

public T get() {
    Thread t = Thread.currentThread();//获取当前线程
    ThreadLocalMap map = getMap(t);//获取当前线程所属的ThreadLocalMap实例,键值对结构
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//通过当前ThreadLocal作为键取出对应的值
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

为什么要选择ThreadLocal建立绑定关系?

因为我们是要让每一个线程都有且只有一个唯一的Looper实例,这时就可以使用ThreadLocal给每个线程绑定一个唯一实例的特性很方便的建立绑定关系。如果不采用ThreadLocal去实现,那么只能使用一个LooperManager管理类然后通过其中的Map去统一管理,那么这样无疑是很麻烦的。ThreadLocal只是作为主键,如果是Thread作为主键,那么很显然一个线程只能与一个对应的对象建立绑定关系,这显然是非常不合理的。

  • Looper.loop()
    loop()进入循环模式,主要进行了如下几点:
    1. 获取当前线程的Looper实例
    2. 通过Looper获取MessageQueue实例
    3. 开启死循环并在其中调用MessageQueue的next方法不断轮询MessageQueue的头结点
public static void loop() {
    final Looper me = myLooper();  //获取TLS存储的Looper对象 -->sThreadLocal.get()
    final MessageQueue queue = me.mQueue;  //获取Looper对象中的消息队列


    Binder.clearCallingIdentity();
    //确保在权限检查时基于本地进程,而不是调用进程。
    final long ident = Binder.clearCallingIdent
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值