Android Handler消息机制01-Message源码学习

0. 系列文章汇总

1. 背景

背景是在实际开发中,有一个Message消息在主线程接收到了后,将Message切换到子线程去处理,但是出现了Message.obj被串改或者被置空,导致出现NullPointException或者ClassCastException,此时正好从源码分析下出现的原因和优化方法。

2. Message源码解析

Message 在Handler机制中是用于传递信息的,位于android.os.Message

package android.os;
/**
 * 存储消息对象的实体类,用于发送至Handler,包括两个额外的整形对象arg1,arg2,
 * 以及额外Object对象-obj满足大多数场景的消息数据存储。
 *
 * 尽管Message的构建方法是Public,但是最好的构建方法是通过Message.obtain()或Handler.obtainMessage()
 */
public final class Message implements Parcelable {

    /**
     * 用于定义消息类别,每个Handler都有各自的消息类别,互不冲突,为必选参数
     */
    public int what;

    /**
     * arg1,arg2是用于存储少量整形数据的setData()的轻量替代方案
     */
    public int arg1;

    public int arg2;

    /**
     * 用于发送给Handler接收者的Object对象,如果跨进程的话只能是Parcelable对象(继承不行)
     */
    public Object obj;

    /**
     * 额外的消息接收者,具体使用看发送者和接收者的自定义
     */
    public Messenger replyTo;

    /**
     * 定义uid的默认值,仅用于系统服务
     */
    public static final int UID_NONE = -1;

    /**
     * 额外参数用于定义发送者的uid,仅当replayTo被定义是合法的。否则应该定义为默认值-1.
     */
    public int sendingUid = UID_NONE;

    /**
     * 可选字段,指示导致该消息进入队列的uid,仅用于系统服务
     */
    public int workSourceUid = UID_NONE;

    /** If set message is in use.
     * This flag is set when the message is enqueued and remains set while it
     * is delivered and afterwards when it is recycled.  The flag is only cleared
     * when a new message is created or obtained since that is the only time that
     * applications are allowed to modify the contents of the message.
     *
     * It is an error to attempt to enqueue or recycle a message that is already in use.
     */
    /**
     * 当消息被加入Handler队列及分发、回收时被设置,表示此消息正在被使用,此标志只有新消息被创建,obtain时才能被清理,
     * 因为只有此时才允许程序去修改消息内容
     */
    /*package*/ static final int FLAG_IN_USE = 1 << 0;

    /** 异常标志 */
    /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;

    /**在copyFrom方法清理 flags属性*/
    /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;

    @UnsupportedAppUsage
    /*package*/ int flags;

    /**
     * 设置消息分发时间,仅在测试时使用
     */
    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;

    /*package*/ Bundle data;

    @UnsupportedAppUsage
    /*package*/ Handler target;

    @UnsupportedAppUsage
    /*package*/ Runnable callback;

    // Message对象链表的下一个消息对象
    @UnsupportedAppUsage
    /*package*/ Message next;

    /** 类锁 @hide */
    public static final Object sPoolSync = new Object();
    /**
     * 静态对象,消息池,用链表存储
     */
    private static Message sPool;

    /**
     * 消息池大小
     */
    private static int sPoolSize = 0;

    /**
     * 消息池Size最大值
     */
    private static final int MAX_POOL_SIZE = 50;

    private static boolean gCheckRecycle = true;

