深入浅出Handler

故事是从线程开始的。。。

class MyThread implements Runnable {
   @Override
   public void run() {
   // 处理具体的逻辑
   }
}
MyThread myThread = new MyThread();
new Thread(myThread).start();

通过实现一个Runnable接口,而我们 new出的 MyThread正是一个实现了这个接口的对象,再把它传到Thread的构造函数里。老生常谈了,再或者上个更常见的匿名类。

new Thread(new Runnable() {
   @Override
   public void run() {
   // 处理具体的逻辑
   }
}).start();

莫名亲切~
还记得大明湖畔的点Button然后改Hello world为Nice to meet you吗?

系统为什么不允许在子线程中访问UI?

这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态,那么为什么系统不对UI控件的访问加上锁机制呢?缺点有两个:
①首先加上锁机制会让UI访问的逻辑变得复杂 ②锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
所以最简单且高效的方法就是采用单线程模型来处理UI操作。——《Android开发艺术探索》

第一次在子线程中更新UI然后崩掉,就知道不能在子线程中干,还记得我们是怎么解决的么?

new Thread(new Runnable() {
   @Override
   public void run() {
   Message message = new Message();
   message.what = UPDATE_TEXT;
   handler.sendMessage(message); //将Message对象发送出去
   }
}).start();//子线程发球
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
   switch (msg.what) {
    case UPDATE_TEXT:
        // 在这里可以进行 UI 操作
        text.setText("Nice to meet you");
        break;
    default:
        break;
    }
  }
};//主线程接球

话不多说,上代码,两个按钮分别代表不用和用Handler的情况,简陋轻喷~ (移步AS)

刚刚的代码就是采用的使用Handler的一般步骤:
1.创建Handler成员变量对象,并重写其handleMessage()
2.在分/主线程创建Message对象
3.使用handler对象发送Message
4.在handleMessage()中处理消息

下面介绍一下安卓异步消息处理机制中的四个主角:

1. Message:消息
Message是在线程之间传递的消息,随身携带了少量的信息,用于在不同线程之间交换数据。
说明:Message创建对象不是new,而是通过其中一个静态方法obtain,可以减少空间的浪费(后面会讲到)
而且public的优越性使得它不需要set和get方法,从而简化了数据存储和获取的操作。

public int what //id 标识码
public int arg1 //携带整形数据1
public int arg2 //携带整形数据2
public Objec obj//携带一个 Object对象

2.Handler:处理器
Handler是Message的处理器,同时也负责消息的发送和移除的工作
●发送即时消息: sendMessage(Message msg)
●发送延时消息: sendMessageDelayed(Message msg, long time)(延迟处理,而不是延迟发送,都会立即发送)
●处理消息: handleMessage(Message msg) (回调方法)(一般方法,通过对象调用;回调方法,重写)
●移除还未处理的消息: removeMessages(int what)

3.MessageQueue:消息队列
用来存放通过Handler发送的消息,它是一个按Message的when排序的优先级队列,这部分消息会一直存在于消息队列中,等待被处理。
注意:每个线程中只会有一个MessageQueue 对象。

4.Looper:钩子——循环器
负责循环取出MessageQueue里面的当前需要处理的Message,交给对应的Handler进行处理,处理完后,将Message缓存到消息池中,以备复用。

相信你对它们有了一个初步了解,但是只是一个粗糙的轮廓。所以我们来打个形象的比喻~(移步PPT)

众所周知,队列采用先进先出的模式,但是这里的优先级队列并不是完全按放置顺序取,而是按照when排序(eg:A即时 B延时 C即时)即使C在后面也是先取C。——即时消息:when为当前时间;延时消息:when为当前消息+延迟的时间。
真实的数据结构只有两种:数组和链表,其他的队列,堆栈巴拉巴拉的都是程序员们在基础结构之上虚构的,那么要构造队列选什么?顺序表还是链表?毫无疑问选链表,因为插入和移除操作偏多。

刚刚讲的只是最一般的情形,也就是标准流程,但是现实生活总会发生诸多意外,比如客人要求30分钟后人齐了再制作,比如打单机中全是延迟订单。。。怎么办?

不要着急,跟我一起分析源码!就真相大白了!

