Android异步消息处理线程之----Looper+MessageQueue+Handler

博文来源:http://blog.csdn.net/shift_wwx


对于普通的线程,运行完run() 就会结束。但是异步消息处理线程,是指线程启动后会进入一个无限的循环中,每循环一次,就会从内部的消息队列中取出一个消息,并会调用相应的消息处理函数,执行完一个消息会继续循环。如果消息队列为空,线程会暂停,直到消息队列里面有新的消息。


1. Andorid实现异步线程方法:

Android异步消息处理线程之----Looper+MessageQueue+Handler_第1张图片

在线程内部可以有一个或者多个Handler对象,外部通过Handler向异步线程发送消息,消息由Handler传给MessageQueue对象中。线程内部只能包含一个MessageQueue对象,线程主执行函数中从MessageQueue中读取消息,并回调Handler对象中的回调函数handleMessage()。

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

2. Looper

Looper的作用有两点,第一是调用静态函数prepare()为线程创建一个消息队列;第二是调用静态函数loop(),使调用该函数的线程进行无限循环,并从消息队列中读取消息。

     /** 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));
    }
code中需要了解的有两点:

a). sThreadLocal对象
b). new Looper(quitAllowed)

在代码中,变量sThreadLocal的类型是ThreadLocal,该类的作用是提供“线程局部存储”,那什么是线程局部存储(TLS)呢?这个问题可以从变量的作用域的角度来理解。

变量常见作用域分为以下几种:

(1)函数内部的变量:其作用域是该函数,在函数调用的时候,该变量会重新初始化。

(2)类内部的变量:其作用域是该类对象,即只要对象没有销毁,那么这个变量在对象内部的值是不变的。

(3)类内部的局部变量:其作用域是整个进程,则只要在该进程中个,这个变量的值是一直保持,无论该类构造过多少对象,这个值是一样的。

对于类内部的变量而言,无论从进程中的哪个线程引用该变量,其值总是一样的。因为在编译器内部为这个局部变量独立分配了一个空间。但是,有时候我们想从一个线程中引用这个变量的值是一样的,不同的线程引用这个变量的时候值是不一样的,即我们需要一种作用域为线程变量定义,这就是”线程局部存储“。

public class ThreadLocal<T> {

    /* Thanks to Josh Bloch and Doug Lea for code reviews and impl advice. */

    /**
     * Creates a new thread-local variable.
     */
    public ThreadLocal() {}

    /**
     * Returns the value of this variable for the current thread. If an entry
     * doesn't yet exist for this variable on this thread, this method will
     * create an entry, populating the value with the result of
     * {@link #initialValue()}.
     *
     * @return the current value of the variable for the calling thread.
     */
    @SuppressWarnings("unchecked")
    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);
    }

    /**
     * Provides the initial value of this variable for the current thread.
     * The default implementation returns {@code null}.
     *
     * @return the initial value of the variable.
     */
    protected T initialValue() {
        return null;
    }

    /**
     * Sets the value of this variable for the current thread. If set to
     * {@code null}, the value will be set to null and the underlying entry will
     * still be present.
     *
     * @param value the new value of the variable for the caller thread.
     */
    public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }
上面是ThreadLocal.java中的部分code,可以看出ThreadLocal是个模板,这样就方便各种不同的对象类型。例如,Looper中的sThreadLocal是这样定义的:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
详细的code可以参考/libcore/luni/src/main/java/java/lang/ThreadLocal.java

Looper.prepare()中另一点就是Looper的构造函数:

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }
从之前prepare中可以看出,sThreadLocal.get()!=null的时候,会出现exception提示不能重复prepare,也就是说一个线程只能调用一次prepare,只能创建一个Looper对象,也只能创建唯一的MessageQueue对象。

注意:

(1)Activity的MainUI线程默认是有消息队列的。所以在Activity中新建Handler时,不需要先调用Looper.prepare()

        主线程创建时,会创建一个默认的Looper对象,而Looper对象的创建,将自动创建一个MessageQueue。

        其他非主线程,不会自动创建Looper,要需要的时候,通过调用prepare函数来实现。

