android消息循环的形象描述
android消息循环的本质以及原理(pipe)
例子巩固
推荐博文

android消息循环的描述

android系统有一个核心的特点,那就是android的每一个应用程序都有一个消息队列,每一个应用程序的主线程(ActivityThread)不断地从这个消息队列里面取出消息(looper消息循环),然后由对应的回调函数处理(回调函数用Handler注册)。说白了这个应用程序的主线程在最后就是一个死循环,不断地读取消息,如果没有的话会间断性的休眠唤醒。

1,举一个简单的例子,形象的描述一下,当我们打开一个app之后,最后就进入了这个APP的ActivityThread里面的消息循环,我们一般在我们的应用程序里面会添加onClick方法,这个onClick方法,就是在读到对应的点击事件之后调用的。当我们点击button的时候,内核会产生中断,然后会向/dev/input/eventX(这里的X代表一个数字,其中有一个就是我们的触摸屏)
,在framwork层的systemserver进程里面有一个InputManagerService,这个service会不断的读取eventX里面的数据,当我们点击的时候他读取到点击事件,然后会通过pipe传递给我们的应用程序。也就是进入了我们的消息循环,然后我们的消息循环接收到这个点击事件就能判断出点击的是哪里,传递给我们的button组件,调用button注册的onClick方法,调用完了之后,有回到我们的消息循环如此重复。

通过上面的描述,大家应该可以知道为什么所有的UI操作要在这个主线程里面完成,并且我们注册的方法一定不能阻塞主线程,因为这样的话,我们的主线程就不能继续读取消息队列,造成界面假死。这也就是为什么我们要将耗时的操作放在service或者线程里面完成。

android消息循环的本质以及原理

我们知道android应用程序有一个主消息循环在ActivityThread里面最后会调用loop进入主消息循环,代码如下:

//ActivityThread.java:最后的loop()进入主消息循环
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());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        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");
    }

从上面的代码中我们知道最后调用loop函数,这个函数是一个死循环会不断的读取消息循环,如果没有消息则会休眠固定时间,代码如下:

//Looper.java:线程内的单例模式
public final class Looper {
    private static final String TAG = "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;

    private Printer mLogging;

     /** 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;
        }
    }

    /**
     * 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.recycleUnchecked();
        }
    }

    /**
     * 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();
    }

    public void setMessageLogging(Printer printer) {
        mLogging = printer;
    }


    public static MessageQueue myQueue() {
        return myLooper().mQueue;
    }

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

    /**
     * Returns true if the current thread is this looper's thread.
     * @hide
     */
    public boolean isCurrentThread() {
        return Thread.currentThread() == mThread;
    }

    。。。。。
    。。。。。
}

我们看Looper的代码会感觉很像单例模式,没有public构造函数,这个类里面大部分都是静态方法,在prepare函数里面会调用sThreadLocal.get()这样就保证了一个线程里面只有一个消息循环,之所以一个线程里面只能有一个消息循环,是由于这里的loop是阻塞IO方式的,所以不能同时实时的监控多个looper调用,这个可以认为在线程内是单例模式。
下面讲解Looper里面比较重要的几个function:prepare(),prepareMainLooper(),getMainLooper(),loop()

1,prepare():为调用者线程创造looper,在这个函数里面会通过sThreadLocal.get()保证当前线程只有一个looper.
2,prepareMainLooper():这个函数是android应用程序主线程调用的保证应用程序主线程有一个消息循环,从而保证所有与UI相关的操作全部在主线程中完成,如果早其他线程中,则会出现异常。
3,getMainLooper():这个function用于得到主消息循环的
looper–MainLooper
4,loop:该函数中会不断的读取消息循环代码是:queue.next(),有兴趣的可以继续跟踪这里是如何唤醒的,在这里提示一下,这里会通过JNI调用,本质上是一个pipe(管道),管道有数据(注意不是message,仅仅起到唤醒的作用)然后looper被唤醒,得到message,然后会调用msg.target.dispatchMessage处理:这里target其实以一个handler类型的在我们发送消息的时候会指定target,这个一般我们会继承handler类,因此这里的target一般会是我们的继承的handler,调用handler的dispatchMassage,在这里我将这个函数代码粘贴如下:

//Handler.java
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

我们可以看到这个里面会有三个优先级,首先看msg.callback也就是我们发送消息的时候对消息指定的处理函数,如果我们没有指定,那么接着会判断我们实例化Handler的时候有没有指定mCallback,如果还是没有的话,只有在这种情况下才会调用handleMessage(),在这里我不得不感叹android的强大,一个小小的pipe竟然应用的如此的淋漓尽致。膜拜。

例子巩固
public class MyTimer extends Activity {  

    private int num=0;

    Timer timer = new Timer();  
    Handler handler = new Handler(){   
        public void handleMessage(Message msg) {  
            switch (msg.what) {      
            case 1:      
                setTitle("Mytimer:"+num);  
                break;      
            }      
            super.handleMessage(msg);  
        }  

    };  

    TimerTask task = new TimerTask(){    
        public void run() {  
            Message message = new Message();  
            num++;    
            message.what = 1;      
            handler.sendMessage(message);    
        }            
    };  

    public void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        setContentView(R.layout.main);  
        timer.schedule(task, 1000);  
    }  
}

在上面这个例子中为了简单我们利用了android的主消息循环,因此这里只是实现了handler,并且指定了handleMessage(),因此当我们的timer启动之后在run方法中handler.sendMessage(message);然后handleMessage()会处理,调用setTitle(“Mytimer”+num); 这样我们的UI就会每隔1秒钟更新一次,并且num会自加一次。并显示