    /**
     * 从全局消息池中返回一个新的Message对象。避免在许多场景分配新对象
     * @return 从消息池中返回的消息对象
     */
    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();
    }

    /**
     * obtain的重载方法,支持从已存在的消息对象中复制一个新的消息对象
     *
     * @param orig 原始消息对象
     * @return 从全局池中返回的消息对象
     */
    public static Message obtain(Message orig) {
        Message m = obtain();
        m.what = orig.what;
        m.arg1 = orig.arg1;
        m.arg2 = orig.arg2;
        m.obj = orig.obj;
        m.replyTo = orig.replyTo;
        m.sendingUid = orig.sendingUid;
        m.workSourceUid = orig.workSourceUid;
        if (orig.data != null) {
            m.data = new Bundle(orig.data);
        }
        m.target = orig.target;
        m.callback = orig.callback;

        return m;
    }

    /**
     * obtain的重载方法,支持从已存在的消息对象中复制一个新的消息对象
     *
     * @param h 消息的目标Handler
     * @return 从全局池中返回的消息对象
     */

    public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;
        return m;
    }

    /**
     * 重载方法,支持设置目标Handler以及消息处理时的回调方法
     *
     * @param h  目标Handler
     * @param callback 消息处理时的回调方法
     * @return A Message object from the global pool.
     */
    public static Message obtain(Handler h, Runnable callback) {
        Message m = obtain();
        m.target = h;
        m.callback = callback;

        return m;
    }

    public static Message obtain(Handler h, int what) {
        Message m = obtain();
        m.target = h;
        m.what = what;

        return m;
    }

    public static Message obtain(Handler h, int what, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.obj = obj;
        return m;
    }

    public static Message obtain(Handler h, int what, int arg1, int arg2) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;

        return m;
    }

    public static Message obtain(Handler h, int what,
                                 int arg1, int arg2, Object obj) {
        Message m = obtain();
        m.target = h;
        m.what = what;
        m.arg1 = arg1;
        m.arg2 = arg2;
        m.obj = obj;

        return m;
    }


    /**
     * 更新checkRecycle标志值,仅当sdkVersion<21(android 5)时才生效
     *
     * @param targetSdkVersion 目标SDK版本
     */
    /** @hide */
    public static void updateCheckRecycle(int targetSdkVersion) {
        if (targetSdkVersion < Build.VERSION_CODES.LOLLIPOP) {
            gCheckRecycle = false;
        }
    }

    /**
     * 回收一个消息对象到消息池中,添加到队尾
     * 当调用回收方法,消息已经被回收,不再允许再使用此消息
     */
    public void recycle() {
        /**
         * 如果在使用,则抛出IllegalStateException异常
         */
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        recycleUnchecked();
    }

    /**
     * 回收可能在使用的消息,清理其所有相关内容,且将其添加在消息池的队首
     * 当处理队列消息时,由MessageQueue和Looper在内部使用。
     */
    @UnsupportedAppUsage
    void recycleUnchecked() {
        // 标记此消息正在被使用,当其存在在消息池中时,初始化消息所有内容
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = UID_NONE;
        workSourceUid = UID_NONE;
        when = 0;
        target = null;
        callback = null;
        data = null;
        // 其添加在消息池的队首,多线程安全
        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

    /**
     * 从 对象O中浅拷贝一个消息对象,但是不包括原始消息的消息链表next,及时间next,target/callback
     * @param o 原始消息对象
     */
    public void copyFrom(Message o) {
        this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
        this.what = o.what;
        this.arg1 = o.arg1;
        this.arg2 = o.arg2;
        this.obj = o.obj;
        this.replyTo = o.replyTo;
        this.sendingUid = o.sendingUid;
        this.workSourceUid = o.workSourceUid;

        if (o.data != null) {
            this.data = (Bundle) o.data.clone();
        } else {
            this.data = null;
        }
    }

    /**
     * 返回分发的时间,单位:毫秒
     */
    public long getWhen() {
        return when;
    }

    public void setTarget(Handler target) {
        this.target = target;
    }

    /**
     * 这个对象发现实现了Handler.handleMessage()方法,每个Handler都有自己的命名空间,
     * 不必担心与其他Handler冲突
     *
     * @return 消息处理对象
     */
    public Handler getTarget() {
        return target;
    }

    /**
     * callback对象在对象被处理时被调用,如果未被设置,则在仅调用Handler#handleMessage(Message)
     * @return callback对象
     */
    public Runnable getCallback() {
        return callback;
    }

    /** @hide */
    @UnsupportedAppUsage
    public Message setCallback(Runnable r) {
        callback = r;
        return this;
    }

    /**
     * 获取消息相关的Bundle对象,当跨进程使用时,需要调用Bundle#setClassLoader(ClassLoader)
     * 他是懒汉模式来设置,当data为空时会主动为其初始化
     *
     * @return Bundle对象
     */
    public Bundle getData() {
        if (data == null) {
            data = new Bundle();
        }

        return data;
    }

    /**
     * 类似于data,但是会直接返回,有可能返回空对象
     * @return Bundle对象
     */
    public Bundle peekData() {
        return data;
    }

    /**
     * 设置Bundle对象,在仅保存少量的整形数据时,可使用arg1和arg2代替。
     * @param data Bundle对象
     */
    public void setData(Bundle data) {
        this.data = data;
    }

    public Message setWhat(int what) {
        this.what = what;
        return this;
    }

    /**
     * 将此消息发送给指定的Handler,当未指定时会抛出空指针异常
     */
    public void sendToTarget() {
        target.sendMessage(this);
    }

    /**
     * 当消息是异步的,即不受Looper 同步栅栏的影响,则返回true。
     * @return true:消息是异步的
     */
    public boolean isAsynchronous() {
        return (flags & FLAG_ASYNCHRONOUS) != 0;
    }

    /**
     * 设置消息消息是否是异步的,即是否受Looper同步栅栏的影响。
     * 某些操作,比如视图失效,可能会在{@link Looper}的消息队列中引入同步障碍,以防止后续消息在满足某些条件之前被传递。
     * 异步消息免于同步障碍。它们通常表示中断、输入事件和其他必须独立处理的信号,即使其他工作已经暂停
     * 
     * 请注意,异步消息可能会按照同步消息的顺序传递,尽管它们之间总是按照顺序传递的。
     * 如果这些消息的相对顺序很重要,那么他们可能一开始就不应该异步。谨慎使用。
     *
     * @param async true:消息是异步的
     */
    public void setAsynchronous(boolean async) {
        if (async) {
            flags |= FLAG_ASYNCHRONOUS;
        } else {
            flags &= ~FLAG_ASYNCHRONOUS;
        }
    }

    /*package*/ boolean isInUse() {
        return ((flags & FLAG_IN_USE) == FLAG_IN_USE);
    }

    /**
     * 设置消息在使用,不允许被应用调用
     */
    @UnsupportedAppUsage
        /*package*/ void markInUse() {
        flags |= FLAG_IN_USE;
    }

    /** 构造方法
     * 但是更建议通过obtain来获取
     */
    public Message() {
    }

    @Override
    public String toString() {
        return toString(SystemClock.uptimeMillis());
    }

    @UnsupportedAppUsage
    String toString(long now) {
        StringBuilder b = new StringBuilder();
        b.append("{ when=");
        TimeUtils.formatDuration(when - now, b);

        if (target != null) {
            if (callback != null) {
                b.append(" callback=");
                b.append(callback.getClass().getName());
            } else {
                b.append(" what=");
                b.append(what);
            }

            if (arg1 != 0) {
                b.append(" arg1=");
                b.append(arg1);
            }

            if (arg2 != 0) {
                b.append(" arg2=");
                b.append(arg2);
            }

            if (obj != null) {
                b.append(" obj=");
                b.append(obj);
            }

            b.append(" target=");
            b.append(target.getClass().getName());
        } else {
            b.append(" barrier=");
            b.append(arg1);
        }

        b.append(" }");
        return b.toString();
    }

    /**
     * 下面是跨进程相关的Parcelable方法,模板方法(未被加入)
     */
}

3.出现NullPointException或者ClassCastException原因解析

首先明确下recycle()什么时候会被调用
recycle()被调用也即意味着这个消息对象会清空,并重新加入到消息池中等待调用。这就意味着某个Message对象在回收之后再使用此消息会出现空指针异常或者其他未知的异常。

  1. 当Handler指定移除单条消息,或所有消息的时候
void removeMessages(Handler h, int what, Object object)
void removeMessages(Handler h, Runnable r, Object object)
void removeCallbacksAndMessages(Handler h, Object object)
  1. 当android.os.Looper#loop() 循环处理消息最后也会调用msg.recycleUnchecked(),
    这也即意味着Message不要去切换线程处理,有可能会导致多线程安全
    public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); // might block
            /**
             * 中间是处理消息的代码,省略,关键方法消息分发方法:msg.target.dispatchMessage(msg)
             */
            //loop()最后会回收消息重新放入消息池中
            msg.recycleUnchecked();
        }
    }

出现异常触发点
其实会出现如上问题的原因在如上Loop.loop()的源码 已经说明了。获取Message是通过Message.obtain()来从线程池来获取的,而Loop.loop()方法在调用了Mesesage.target.handlerMessage()后会在最近调用msg.recycleUnchecked()重新回收消息对象到消息池中,所以在频繁给同一个Looper发送消息的场景。如果HandlerMessage()方法有切子线程操作的操作,有可能会出现NullPointerException(此时Meesage 对象的相关属性值被置空),或者ClassCastException(此时已经被下一次的obtain获取并重新赋值)。

参考资料

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值