您的位置:首页 > 移动开发 > Android开发

从源码看Android】01从Looper说起

2015-10-31 18:27 459 查看
1 为什么以这一个点为开头?

因为面试的时候被问到ThreadLocal完全不懂,前几天发现Looper内正好使用了ThreadLocal,那么从哪里跌倒就从哪里爬起来。

2 什么是Looper

首先看/sdk/docs/reference/android/os/Looper.html内的定义

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

Looper类用于在一个线程中运行一个消息循环。默认的线程不包含消息循环。

如果需要创建一个Looper,在Thread的run函数中调用Looper.prepare()

接着调用Thread.loop()函数,直到消息队列停止。

简单的说,Looper就是一个消息循环,在一个线程中不停的去消息队列里poll新消息出来给Handler处理,

3 如何创建Looper

[java] view
plaincopy

import android.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.support.v7.app.ActionBarActivity;

import android.os.Bundle;

import android.util.Log;

public class MainActivity extends ActionBarActivity {

private static final String TAG = "MainActivity";

private class LooperThread extends Thread

{

public Handler mHandler;

@Override

public void run() {

Looper.prepare();

mHandler = new Handler() {

@Override

public void handleMessage(Message msg)

{

Log.i(TAG,msg.toString());

}

};

Looper.loop();

}

}

private LooperThread mLooperThread = null;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

mLooperThread = new LooperThread();

mLooperThread.start();

}

}

这是reference中给出的子线程中Looper的构建方法

a 创建新线程,重写run函数

b 调用Looper.prepare()

c 创建Handler

d 调用Looper.loop()

我们下面分4部分解读源代码

3.1 Looper概览

- 成员变量

[java] view
plaincopy

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;

}

Looper拥有6个成员变量,其中mLogging和Tag先忽略

sMainLooper、ThreadLocal为static成员

mQueue、mThread为实例的成员变量

- 成员函数

Public Methods
voiddump(Printer pw, String prefix)
static LoopergetMainLooper()
Returns the application's main looper, which lives in the main thread of the application.
ThreadgetThread()
Return the Thread associated with this Looper.
static voidloop()
Run the message queue in this thread.
static LoopermyLooper()
Return the Looper object associated with the current thread.
static MessageQueuemyQueue()
Return the
MessageQueue
object
associated with the current thread.
static voidprepare()
Initialize the current thread as a looper.
static voidprepareMainLooper()
Initialize the current thread as a looper, marking it as an application's main looper.
voidquit()
Quits the looper.
voidquitSafely()
Quits the looper safely.
voidsetMessageLogging(Printer printer)
Control logging of messages as they are processed by this Looper.
StringtoString()
Returns a string containing a concise, human-readable description of this object.
3.2 调用Looper.prepare()

我们先看看Looper.prepare()定义

[java] view
plaincopy

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

}

从静态的prepare函数可以每一个Thread只能对应一个Looper,不然会报RuntimeException

而prepare函数所做的事情只是在全局的sThreadLocal中存放了个新的Looper

[java] view
plaincopy

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mThread = Thread.currentThread();

}

new Looper时赋值了mQueue和mThread

既然sThreadLocal是个全局的静态变量,那么所有的Looper类都共享同一个sThreadLocal

sThreadLocal.set操作定义如下

java.lang.ThreadLocal.java

[java] view
plaincopy

public void set(T value) {

Thread currentThread = Thread.currentThread();

Values values = values(currentThread);

if (values == null) {

values = initializeValues(currentThread);

}

values.put(this, value);

}

即从当前调用线程中取出values对象,然后往这个values对象存放这个Looper

需要注意的是每个Thread中都有一个values对象,

这个values对象再按照ThreadLocal<Looper> sThreadLocal对象在当前线程的values哈希表中找出对应的Looper

那么这个Looper就对应为当前线程的Looper

那么使用ThreadLocal有什么好处呢?

好处是显而易见的,如果用全局的HashMap管理一个Thread对应一个Looper,

那么增删改某个Looper对象时就需要进行同步操作,这大大增加了系统开销

而如果有一个ThreadLocal.Values对象存放在Thread里,需要用到时就直接获取,不与其他线程的数据进行交互,

那么就避免了同步带来的低效率问题,所以这个ThreadLocal正好被应用到了一个Thread对应一个Looper中

3.3 创建Handler

android.os.Handler.java

[java] view
plaincopy

public Handler() {this(null, false);}

[java] view
plaincopy

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;

}

因篇(lan)幅(de)限(xie)制(le),Handler只提到1个函数,在创建Handler时无参数代入,即调用到Handler(null,false)

在下面一个函数中handler进行了一些初始化的赋值,最重要的一步是赋值了mLooper和mQueue

Looper.myLooper()函数定义如下

[java] view
plaincopy

public static Looper myLooper() {

return sThreadLocal.get();

}

即从sThreadLocal中获取了当前Thread中得looper,赋值给了Handler,

这也就解释了为什么需要先Looper.prepare()

再new Handler的原因了

3.4 调用Looper.loop()

这一步是最重要的内容,也包含了大量的深度机制

先看函数定义

[java] view
plaincopy

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

}

}

因为是静态函数,需要先获得当前线程的looper实例,

接着调用Binder.clearCallingIdentity(),这个函数返回的是当前进程的pid、uid等信息混合后的唯一标示

接着一个for不断循环获取消息队列中得下一条消息,

如果调用的queue.next返回的msg为null则立即结束此线程

如果不为null则调用msg.target.dispatchMessage(此target为handler实例,即在当前线程调用到了handler的handlerMessage)

接着再获取一次Binder.clearCallingIdentity(),判断前后两次的唯一标示是否相同

我很奇怪既然在同一个线程为什么会需要这样的判断,后来查看Log.wtf定义发现注释如下:

What a Terrible Failure: Report a condition that should never happen.

也就是永远不会发生的情况发生了做一个记录,

因为uid是安装完app就已经生成了,pid是在程序跑起来时也生成了,如果这两个其中一个改变,

说明app被kill后重启或者重新安装,既然被kill后就不应该存在这个handler,

所以理论上应该不会发生这个情况

最后调用msg.recycle()对msg进行一个重置回收

我们来看看

android.os.Message.java的recycle函数实现

[java] view
plaincopy

public void recycle() {

clearForRecycle();

synchronized (sPoolSync) {

if (sPoolSize < MAX_POOL_SIZE) {

next = sPool;

sPool = this;

sPoolSize++;

}

}

}

正如代码中所示,首先把所有消息内容清空,然后放入对象池提供给其他对象使用

4 最佳实践

既然上面的Message使用了对象池,

那么我们在使用Message msg = new Message()就太瞎了

在需要使用新的Message时,使用Message.obtain()或者Handler.obtainMessage()是个不错的选择!

5 总结

本文只是简单的叙述了Looper的构建过程,但其实内部中mQueue.next()是夸线程调用的最重要体现,mQueue是如何把其他线程上的msg dispatch给当前线程的Handler,我将在下一讲中给出解释,顺便具体学习下MessageQueue,也一定会讲到MessageQueue cpp层的一部分源代码。
http://blog.csdn.net/ashqal/article/details/23177397
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: