Android开发艺术探索(十)

来源:互联网 发布:js 移动端 上传照片 编辑:程序博客网 时间:2024/06/07 10:21

Andrid的消息机制

消息机制概述

Android的消息机制主要是指Handler的运行机制以及Handler所附带的MessageQueue和Looper的工作过程。
Handler的主要作用是将一个任务切换到某个指定的线程中去执行。

Android规定访问UI只能在主线程中进行,如果在子线程中访问UI,程序就会排除异常。ViewRootImpl对UI操作做了验证,这个验证工作是由ViewRootImpl的checkThread方法来完成的。

 void checkThread() {        if (mThread != Thread.currentThread()) {            throw new CalledFromWrongThreadException(                    "Only the original thread that created a view hierarchy can touch its views.");        }    }

Android系统不允许在子线程中访问UI?

这是因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致UI控件处于不可预期的状态。

为什么系统不对UI控件的访问加上锁机制呢?

1、加上锁机制会阻塞某些线程的执行
2、锁机制会降低UI访问的效率,因为锁机制会阻塞某些线程的执行。
鉴于这两个缺点,最简单高效的方法就是采用单线程模型来处理UI操作,这个时候我们只需要使用Handler切换UI访问的执行线程即可。

Android的消息机制分析

ThreadLocal的工作原理

ThreadLocal是一个线程内部的数据存储类,通过它可以在指定的线程中存储数据,存储数据以后,只有在指定线程中可以获取到存储的数据,对于其他线程来说则无法获取到数据。
我们来做一个例子:首先定义一个ThreadLocal对象,这里选择Boolean类型的。如下:

private ThreadLocal<Boolean> mBooleanThreadLocal=new ThreadLocal<>();

然后分别再主线程、子线程1、子线程2中设置和访问它的值,代码如下:

mBooleanThreadLocal.set(true);        Log.e(TAG,"{Thread#main}mBooleanThreadLocal="+mBooleanThreadLocal.get());        /**thread1*/        new Thread(){            @Override            public void run() {                mBooleanThreadLocal.set(false);                Log.e(TAG,"{Thread#1}mBooleanThreadLocal="+mBooleanThreadLocal.get());            }        }.start();        /**thread2*/        new Thread(){            @Override            public void run() {                Log.e(TAG,"{Thread#2}mBooleanThreadLocal="+mBooleanThreadLocal.get());            }        }.start();

运行结果

我们来分析一下ThreadLocal的内部实现:

