• Android的消息机制简单总结


    参考文章:
    http://gityuan.com/2015/12/26/handler-message-framework/#next
    参考资料:
    Android Framework的源码:
    Message.java
    MessageQueue.java
    Looper.java
    Handler.java
    ThreadLocal.java
    (以上几个类代码都不复杂,自己去打开看一看还是有必要的)

    1.前言

    在Android开发中,Handler是非常常用的,如果多线程需要切换到主线程更新UI,通常都会使用到Handler。Handler后面的是Android的消息机制。

    2.思考

    2.1为什么Android需要一套通用的多线程通信的机制?

    Android的UI是单线程机制(从效率,实现的复杂程度来说,是最优解),这意味着
    我们只能在UI线程(主线程)更新UI,如果在其他线程更新UI,app会报异常(奔溃)
    然而多线程是Android开发中非常常见的情景,不可能把所有代码都运行在主线程,如网络请求,文件读写,复杂的运算,这些操作如果运行在主线程,会造成UI线程卡顿,甚至会带来ANR(应用无响应)的问题。
    当我们在子线程执行完任务后,需要把结果显示在UI上,就必然涉及到多线程的通信。
    多线程通信的实现方式很多,但是,Android 系统提供了一套已经实现的通用的机制,这是最好的选择,同时这对开发者也是方便友好的。

    2.2为什么Android采用的消息驱动多线程通信的机制?

    由于多线程(我们只考虑同一个进程下的多线程),共享进程的内存空间,所以它们之间的相互操作非常简单
    我们可以使用回调监听,或者更粗暴一点,一个A线程直接持有B线程的引用,然后就可直接通知操作B线程
    唯一的问题是:通用性
    不可能穷尽所有线程之间通信的可能,所以,理想的解决方法是抽象:
    把线程之间的通信抽象为A线程发出消息通知,B处理消息
    消息Message代表两个线程之间的通信传递的内容
    以上,线程之间的通信问题就抽象为由一个线程发出消息Message,另一个线程接收并处理这个Message问题

    2.3为什么Android Loop循环不会卡死CPU?

    对于一个App来说:
    它需要一直运行,直至被退出,结束进程
    最简单的方式就是一个死循环(永真循环),注意为了不让这个死循环一直占用CPU,所以必然有一个等待并让出cpu的处理。
    对应Android来说,每一个程序的入口是:
    ActivityThread中的main方法(我们在学习Java里面非常熟悉的main方法,这是整个App的入口):

      public static void main(String[] args) {
    
            Looper.prepareMainLooper();
    //注意这个方法,这个方法为当前的线程创建了一个属于当前线程的Looper,并赋值给 Looper的静态变量 private static Looper sMainLooper; 
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
            Looper.loop();//这里是一个死循环
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
    

    在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;//ongoing这里可以看到一个Looper里面包含着一个对应的MessageQueue
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
            //这就是死循环
            for (;;) {
                Message msg = queue.next(); // might block,这个方法可能会被阻塞,等待,让出CPU
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
               try {
                    msg.target.dispatchMessage(msg);
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                msg.recycleUnchecked();
            }
        }

    3.Java层研读:

    3.1理清下面几个类的作用以及它们之间的联系:

    首先是它们的整体关系:使用Gityuan大神的图片:
    handler_communication
    我们使用一个最常见的的场景解析它们的关系:
    UI线程A:刷新,显示UI
    工作线程B:负责请求网络,获取一个结果,假设结果是一个字符串 "Hello ,thread A,I 'm thread B."
    现在我们需要把结果从工作线程B传递到UI线程A,然后由UI线程把这个结果显示到一个Textview的控件上
    分为三个步骤:
    在主(UI)线程 初始化一个Handler mHandler对象

    在工作线程中
    通过
    Message message=mHandler.obtain();
    获取一个Message
    这个时候可以把"Hello ,thread A,I 'm thread B." 保存在Message里面
    调用mHandler.sendMessage()方法,就会把Message保存到主线程的Looper的 MessageQueue中

    主线程的Looper会不断循环从MessageQueue中取出Message,然后通过dispatchMessage方法分发给它对应的Handler处理

    3.2Message:

    Message代表一个从一个线程传到另外一个线程的消息
    Android的消息驱动多线程通信的机制是基于Message的
    所以Message需要重点关心的是Message所能包含的信息,也就是这个类的
    成员变量
    源码:

    public final class Message implements Parcelable {
        /**
         * User-defined message code so that the recipient can identify
         * what this message is about. Each {@link Handler} has its own name-space
         * for message codes, so you do not need to worry about yours conflicting
         * with other handlers.
         */
        public int what;//用于区分Message类型的id,作用域是单个Handler,也就是不同的Handler这个值即使重复也没关系
    
        /**
         * arg1 and arg2 are lower-cost alternatives to using
         * {@link #setData(Bundle) setData()} if you only need to store a
         * few integer values.
         */
        public int arg1;//arg1,一个位置,供使用者保存一个整形的值
    
        /**
         * arg1 and arg2 are lower-cost alternatives to using
         * {@link #setData(Bundle) setData()} if you only need to store a
         * few integer values.
         */
        public int arg2;/arg2,一个位置,供使用者保存一个整形的值
    
        /**
         * An arbitrary object to send to the recipient.  When using
         * {@link Messenger} to send the message across processes this can only
         * be non-null if it contains a Parcelable of a framework class (not one
         * implemented by the application).   For other data transfer use
         * {@link #setData}.
         *
         * <p>Note that Parcelable objects here are not supported prior to
         * the {@link android.os.Build.VERSION_CODES#FROYO} release.
         */
        public Object obj;
    
        /**
         * Optional Messenger where replies to this message can be sent.  The
         * semantics of exactly how this is used are up to the sender and
         * receiver.
         */
        public Messenger replyTo;
    
        /**
         * Optional field indicating the uid that sent the message.  This is
         * only valid for messages posted by a {@link Messenger}; otherwise,
         * it will be -1.
         */
        public int sendingUid = -1;
    
        /** 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.
         */
        /*package*/ static final int FLAG_IN_USE = 1 << 0;
    
        /** If set message is asynchronous */
        /*package*/ static final int FLAG_ASYNCHRONOUS = 1 << 1;
    
        /** Flags to clear in the copyFrom method */
        /*package*/ static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
    
        /*package*/ int flags;//状态标记位
    
        /*package*/ long when;//记录当前Message需要被处理的时间
    
        /*package*/ Bundle data;//保存一个类型为Bundle的数据
    
        /*package*/ Handler target;//记录当前的Message的处理者(目的地)
    
        /*package*/ Runnable callback;//记录当前的Message的Runnable回调
    
        /*下面的代码使得我们可以用多个Message构建一个单链表
        // sometimes we store linked lists of these things
        /*package*/ Message next;
    
        private static final Object sPoolSync = new Object();
        private static Message sPool;
        private static int sPoolSize = 0;
    
        private static final int MAX_POOL_SIZE = 50;//为了避免频繁创建销毁Message,设置一个最大size为50的Message对象池
    
        private static boolean gCheckRecycle = true;
    }

    3.2MessageQueue:

    MessageQueue是消息机制的Java层和C++层的连接纽带,大部分核心方法都交给native层来处理,
    这个需要结合C++ Native层的NativeMessageQueue来看。
    具体请参考Gityuan大神的分析文章:
    http://gityuan.com/2015/12/27/handler-message-native/

    3.4Looper:

    Looper负责从MessageQueue不断取出需要处理的Message,然后把Message分发给它对应的Handler处理
    重要的方法有以下三个:
    Looper.prepare()

    /** Initialize the current thread as a looper.
          * This gives you a chance to create handlers that then reference
          * this looper, before actually starting the loop. Be sure to call
          * {@link #loop()} after calling this method, and end it by calling
          * {@link #quit()}.
          */
        public static void prepare() {
            prepare(true);
        }
    
        private static void prepare(boolean quitAllowed) {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            sThreadLocal.set(new Looper(quitAllowed));//这个方法就为当前的线程set一个属于它的Looper,ThreadLocal见另外一篇文章的分析
        }
    

    Looper.prepareMainLooper()
    这个方法本质也是使用了Looper.prepare(),这个方法只在UI线程被调用,然后
    把Ui线程对应的Looper也赋值给了sMainLooper

      /**
         * Initialize the current thread as a looper, marking it as an
         * application's main looper. The main looper for your application
         * is created by the Android environment, so you should never need
         * to call this function yourself.  See also: {@link #prepare()}
         */
        public static void prepareMainLooper() {
            prepare(false);
            synchronized (Looper.class) {
                if (sMainLooper != null) {
                    throw new IllegalStateException("The main Looper has already been prepared.");
                }
                sMainLooper = myLooper();
            }
        }

    Looper.loop()
    这个方法是一个死循环,它负责从MessageQueue中取出需要处理的Message,并分发处理

     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;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {
                Message msg = queue.next(); // might block 可能阻塞,然后会让出CPU给其他线程(进程)
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                final long traceTag = me.mTraceTag;
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
                try {
                    msg.target.dispatchMessage(msg);//把当前的Message分发给对应的Handler处理
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
    
                msg.recycleUnchecked();
            }
        }

    3.5Handler:

    Handler是我们最经常使用的:
    它有两个职责:

    1. 发送Message
    2. 处理Message
      通常我们会继承并重写它的 handleMessage(Message msg)方法,因为这个方法在父类只是一个空实现:
      /**
         * Subclasses must implement this to receive messages.
         */
        public void handleMessage(Message msg) {
        }

    在一个需要接收处理Message的线程初始化一个Handler

    在需要发送Message的线程利用这个Handler发送消息(本质把Message添加到接收线程的Looper的MessageQueue中)

    4.代码赏析:

    4.1在Message类中,必须考虑一个这样的问题,每一次都new 一个Message实例,有时是非常低效并且消耗资源的。Java虽然有Gc机制,但是这个并不能保证及时回收。

    解决问题的方法之一就是对象池:
    我们短时间内需要获取很多对象,这些对象又很快使用完毕,可以考虑重用对象。
    考虑到对象池的数目大小是不断变化的,插入删除操作比较多,考虑使用单链表的数据结构

    private static Message sPool;//静态全局变量,记录链表头
    private static int sPoolSize = 0; //静态全局变量,记录链表长度
    private static final int MAX_POOL_SIZE = 50;//常量,对象池最大值
    
       /**
         * Return a new Message instance from the global pool. Allows us to
         * avoid allocating new objects in many cases.
         */
        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();
        }
    
      /**
         * Recycles a Message that may be in-use.
         * Used internally by the MessageQueue and Looper when disposing of queued Messages.
         */
        void recycleUnchecked() {
            // Mark the message as in use while it remains in the recycled object pool.
            // Clear out all other details.
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;
    
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }
    

    上面的代码最精彩的地方就是并没有单独实现一个对象池的类来保存Message的对象,通过全局静态变量
    Message sPool 和一个int sPoolSize 记录一个单链表就完成了一个简单的对象池(抽象意义上的)

    1. 第一次通过 obtain()方法获取Message对象,注意,此时sPool==null,所以直接new 一个 Message实例
    2. 如果sPool==null,调用多少次obtain()方法都会new 多少个Message实例(因为没有对象池是空的)
    3. 注意:一个Message实例被使用后,将会调用recycleUnchecked()方法,这个时候会把Message的值重设
      然后,做判断,如果对象池的数目没有超过最大值,就把这个对象插入到单链表的头部(回收),并且把sPollSzie++
      4.如果sPool !=null(sPool已经初始化) 此时调用obtain方法,
      Message m = sPool;
      sPool = m.next;
      m.next = null;
      m.flags = 0; // clear in-use flag
      sPoolSize--;
      return m;

    会从单链表头部取一个Message 实例,并且把sPollSize--

    5.能不能模仿实现一个多线程消息机制

    由于这个涉及到Linux的epoll机制,需要理解这个并使用C,C++调用,目前还个人还完成不了。
    留给大家一起想一下吧。
    具体解析参考:
    http://gityuan.com/2015/12/27/handler-message-native/

    http://gityuan.com/2015/12/06/linux_epoll/

  • 相关阅读:
    [android] 帧布局
    [android] 表格布局和绝对布局
    使用LVS实现负载均衡原理及安装配置详解
    学习Docker之Dockerfile的命令
    使用docker-compose部署nginx
    CentOS Bash 命令补全增强软件包 bash-completion
    “三次握手,四次挥手”你真的懂吗?
    0777 0的意思
    linux文件或目录权限修改后如何恢复(备份了权限就能恢复)
    centos7进入单用户模式
  • 原文地址:https://www.cnblogs.com/bylijian/p/7344898.html
Copyright © 2020-2023  润新知