这里可以简单的说一下,在应用程序启动的时候会创建一个thread,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");
    }
详细的不做说明,针对应用程序进程启动过程,网上资料很多,后期也可能做一些自己的分析和补充。

(2) 什么时候用Looper.prepare(),当你的线程想拥有自己的MessageQueue的时候先Looper.prepare(),然后Looper.loop()

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();
        }
    }
code中可以看出如下几点:

(1)loop函数必须是在prepare函数之后调用,不然会有提示:

throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
(2)for循环是无限循环,会一直取消息,如果没有消息,会暂时的挂起来
(3)msg.target.dispatchMessage(msg); msg.target就是Handler

    /**
     * Handle system messages here.
     */
    public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }
new Handler的时候如果定义了Runnable的话就会调用handleCallback(msg),如果定义了final Callback mCallback;就会调用mCallback.handleMessage(msg),如果重定义了handleMessage()就会调用handleMessage(),所以,一般我们在定义Handler的时候都会重定义handleMessage(),为了回调。
(4)处理完一个message的时候,都会调用msg.recycle()回收系统资源

    /**
     * Return a Message instance to the global pool.  You MUST NOT touch
     * the Message after calling this function -- it has effectively been
     * freed.
     */
    public void recycle() {
        clearForRecycle();

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }
注意:写在Looper.loop()之后的代码不会被执行,这个函数内部应该是一个循环,当调用mHandler.getLooper().quit()后,loop才会中止,其后的代码才能得以运行。


3. MessageQueue

消息队列采用的是排队方式对消息进行处理,即先到的消息会先处理,但如果消息本身被指定了处理时间,则必须等到该时刻才能处理该消息。

MessageQueue与native的联系比较多,从MessageQueue创建就开始了:

    MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
static jint android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jint>(nativeMessageQueue);
}
在nativeInit中,new了一个Native层的MessageQueue的对象,并将其地址保存在了Java层MessageQueue的成员mPtr中,Android中有好多这样的实现,一个类在Java层与Native层都有实现,通过JNI的GetFieldID与SetIntField把Native层的类的实例地址保存到Java层类的实例的mPtr成员中,比如Parcel。
再看NativeMessageQueue的实现:

NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}
在NativeMessageQueue的构造函数中获得了一个Native层的Looper对象,Native层的Looper也使用了线程本地存储,注意new Looper时传入了参数false。
如果需要详细理解需要查看source code,从android_os_MessageQueue.cpp开始。

有一篇博文很不错,可以借鉴。http://www.cnblogs.com/angeldevil/p/3340644.html


4. Handler

尽管MessageQueue提供了直接读/写的函数接口,但对于应用程序员而言,一般不直接读/写消息队列。

程序员一般使用Handler类向消息队列发送消息,并重载Handler的handleMessage函数添加消息处理代码。

    boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
通过MessageQueue 中可以看到,消息队列处理是需要这个target, 也就是Handler。

handler对象只能添加到有消息队列的线程中,否则会发生异常。因此,在构造Handler对象前,必须已经执行过Looper.prepare(),但prepare()不能被执行两次。

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        mLooper = Looper.myLooper();
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

一个线程中可以包含多个Handler对象。在Looper.loop函数中,不同的Message对应不同的Handler对象,从而回调不同的handleMessage函数。

具体事例不做介绍,可以尝试线程与主线程、主线程与其他线程之间通信。至于发msg可以看一下:Handler sendMessage 与 obtainMessage (sendToTarget)比较


小结:

1)Handler的处理过程运行在创建Handler的线程里
2)一个Looper对应一个MessageQueue
3)一个线程对应一个Looper
4)一个Looper可以对应多个Handler
5)不确定当前线程时,更新UI时尽量调用post方法


异步消息处理线程处理用于多线程的消息传递外,它还和跨进程调用(IPC)一起被使用,用于实现异步跨进程调用。




你可能感兴趣的:(线程,android,异步,消息)