/**泛型类*/public class ThreadLocal<T> {/**get方法获取值*/  public T get() {        // Optimized for the fast path.        Thread currentThread = Thread.currentThread();        Values values = values(currentThread);        if (values != null) {            Object[] table = values.table;            int index = hash & values.mask;            if (this.reference == table[index]) {                return (T) table[index + 1];            }        } else {            values = initializeValues(currentThread);        }        return (T) values.getAfterMiss(this);    }/**set方法设置值*/    public void set(T value) {    //获取当前运行的线程        Thread currentThread = Thread.currentThread();        Values values = values(currentThread);        if (values == null) {            values = initializeValues(currentThread);        }        values.put(this, value);    }}

从ThreadLocal的get和set方法中可以看到他们所操作的对象都是当前线程的localValues对象的table数组,因此在不同的线程中访问同一个ThreadLocal的set和get方法,他们对ThreadLocal所做的读/写操作仅限于个子线程的内部!所以ThreadLocal可以在多个线程中互不干扰的存储和修改数据。

消息队列的工作原理
消息队列在Android中指的是MessageQueue,MessageQueue包含两个操作:插入和读取。读取操作本身会伴随着删除操作。

插入的方法为:enqueueMessage,作用是往消息队列中插入一条消息。
读取的方法为:next,作用是从消息队列中读取出一条消息并将其从消息队列中移除。
MessageQueue内部实现是通过一个单链表的数据结构来维护消息列表,单链表在插入和删除上比较有优势。
MessageQueue的enqueueMessage方法:

  boolean enqueueMessage(Message msg, long when) {       ....        synchronized (this) {         ....            msg.markInUse();            msg.when = when;            Message p = mMessages;            boolean needWake;            if (p == null || when == 0 || when < p.when) {                msg.next = p;                mMessages = msg;                needWake = mBlocked;            } else {                needWake = mBlocked && p.target == null && msg.isAsynchronous();                Message prev;                for (;;) {                    prev = p;                    p = p.next;                    if (p == null || when < p.when) {                        break;                    }                    if (needWake && p.isAsynchronous()) {                        needWake = false;                    }                }                msg.next = p; // invariant: p == prev.next                prev.next = msg;            }            if (needWake) {                nativeWake(mPtr);            }        }        return true;    }

MessageQueue的next方法:

   Message next() {        final long ptr = mPtr;        if (ptr == 0) {            return null;        }        int pendingIdleHandlerCount = -1; // -1 only during first iteration        int nextPollTimeoutMillis = 0;        for (;;) {            if (nextPollTimeoutMillis != 0) {                Binder.flushPendingCommands();            }            nativePollOnce(ptr, nextPollTimeoutMillis);            synchronized (this) {                // Try to retrieve the next message.  Return if found.                final long now = SystemClock.uptimeMillis();                Message prevMsg = null;                Message msg = mMessages;                if (msg != null && msg.target == null) {                    // Stalled by a barrier.  Find the next asynchronous message in the queue.                    do {                        prevMsg = msg;                        msg = msg.next;                    } while (msg != null && !msg.isAsynchronous());                }                if (msg != null) {                    if (now < msg.when) {                        // Next message is not ready.  Set a timeout to wake up when it is ready.                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);                    } else {                        // Got a message.                        mBlocked = false;                        if (prevMsg != null) {                            prevMsg.next = msg.next;                        } else {                            mMessages = msg.next;                        }                        msg.next = null;                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);                        msg.markInUse();                        return msg;                    }                } else {                    // No more messages.                    nextPollTimeoutMillis = -1;                }                ....            }         ....        }    }

我们发现next中有一个无线循环的方法,如果消息队列中没有消息,那么next方法会一直阻塞在这里,当有新消息到来时,next方法会返回这条消息并将其从单链表中删除。

Looper的工作原理
Looper在Android的消息机制中扮演着循环的角色,具体来说就是它会不停的从MessageQueue中查看是否有新消息,如果有新消息就会立即处理,否则就一直阻塞在哪里。

Looper的构造方法:

/**在构造方法中会创建一个MessageQueue消息队列*/    private Looper(boolean quitAllowed) {        mQueue = new MessageQueue(quitAllowed);        mThread = Thread.currentThread();    }

通过Looper.prepare()为当前线程创建Looper对象,然后通过Looper.loop();开启消息循环。

  new Thread(){            @Override            public void run() {                Looper.prepare();                Handler handler=new Handler();                Looper.loop();            }        }.start();

Looper中还提供了一个prepareMainLooper方法,这个方法主要是给主线程也就是ActivityThread创建Looper使用的,其本质也是通过prepare方法来实现的。

同样Looper也是可以退出的:Looper提供了quit和quitSafely来退出一个Looper,他们的区别:

quit会直接退出Looper
quitSafely只是设定一个退出标记,然后把消息队列中的已有消息处理完毕后才安全退出。

Looper退出后,通过Handler发送的消息会失败,这个时候Handler的send方法会返回false,同样在不需要Looper时,应该调用quit方法来终止消息循环,否则这个子线程会一直处于等待的状态。

Looper的loop方法:

      public static void loop() {        final Looper me = myLooper();        if (me == null) {            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");        }        final MessageQueue queue = me.mQueue;        Binder.clearCallingIdentity();        final long ident = Binder.clearCallingIdentity();        /**这里一个死循环*/        for (;;) {            /**通过MessageQueue的next方法获取到消息*/            Message msg = queue.next(); // might block            if (msg == null) {                /**如果返回的消息是null,则跳出循环*/                return;            }            /**如果消息不为空,则进行消息的处理*/            msg.target.dispatchMessage(msg);            final long newIdent = Binder.clearCallingIdentity();            msg.recycleUnchecked();        }    }

Handler的工作原理
Handler的工作主要包含消息的发送和接收过程,消息的发送可以通过post的一系列方法以及send的一系列方法来实现,post的一系列方法最终是通过send的一系列方法来实现的。
我们来看一下Handler的sendMessage方法:

/**调用了sendMessageDelayed方法*/    public final boolean sendMessage(Message msg){        return sendMessageDelayed(msg, 0);    }    /**这里又调用了sendMessageAtTime方法*/    public final boolean sendMessageDelayed(Message msg, long delayMillis){        if (delayMillis < 0) {            delayMillis = 0;        }        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);    }   public boolean sendMessageAtTime(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;        }        /**这里又调用了enqueueMessage方法*/        return enqueueMessage(queue, msg, uptimeMillis);    }   private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {        msg.target = this;        if (mAsynchronous) {            msg.setAsynchronous(true);        }        /**这里调用了MessageQueue的enqueueMessage方法*/        return queue.enqueueMessage(msg, uptimeMillis);    }

如图流程:
发消息流程

然后我们再来看处理消息:Looper对象将消息返回给Handler,Handler使用dispatchMessage去处理消息。

    public void dispatchMessage(Message msg) {    //检查Messge的callback是否为null,不为null就通过handleCallback来处理消息        if (msg.callback != null) {            handleCallback(msg);        } else {        /**然后再这里判断CallBack是否为空,不为null就调用mCallback的handlerMessage来处理消息*/            if (mCallback != null) {                if (mCallback.handleMessage(msg)) {                    return;                }            }            handleMessage(msg);        }    }

这里呢:msg.callback()是一个Runnable对象。

 private static void handleCallback(Message message) {        message.callback.run();    }

mCallBack是一个接口:

   public interface Callback {        public boolean handleMessage(Message msg);    }

通过Callback 可以采用如下方式创建Handler对象:
Handler handler=new Handler(callback);
通过CallBack创建一个Handler的实例,并且不需要派生Handler的子类。

Handler还有一个特殊的构造方法,通过Looper来构造Handler来构造。当looper为空时,就会抛出异常。这也解释了在没有Looper的子线程中创建Handler会引发程序异常的原因。

public Handler(Looper looper){this(looper,null,false);}

主线程的消息循环

Android的主线程就是ActivityThread,主线程的入口为main,在main方法中系统会通过Looper.prepareMainLooper()来创建主线程的Looper以及MessageQueue,并通过Looper.loop()俩开启主线程的消息循环。

   public static void main(String[] args) {       ....        Looper.prepareMainLooper();        ActivityThread thread = new ActivityThread();        thread.attach(false);        if (sMainThreadHandler == null) {            sMainThreadHandler = thread.getHandler();        }        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);        Looper.loop();        throw new RuntimeException("Main thread loop unexpectedly exited");    }
    private class H extends Handler {        public static final int LAUNCH_ACTIVITY         = 100;        public static final int PAUSE_ACTIVITY          = 101;        public static final int PAUSE_ACTIVITY_FINISHING= 102;        public static final int STOP_ACTIVITY_SHOW      = 103;        public static final int STOP_ACTIVITY_HIDE      = 104;        public static final int SHOW_WINDOW             = 105;    ......    }

ActivityThread通过Application和AMS进行进程间通讯,AMS以进程间通讯方式完成ActivityThread的请求后会回调ApplicationThread中的Binder方法,然后ApplicationThread会向H发消息,H收到消息后会将ApplicationThread中的逻辑切换到ACtivityThread中去执行,即切换到主线程中去执行,这个过程就是主线程的消息循环模型。

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 媳妇带了避孕环我想要孩子怎么办 新开的文具店一点生意都没有怎么办 孩子在学校被坏孩子欺负了该怎么办 老师像个傻叉我妈还喷我我怎么办啊 承台上预埋桥墩连接钢筋错了怎么办 冲床油缸螺栓拆不下来怎么办 汇款到账银行写错了怎么办 搜狗输入法数字序号超过20怎么办 苹果手机保存的图片变模糊怎么办 微信视频保存到手机变模糊怎么办 自己的位置被别人取代了怎么办 给工厂做半成品老板跑了怎么办 微信变成英文再恢复汉字怎么办 cad中标注尺寸数字太小怎么办 扣扣的钱包手势密码忘记了怎么办 台式电脑带符号的数字打不出怎么办 情侣之间出现看见对方就烦怎么办 电信卡号和联通卡号怎么办情侣号 电脑能登qq但打不开网页怎么办 想跟朋友聊天但对方不理怎么办 刚进婆家门被婆婆欺负怎么办 支付宝的聊天记录被删了怎么办 彩票站买彩票把钱付了没出票怎么办 与异性朋友聊天没话题了怎么办 快递写错地址但已经发货了怎么办 快递写错电话但已经发货了怎么办 微信添加好友功能被限制怎么办 qq号被冻结了限制解封怎么办 被别人强制拉入qq群怎么办 qq群里的图片过期了怎么办 q附近人不能关注不能发信息怎么办 qq畅聊之火掉了怎么办 打印机打印时上面空白留太多怎么办 发短信一直空格里面写0怎么办 网贷获取我新手机号通讯录怎么办 系统音频驱动异常或未安装怎么办 附近功能已屏蔽你的qq好友怎么办 新申请的qq号忘了怎么办 刚申请的qq号忘了怎么办 以前申请的qq号忘了怎么办 小孩玩手机游戏扣费了怎么办