Message源码

public final class Message implements Parcelable {
	public int what;//标识id
	public int arg1;//保存int数据
	public int arg2;//保存int数据
	public Object obj;//保存任意数据
	public long when;//记录应该被处理的时间
	Handler target;//记录发送消息的那个handler
	Runnable callback;//用来处理消息的回调器(一般不用)
	Message next;//指向下一个message
	private static Message sPool;//消息池
	

obtain()利用了Message中的消息池:

public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

现在就可以理解为什么Message不new?因为每次都可以利用缓存的消息,不用多浪费空间。

Handler源码(发送消息,处理消息,移除消息)

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
//sendMessageAtTime真容如下
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);//将消息添加到消息队列
    }

看吧!它来啦!当订单为延迟订单时,我们会把该订单按时间排序放到订单队列的合适位置,并通过SystemClock.uptimeMillis()定好闹铃。这个uptimeMillis,是从系统启动开始计算的毫秒时间,不受手动调控时间的影响。

public final boolean sendEmptyMessage(int what)
    {
        return sendEmptyMessageDelayed(what, 0);//发送不带数据的消息
    }
//那你又要问了sendEmptyMessageDelayed长什么亚子?👇 
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
        Message msg = Message.obtain();
        msg.what = what;
        return sendMessageDelayed(msg, delayMillis);
    }

发送空的消息和空的延迟消息

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;//保存发送消息的那个handeler对象
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);//调用消息队列保存消息对象
    }

让消息们各就各位

public final void removeMessages(int what) {//移除消息
        mQueue.removeMessages(this, what, null);//调用消息队列移除它内部指定的what
    }
public interface Callback {
        /**
         * @param msg A {@link android.os.Message Message} object
         * @return True if no further handling is desired
         */
        boolean handleMessage(@NonNull Message msg);//处理消息的回调方法
    }

这个handleMessage()是需要我们自己重写的。

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {//如果消息可以自己处理
            handleCallback(msg);//就让它自己处理
        } else {
            if (mCallback != null) {//如果handler对象中有回调监听器,就用回调监听器处理消息
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);//让Handler的handleMessage()来处理
        }
    }

Handler的灵魂在于分派调度任务、定时循环,其实前两个方法基本用不上,存在的意义在于容错,基本都是用自己重写的handleMessage()方法。

MessageQueue源码(存储的以when排序的Message优先级队列)

boolean enqueueMessage(Message msg, long when) {//将Message添加到队列中
	msg.when = when;//指定消息应该被处理的时间
	for (;;) {//无限循环,将当前消息保存到消息队列中的合适位置
        prev = p;
        p = p.next;
        if (p == null || when < p.when) {
            break;
        }
        if (needWake && p.isAsynchronous()) {
            needWake = false;
        }
    }
    if (needWake) {
        nativeWake(mPtr);//本地方法,处理等待状态的底层线程
    }
}
Message next() {//取出一个合适的Message对象,可能不会立即返回
	nativePollOnce(ptr, nextPollTimeoutMillis);//会导致可能处于等待状态,但不会阻塞主线程
	Message msg = mMessages;//取出消息队列中的第一个消息
	return msg;//返回
}

Looper在哪个线程创建,就跟哪个线程绑定,并且Handler是在他关联的Looper对应的线程中处理消息的。Handler内部如何获取到当前线程的Looper呢—–ThreadLocal。ThreadLocal可以在不同的线程中互不干扰的存储并提供数据,通过ThreadLocal可以轻松获取每个线程的Looper。当然需要注意的是①线程是默认没有Looper的,如果需要使用Handler,就必须为线程创建Looper。我们经常提到的主线程,也叫UI线程,它就是ActivityThread,②ActivityThread被创建时就会初始化Looper,这也是在主线程中默认可以使用Handler的原因。

压轴出场!!!
Looper源码(从MessageQueue中获取当前需要处理的消息,并交给Handler处理)

public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;//得到消息队列对象
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
        }
        msg.target.dispatchMessage(msg);//调用Handler去分发并处理消息
        msg.recycleUnchecked();//回收利用Message
}

引用文献:
5分钟了解Handler机制,Handler的错误使用场景

腾讯安卓面试

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值