• Android 消息机制



    从一接触Android開始。就听说了消息机制。那时候对消息机制的理解也仅仅是看看别人博客,了解一些概念和相关的类而已,并没有深入的去了解和探究。直到近期,又一次过了一遍消息机制和相关源代码。这里做简单的整理和记录,毕竟好记性不如烂笔头。假设有什么问题,还请大家指出。

    (注:源代码版本号 4.0)

    基础使用

    • Android的消息机制,主要是指Handler的执行机制。通常情况下。我们使用Handler来更新UI。具体的说就是一些耗时操作(IO读取。网络请求等)执行结束后,对UI的状态进行更新。为了避免页面卡顿,耗时操作通常在子线程中处理,然而UI的更新操作必须在UI线程中进行更新。这个时候就须要使用Handler来进行线程间消息的传递
    • 基础演示样例:

      public class MainActivity extends AppCompatActivity {
      
          public static final String TAG = MainActivity.class.getSimpleName();
      
          //报内存泄露警告的handler
          private Handler handler = new Handler() {
              @Override
              public void handleMessage(Message msg) {//处理发送过来的消息
                  if (msg.arg1 == 1) {
                      Log.i(TAG, "1消息已被接收");
                  }
              }
          };
      
          @Override
          protected void onCreate(Bundle savedInstanceState) {
              super.onCreate(savedInstanceState);
              setContentView(R.layout.activity_main);
              sendMessageToHandler();
          }
      
          /**
           * 向Handler发送消息
           */
          private void sendMessageToHandler() {
      
              new Thread(new Runnable() {
                  @Override
                  public void run() {
                      Message message = Message.obtain();
                      message.arg1 = 1;
                      handler.sendMessage(message);
                  }
              }).start();
          }
      }
      
    • 使用过Handler的朋友都知道。上面的使用方式Android Lint会给出内存泄露警告,详细内容例如以下:

    This Handler class should be static or leaks might occur (anonymous android.os.Handler) less...

    Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected. If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue. If the Handler is using the Looper or MessageQueue of the main thread, you need to fix your Handler declaration, as follows: Declare the Handler as a static class; In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler; Make all references to members of the outer class using the WeakReference object.

    • 警告内容说的非常具体。而且给出解决方式。此处先放一放,我们了解源代码后,再回头解释此问题

    对消息机制相关类的说明(源代码细节,直接在源代码中加凝视说明)

    • Handler 该类负责Message的发送和处理

      • 发送消息的核心源代码例如以下:

        public boolean sendMessageAtTime(Message msg, long uptimeMillis)
        {   
            boolean sent = false;
            MessageQueue queue = mQueue;
            if (queue != null) {
                msg.target = this;
                //关键代码。发送消息的实质,事实上是向消息队列中插入一条消息
                sent = queue.enqueueMessage(msg, uptimeMillis);
            }
            else {
                RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
                Log.w("Looper", e.getMessage(), e);
            }
            return sent;
        }
        
      • 处理消息的核心源代码例如以下:

        //该方法对Message进行了分发处理
        public void dispatchMessage(Message msg) {
            if (msg.callback != null) {
                handleCallback(msg);
            } else {
                if (mCallback != null) {
                    if (mCallback.handleMessage(msg)) {
                        return;
                    }
                }
                handleMessage(msg);
            }
        }
        
        ...
        
        private final void handleCallback(Message message) {
            message.callback.run();
        }
        
        ...
        
        public interface Callback {
            public boolean handleMessage(Message msg);
        }
        
        ...
        
        public void handleMessage(Message msg) {
        }
        
    • Message 该类负责Message对象的创建和释放

      • Message的创建obtain()

        //该方法有非常多的重载方法。都是在此方法的基础上扩展而来,其它方法不再列举
        public static Message obtain() {
            synchronized (sPoolSync) {
                if (sPool != null) {
                    Message m = sPool;
                    sPool = m.next;
                    m.next = null;
                    sPoolSize--;
                    return m;
                }
            }
            return new Message();
        }
        
      • Message的释放

        public void recycle() {
            clearForRecycle();
        
            synchronized (sPoolSync) {
                if (sPoolSize < MAX_POOL_SIZE) {
                    next = sPool;
                    sPool = this;
                    sPoolSize++;
                }
            }
        }
        
        void clearForRecycle() {
            flags = 0;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            when = 0;
            target = null;
            callback = null;
            data = null;
        }
        
    • ThreadLocal ThreadLocal的作用是提供线程内的局部变量,这样的变量在线程的生命周期内起作用。降低同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

      这里给出參考博客链接:http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/

      • 该类中定义了一个静态内部类Values。Values中有一个对象数组table。不同的线程訪问着同一个ThreadLocal的set()和get()方法,本质是操作table数组,他们对ThreadLocal的操作是在各自线程的内部。

        结合源代码我们能更好的理解ThreadLocal的实现

      • ThreadLocal中的set()

        //获取当前线程的Values对象,假设当前线程的Values不存在,则又一次实例化一个
        //否则将value加入到table数组中
        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()

        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]) {
                    //Values中的put()方法是将索引和值在相邻位置进行存储的,详细实现查看put()源代码
                    return (T) table[index + 1];
                }
            } else {
                values = initializeValues(currentThread);
            }
        
            return (T) values.getAfterMiss(this);
        }
        
      • Values 中的put()

        void put(ThreadLocal<?> key, Object value) {
            cleanUp();
        
            // Keep track of first tombstone. That's where we want to go back
            // and add an entry if necessary.
            int firstTombstone = -1;
        
            for (int index = key.hash & mask;; index = next(index)) {
                Object k = table[index];
        
                if (k == key.reference) {
                    // Replace existing entry.
                    table[index + 1] = value;
                    return;
                }
        
                if (k == null) {
                    if (firstTombstone == -1) {
                        // Fill in null slot.
                        table[index] = key.reference;
                        table[index + 1] = value;
                        size++;
                        return;
                    }
                    //数组中相邻的位置存储索引和值
                    // Go back and replace first tombstone.
                    table[firstTombstone] = key.reference;
                    table[firstTombstone + 1] = value;
                    tombstones--;
                    size++;
                    return;
                }
        
                // Remember first tombstone.
                if (firstTombstone == -1 && k == TOMBSTONE) {
                    firstTombstone = index;
                }
            }
        }
        
    • Looper 该类是一个循环控制器

      • 该类是消息机制的重要组成部分,本类包括消息机制的准备工作,循环控制器的启动以及退出循环控制器功能
      • Looper的准备

        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        ...
        
        private Looper() {
            mQueue = new MessageQueue();//创建消息队列对象
            mRun = true;
            mThread = Thread.currentThread();//获取当前线程
        }
        ...
        
        public static void prepare() {
            if (sThreadLocal.get() != null) {
                throw new RuntimeException("Only one Looper may be created per thread");
            }
            //将当前线程的Looper对象传递给ThreadLocal
            sThreadLocal.set(new Looper());
        }
        
      • Looper循环控制器的启动

        public static Looper myLooper() {
            //从ThreadLocal中获取Looper对象。这里可看出他们操作的是同一个ThreadLocal对象
            return sThreadLocal.get();
        }
        
        ...
        
        public static void loop() {
            //启动loop方法必须有looper对象。否则会报以下的异常
            Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            //获取Looper中的消息队列
            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();
        
            //无限循环,从MessageQueue中不断的获取Message
            while (true) {
                Message msg = queue.next(); // might block
                if (msg != null) {
                    //唯一退出循环的条件
                    if (msg.target == null) {
                        // No target is a magic identifier for the quit message.
                        return;
                    }
        
                    long wallStart = 0;
                    long threadStart = 0;
        
                    // This must be in a local variable, in case a UI event sets the logger
                    Printer logging = me.mLogging;
                    if (logging != null) {
                        logging.println(">>>>> Dispatching to " + msg.target + " " +
                                msg.callback + ": " + msg.what);
                        wallStart = SystemClock.currentTimeMicro();
                        threadStart = SystemClock.currentThreadTimeMicro();
                    }
        
                    //分发Message到Handler中进行处理
                    msg.target.dispatchMessage(msg);
        
                    if (logging != null) {
                        long wallTime = SystemClock.currentTimeMicro() - wallStart;
                        long threadTime = SystemClock.currentThreadTimeMicro() - threadStart;
        
                        logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                        if (logging instanceof Profiler) {
                            ((Profiler) logging).profile(msg, wallStart, wallTime,
                                    threadStart, threadTime);
                        }
                    }
        
                    // 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);
                    }
                    //释放Message
                    msg.recycle();
                }
            }
        }
        
      • 退出Looper quite()

        //向MessageQueue中插入一个target为null的Message,会退出Looper
        //退出loop()的唯一条件 if (msg.target == null){return;}
        public void quit() {
            Message msg = Message.obtain();
            // NOTE: By enqueueing directly into the message queue, the
            // message is left with a null target.  This is how we know it is
            // a quit message.
            mQueue.enqueueMessage(msg, 0);
        }
        
    • MessageQueue 该类负责Message的插入,取出,以及移除

      • 插入Message方法enqueueMessage()源代码

        //Handler中的sendMessage()方法终于会调用此方法。所以说发送消息事实上是向MessageQueue中插入一条消息
        final boolean enqueueMessage(Message msg, long when) {
            if (msg.isInUse()) {
                throw new AndroidRuntimeException(msg
                        + " This message is already in use.");
            }
            if (msg.target == null && !mQuitAllowed) {
                throw new RuntimeException("Main thread not allowed to quit");
            }
            final boolean needWake;
            synchronized (this) {
                if (mQuiting) {
                    RuntimeException e = new RuntimeException(
                        msg.target + " sending message to a Handler on a dead thread");
                    Log.w("MessageQueue", e.getMessage(), e);
                    return false;
                } else if (msg.target == null) {
                    mQuiting = true;
                }
        
                msg.when = when;
                //Log.d("MessageQueue", "Enqueing: " + msg);
                Message p = mMessages;
                if (p == null || when == 0 || when < p.when) {
                    msg.next = p;
                    mMessages = msg;
                    needWake = mBlocked; // new head, might need to wake up
                } else {
                    Message prev = null;
                    while (p != null && p.when <= when) {
                        prev = p;
                        p = p.next;
                    }
                    msg.next = prev.next;
                    prev.next = msg;
                    needWake = false; // still waiting on head, no need to wake up
                }
            }
            if (needWake) {
                nativeWake(mPtr);
            }
            return true;
        }
        
      • 取出和移除

        final Message next() {
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
        
            //又一个无限循环
            for (;;) {
                if (nextPollTimeoutMillis != 0) {
                    Binder.flushPendingCommands();
                }
                nativePollOnce(mPtr, nextPollTimeoutMillis);
        
                //当有Message时,取出Message。否则该方法将会一直阻塞
                synchronized (this) {
                    // Try to retrieve the next message.  Return if found.
                    final long now = SystemClock.uptimeMillis();
                    final Message msg = mMessages;
                    if (msg != null) {
                        final long when = msg.when;
                        if (now >= when) {
                            mBlocked = false;
                            mMessages = msg.next;
                            msg.next = null;
                            if (false) Log.v("MessageQueue", "Returning message: " + msg);
                            msg.markInUse();
                            return msg;
                        } else {
                            nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
                        }
                    } else {
                        nextPollTimeoutMillis = -1;
                    }
        
                    // If first time, then get the number of idlers to run.
                    if (pendingIdleHandlerCount < 0) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount == 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
        
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                    mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
        
                // Run the idle handlers.
                // We only ever reach this code block during the first iteration.
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
        
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf("MessageQueue", "IdleHandler threw exception", t);
                    }
        
                    //当有Message时。返回Message后,删除Message
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
        
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;
        
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
        

    消息机制关键类与核心方法图


    依据Handler的使用流程。逐步分析

    • 首先,使用Handler必须保证其所在的线程中具有Looper对象,可是有的朋友会说。我在Activity中并没有创建Looper对象。可是我也能够使用Handler啊。事实上Activity所在的主线程(ActivityThread)也就是UI线程是特殊的线程,该线程在创建的时候就已经创建了Looper对象。看源代码

      • ActivityThread的main()方法

        public static void main(String[] args) {
            SamplingProfilerIntegration.start();
        
            // CloseGuard defaults to true and can be quite spammy.  We
            // disable it here, but selectively enable it later (via
            // StrictMode) on debug builds, but using DropBox, not logs.
            CloseGuard.setEnabled(false);
        
            Process.setArgV0("<pre-initialized>");
        
            Looper.prepareMainLooper();//准备工作
            if (sMainThreadHandler == null) {
                sMainThreadHandler = new Handler();
            }
        
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
        
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }
        
            Looper.loop();//启动循环控制器
        
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }
        
      • Looper中的prepareMainLooper()方法是专门用与主线程的

        public static void prepareMainLooper() {
            prepare();//最后还是调用了prepare(),从而创建了Looper对象,并设置给了ThreadLocal
            setMainLooper(myLooper());
            myLooper().mQueue.mQuitAllowed = false;
        }
        private synchronized static void setMainLooper(Looper looper) {
            mMainLooper = looper;
        }
        
    • 以上工作准备完毕后,Looper,ThreadLocal已经创建完毕。而且建立了关联关系。接下来创建Handler对象,获取Message对象(Message.obtain()),使用Handler中的sendMessage()方法向MessageQueue中插入Message(enqueueMessage()),这时候Looper对象中处于阻塞状态的next()方法检測到Message后,将其从MessageQueue中取出。传递给Handler的dispatchMessage()方法后,清除该Message(msg.recycle();)。最后dispatchMessage()方法,将Message分发到run()方法或者handlerMessage(Message msg)中进行处理。

    • 最后来一张消息机制的示意图


    分析Handler内存泄露问题

    1. App启动时,Android Framework 为主线程(ActivityThread)创建了一个Looper对象(main方法中),这个Looper将贯穿整个app的生命周期,它内部持有消息队列(MessageQueue),而且开启一个循环(loop())来处理Message,而Framework的主要事件都包括着内部Message对象,当这些事件被触发的时候。Message对象会被加到消息队列中运行。
    2. 当一个Handler被实例化时(如上面那样),它将和主线程Looper对象的消息队列相关联。被推到消息队列中的Message对象将持有一个Handler的引用以便于当Looper处理到这个Message的时候,Framework运行Handler的handleMessage(Message)方法。

    3. 在 Java 语言中,非静态匿名内部类将持有一个对外部类的隐式引用,而静态内部类则不会。
    4. 内存泄露会在什么情况下产生呢?这里给出一个演示样例

      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          setContentView(R.layout.activity_main);
      
          mHandler.postDelayed(new Runnable() {
              @Override
              public void run() {
                  Log.i(TAG,"post delay");
              }
          },10*60*1000);
      
          finish();
      }
      

    演示样例中,当Activity被finish()掉,Message 将存在于消息队列中长达10分钟的时间才会被运行到。

    这个Message持有一个对Handler的引用。Handler也会持有一个对于外部类(SampleActivity)的隐式引用,这些引用在Message被运行前将一直保持,这样会保证Activity的上下文不被垃圾回收机制回收。同一时候也会泄露应用程序的资源(views and resources)。

    解决的方法

    Handler的警告提示中已经说明。由于静态匿名内部类不会持有外部类的隐式引用。因此我们创建一个静态的Handler子类。假设须要调用外部类(比如:Activity)的方法,就让Handler持有一个Activity的弱引用(WeakReference),这样就不会泄露Activity的上下文了。

    演示样例:

    public class MainActivity extends AppCompatActivity {
    
        public static final String TAG = MainActivity.class.getSimpleName();
    
        private MyHandler myHandler = new MyHandler(this);
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            sendMessageToHandler();
    
        }
    
        /**
         * 向Handler发送消息
         */
        private void sendMessageToHandler() {
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Message message = Message.obtain();
                    message.arg2 = 2;
                    myHandler.sendMessage(message);
                }
            }).start();
    
        }
    
        /**
         * MyHandler的处理方法
         *
         * @param msg 消息
         */
        private void handlerMessage(Message msg) {
            if (msg.arg2 == 2) {
                Log.i(MainActivity.TAG, "2已接收到消息");
            }
        }
    
    
        private static class MyHandler extends Handler {
    
            //弱引用,避免Handler持有外部类的引用。即MainActivity的引用,
            // 这样会导致MainActivity的上下文及资源无法被回收,引发内存泄露的情况发生
            private WeakReference<MainActivity> weakReference;
    
            public MyHandler(MainActivity mainActivity) {
                weakReference = new WeakReference<>(mainActivity);
            }
    
            @Override
            public void handleMessage(Message msg) {
    
                MainActivity mainActivity = weakReference.get();
                mainActivity.handlerMessage(msg);
    
            }
        }
    
    }
    

    參考:
    http://qifuguang.me/2015/09/02/[Java%E5%B9%B6%E5%8F%91%E5%8C%85%E5%AD%A6%E4%B9%A0%E4%B8%83]%E8%A7%A3%E5%AF%86ThreadLocal/ http://developer.51cto.com/art/201204/332155.htm

  • 相关阅读:
    React 父调子 子调父 observer.js 非父子传值
    echarts 折线图
    跳转新页面解决用户重新登录的方法
    微信小程序规范 引入框架 引入外部js css
    js 400
    Angular2 表单校验
    Angular2 登录页进门户页 用户接口正常 从服务平台退出到门户页 登录接口报302
    CSP-S 2020 SC 迷惑行为大赏
    【题解】[CSP-S 2020] 函数调用【拓扑图DP】
    【题解】[CSP-S 2020] 动物园
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/7249458.html
Copyright © 2020-2023  润新知