• Android源码分析之Looper


      先来说说summary,Looper就是用来在某个线程中跑一个message loop。一个线程默认是没有message loop与之相关联的,

    为了创建一个你必须在这个线程中调用Looper.prepare方法,然后还得调用Looper.loop来开始消息循环,直到loop被停止。大部分

    和message loop的交互是通过Handler类来进行的。一个典型的例子在上一篇Handler中已经给过了,这里为了方便再重复下:

    This is a typical example of the implementation of a Looper thread, using the separation of {@link #prepare} and {@link #loop} to create an initial Handler to communicate with the Looper.
    
    class LooperThread extends Thread {
          public Handler mHandler;
    
          public void run() {
              Looper.prepare();
    
              mHandler = new Handler() {
                  public void handleMessage(Message msg) {
                      // process incoming messages here
                  }
              };
    
              Looper.loop();
          }
    }

    这个例子展示了怎样将一个Thread,Looper,Handler关联起来一起work。

      接下来我们看看Looper类的关键字段,代码如下:

      // sThreadLocal.get() will return null unless you've called prepare().
        static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
        private static Looper sMainLooper;  // guarded by Looper.class
    
        final MessageQueue mQueue;
        final Thread mThread;

    sThreadLocal表示和某个线程关联的Looper对象,每个线程有且仅有一个;sMainLooper就是传说中的与UI线程关联的Looper,

    什么touch事件、key事件等等UI发出的事件(message)都在此looper中处理,这里顺便插句,main looper如此重要以至于

    Android系统在每个app启动的时候都已经给我们创建好了,客户端代码不需要操心,相关代码在android.app.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);
    
            Environment.initForCurrentUser();
    
            // Set the reporter for event logging in libcore
            EventLogger.setReporter(new EventLoggingReporter());
    
            Security.addProvider(new AndroidKeyStoreProvider());
    
            Process.setArgV0("<pre-initialized>");
    
            Looper.prepareMainLooper();
    
            ActivityThread thread = new ActivityThread();
            thread.attach(false);
    
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
    
            AsyncTask.init();
    
            if (false) {
                Looper.myLooper().setMessageLogging(new
                        LogPrinter(Log.DEBUG, "ActivityThread"));
            }
    
            Looper.loop();
    
            throw new RuntimeException("Main thread loop unexpectedly exited");
        }

    注意关于Looper的相关调用,先是prepareMainLooper(),然后是loop()方法,这也就是我们一开始说的一个线程为了有一个

    message loop必须要做的2件事情。在这里UI线程已经做了,所以这也就解释了为啥UI线程一开始就有相关的message loop了。

      接下来先看看ctor,如下:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

    Looper有一个private的ctor,内部会初始化mQueue,设置mThread为当前线程。

      几个相关的小方法,如下:

      /**
         * Return the Looper object associated with the current thread.  Returns
         * null if the calling thread is not associated with a Looper.
         */
        public static Looper myLooper() {
            return sThreadLocal.get();
        }
        
        /**
         * Return the {@link MessageQueue} object associated with the current
         * thread.  This must be called from a thread running a Looper, or a
         * NullPointerException will be thrown.
         */
        public static MessageQueue myQueue() {
            return myLooper().mQueue;
        }
    
        /**
         * Returns true if the current thread is this looper's thread.
         * @hide
         */
        public boolean isCurrentThread() {
            return Thread.currentThread() == mThread;
        }

    分别返回与当前线程关联的Looper对象,MessageQueue对象,还有个help方法用来判断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));
        }
    
        /**
         * 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();
            }
        }
    
        /** Returns the application's main looper, which lives in the main thread of the application.
         */
        public static Looper getMainLooper() {
            synchronized (Looper.class) {
                return sMainLooper;
            }
        }

    客户端代码要用到的是无参数的prepare方法,它将创建一个允许退出的message queue;Android系统调用prepareMainLooper,

    内部调用参数为false的prepare方法,创建一个不允许退出的message queue,客户端代码永远不要调用这个方法(因为系统已经

    调用过了,你在调用的话会抛IllegalStateException)。在prepare(boolean quitAllowed)内部sThreadLocal对象会被设置,

    new一个Looper赋给它,这时与本线程相关的Looper对象也就产生了。sMainLooper在prepareMainLooper方法里也会被设置

    成合适的值,以便客户端代码后面调用。

      然后是Looper的重头戏loop方法:

      /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the 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;
    
            // 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
                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
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                msg.target.dispatchMessage(msg);
    
                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.recycle();
            }
        }

    loop()一开始首先检查与当前线程关联的looper是否存在,如果还没有就会抛一个RuntimeException,说明还没Looper与当前线程相关联;

    否则开始进入message循环。这个无限for循环的主体大概是这样:从MessageQueue中取出一个msg(阻塞型操作),如果msg是null说明

    没有更多的message了(MessageQueue正在退出),退出for循环;否则将msg交给它的target(Handler)处理,最后回收msg,然后

    开始下一轮同样的处理。

      最后我们看一组quit方法,如下:

      /**
         * Quits the looper.
         * <p>
         * Causes the {@link #loop} method to terminate without processing any
         * more messages in the message queue.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p><p class="note">
         * Using this method may be unsafe because some messages may not be delivered
         * before the looper terminates.  Consider using {@link #quitSafely} instead to ensure
         * that all pending work is completed in an orderly manner.
         * </p>
         *
         * @see #quitSafely
         */
        public void quit() {
            mQueue.quit(false);
        }
    
        /**
         * Quits the looper safely.
         * <p>
         * Causes the {@link #loop} method to terminate as soon as all remaining messages
         * in the message queue that are already due to be delivered have been handled.
         * However pending delayed messages with due times in the future will not be
         * delivered before the loop terminates.
         * </p><p>
         * Any attempt to post messages to the queue after the looper is asked to quit will fail.
         * For example, the {@link Handler#sendMessage(Message)} method will return false.
         * </p>
         */
        public void quitSafely() {
            mQueue.quit(true);
        }

    quit方法表示尽快退出message loop,着急模式,队列里任何pending的消息都不会被处理了;其他的注意事项参看方法的doc;

    有时候你可能希望处理完队列里面的消息在退出且不接收新的消息了,这时你可以考虑quitSafely方法。但是超过当前时间的message

    还是不会被处理而是回收掉了,也就是说退出时,时间已经到了(msg.when <= now)的message才会被最后处理;比如现在message

    queue要求退出了,你队列里面还有10个1h之后才要执行的message,那么这些message会被回收掉而不会派发;其他事项请参看方法的doc。

      Looper类的分析就到这了。

  • 相关阅读:
    ACM训练计划
    动态规划 最长公共子序列LCS
    Floyd最短路
    邻接表拓扑排序
    数字三角形(数塔) DP入门
    hdu 5533 计算几何 判断是否为正方形
    威尔逊定理--HDU2973
    二分--POJ-3258
    01背包--hdu2639
    矩阵快速幂--51nod-1242斐波那契数列的第N项
  • 原文地址:https://www.cnblogs.com/xiaoweiz/p/3661515.html
Copyright © 2020-2023  润新知