在处理android多线程消息机制时,会因为Handler而了解到有Looper这么个东西。有众多高手解释道:Looper是消息泵。它内部维护了一个消息队列,即MessageQueue。Looper的职责就是负责抽取MessageQueue中的消息让他去找宿主。
对于一些普通的程序开发工作而言,只需要了解Handler的用法即可,几乎接触不到Looper,更别说是MessageQueue。但是如果想以后能写出独特优秀的程序或深入了解别人写的框架,了解Looper的原理是必须的。
今天,我们一起来分析Looper的原理和用法,从源码开始分析吧。说到这,有人可能会说“哥们,能否别这么矫情,有必要千篇一律都开篇分析源码吗?”,我想说不要喷,你不看别人的源码,怎么知道其原理?光看别人告诉你的。万一别人理解的是错的呢?
开始吧,朋友。
通过上面的这段解释和代码示例,了解到:
1、Looper是用来运行消息循环的。
2、默认创建的线程是没有Looper的,必须在线程中调用Looper.prepare()和Looper.loop()方法来创建Looper。
3、在这个消息循环中,有一个内置的MessageQueue消息队列。如果消息队列中没有消息,则该线程休眠,一旦有消息时,Looper开始工作。整个过程知道调用quit()方法终止。
4、一个Looper对象对应一个内置的消息队列,一对一关系。
*5、Main线程为什么可以修改处理消息,其实是因为Main线程中开启了Looper的消息循环:
此处暂时淡化更新UI的问题,最后再讨论。
ActivityThread部分源码:
Looper.prepareMainLooper()方法源码:
搜嘎,在Activity中之所以可以处理消息,是因为其中已经初始化了Handler和一个含有Looper的线程。发送到UI的所有消息都由这个LooperThread专门处理。故像我前面分析的,普通的app开发工作中,我们多需要和UI交互处理,正好在Activity中已经有一个Looper线程了,因此在子线程中不需要再创建Looper循环了。也就是说一般我们只需要会用handler就够了,很少要你去处理Looper和MessageQueue。然而事已至此了,还是来看看Looper的源码,最后会附上一个例子,看看一个Looper线程的功效如何。
在阅读的过程中,重点关注代码中的中文注释,很多关键点都有说明,不要错过。
接下来做一个简单的looper线程实验,看看LooperThread的用:
MyLooperThread,含有Looper的线程代码:
xml页面代码:
说明:
上例中,主要是自定义一个线程,在线程中调用prepare()和loop()方法,启动消息循环,让当前线程成为looper线程。那么这个线程就专门负责消息处理了。在其内部定义了一个handler对象,关联当前looper。在主线程中创建和启动looper线程对象,获取looper线程的handler发送消息。消息发出后就有handler关联的looper从其关联的MessageQueue中抽取并,派遣到宿主handler执行。由于handler是在looper线程中创建的,且有实现方法handleMessage,故在handleMessage方法执行消息。这也就是looper线程的用法,比较少见。上例中还有一个疑问,到底哪些线程可以更新ui?有人说是主线程,有人说只要是looper线程都可以。
这个问题,上例中隐约说明了:
1、从MainActivity中传递了textView的引用到looperThread中,在looperThread中尝试修改textView的text属性。以前我在较低的版本试过没问题;现在再是,仍然可以修改,只不过会抛异常CalledFromWrongThreadException,故我强行try catch了。
2、参考文章 http://blog.csdn.net/imyfriend/article/details/6877959和根据具体异常信息的提示,发现跟ui创建的位置有关。如果ui是在main线程创建的,则在子线程中不可以更改此ui;如果在含有looper的子线程中创建的ui,则可以任意修改。参见上例,针对这个问题以及ViewRoot问题,若有高见欢迎探讨。由于篇幅问题,不在此多聊了。
对于一些普通的程序开发工作而言,只需要了解Handler的用法即可,几乎接触不到Looper,更别说是MessageQueue。但是如果想以后能写出独特优秀的程序或深入了解别人写的框架,了解Looper的原理是必须的。
今天,我们一起来分析Looper的原理和用法,从源码开始分析吧。说到这,有人可能会说“哥们,能否别这么矫情,有必要千篇一律都开篇分析源码吗?”,我想说不要喷,你不看别人的源码,怎么知道其原理?光看别人告诉你的。万一别人理解的是错的呢?
开始吧,朋友。
先来一段Looper类头部的解释说明:
/**
* 这是用于运行线程的消息循环的类。默认的线程没有与它们关联的消息循环;
* 在运行循环的线程中,调动prepare方法可以创建一个Looper对象, 然后调动loop方法,循环处理它的
* 消息队列中的消息,知道循环退出为止。
* 与消息循环相关的大多数交互是通过Handler类处理的。
* 下面是一个Looper线程上线的经典例子,
* 使用prepare和loop两个方法来初始化一个含有Looper的线程,简称Looper线程。
*
* <pre>
* 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();
* }
* }</pre>
*/
通过上面的这段解释和代码示例,了解到:
1、Looper是用来运行消息循环的。
2、默认创建的线程是没有Looper的,必须在线程中调用Looper.prepare()和Looper.loop()方法来创建Looper。
3、在这个消息循环中,有一个内置的MessageQueue消息队列。如果消息队列中没有消息,则该线程休眠,一旦有消息时,Looper开始工作。整个过程知道调用quit()方法终止。
4、一个Looper对象对应一个内置的消息队列,一对一关系。
*5、Main线程为什么可以修改处理消息,其实是因为Main线程中开启了Looper的消息循环:
此处暂时淡化更新UI的问题,最后再讨论。
Activity部分源码:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
ActivityThread mMainThread;
// we must have a handler before the FragmentController is constructed
final Handler mHandler = new Handler();
//...
}
ActivityThread部分源码:
public final class ActivityThread {
final Looper mLooper = Looper.myLooper();
public static void main(String[] args) {
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
}
Looper.prepareMainLooper()方法源码:
/**
* 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();
}
}
Looper.prepare(false)方法源码:
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.
* 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);
}
搜嘎,在Activity中之所以可以处理消息,是因为其中已经初始化了Handler和一个含有Looper的线程。发送到UI的所有消息都由这个LooperThread专门处理。故像我前面分析的,普通的app开发工作中,我们多需要和UI交互处理,正好在Activity中已经有一个Looper线程了,因此在子线程中不需要再创建Looper循环了。也就是说一般我们只需要会用handler就够了,很少要你去处理Looper和MessageQueue。然而事已至此了,还是来看看Looper的源码,最后会附上一个例子,看看一个Looper线程的功效如何。
//这是个最终类,不能被继承
public final class Looper {
//sThreadLocal.get()方法会返回null,除非你调用了prepare()方法。
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
//这就是传说中的消息队列,有Looper自己管理
final MessageQueue mQueue;
final Thread mThread;
/** 初始化当前线程的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);
}
//每个线程只能创建一个looper对象
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));
}
/**
* 初始化当前的线程作为一个looper,标记其为应用程序的main looper。
* 应用程序的主looper是由Android环境的创建, 所以你没有必要自己主动的调用此方法。
* 请查看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();
}
}
/**
* 获取application UIThread的looper,加了线程类锁
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
/**
* 在当前线程中运行消息队列循环,直到调用quit()方法结束循环。
*/
public static void loop() {
final Looper me = myLooper();//获取当前线程自己的looper
if (me == null) {//如果looper为null,可能是prepare()方法没有调用
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
// 确保这个线程的身份是本地进程
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
//死循环,消息调度
for (;;) {
Message msg = queue.next(); // 此方法可能会阻塞
if (msg == null) {
// 如果没有消息关联到这个消息队列中时就退出
return;
}
//此处是让message去找自己的宿主,后期
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();
}
}
/**
* 返回当前线程的looper对象,如果当前线程还没有关联上looper,可能会返回null。
* 例如,当前线程还没有执行到Looper.prepare()方法时,你就调用了myLooper()方法。
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/**
* 返回与当前线程关联的MessageQueue对象。
* 调用此方法前必须确保当前线程已经运行了一个looper,
* 否则必定NullPointerException登门拜访
*/
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
//在构造Looper的时候,内部构造了一个MessageQueue对象
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
/**
* 如果当前线程是looper线程,则返回true,否则返回false.
*/
public boolean isCurrentThread() {
return Thread.currentThread() == mThread;
}
/**
* Control logging of messages as they are processed by this Looper. If
* enabled, a log message will be written to <var>printer</var>
* at the beginning and ending of each message dispatch, identifying the
* target Handler and message contents.
*
* @param printer A Printer object that will receive log messages, or
* null to disable message logging.
*/
public void setMessageLogging(@Nullable Printer printer) {
mLogging = printer;
}
/**
* 退出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);
}
/**
* 安全的退出消息循环
* <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);
}
/**
* 返回与当前线程关联的looper
*
* @return The looper's thread.
*/
public @NonNull Thread getThread() {
return mThread;
}
/**
* 返回当前线程的looper的MessageQueue
*
* @return The looper's message queue.
*/
public @NonNull MessageQueue getQueue() {
return mQueue;
}
/**
* Dumps the state of the looper for debugging purposes.
*
* @param pw A printer to receive the contents of the dump.
* @param prefix A prefix to prepend to each line which is printed.
*/
public void dump(@NonNull Printer pw, @NonNull String prefix) {
pw.println(prefix + toString());
mQueue.dump(pw, prefix + " ");
}
@Override
public String toString() {
return "Looper (" + mThread.getName() + ", tid " + mThread.getId()
+ ") {" + Integer.toHexString(System.identityHashCode(this)) + "}";
}
}
在阅读的过程中,重点关注代码中的中文注释,很多关键点都有说明,不要错过。
接下来做一个简单的looper线程实验,看看LooperThread的用:
MainActivity代码:
public class MainActivity extends Activity implements OnClickListener {
private Button uiBtn, looperBtn, quitBtn;
private TextView show;
private MyLooperThread looperThread;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
uiBtn = (Button) findViewById(R.id.uiBtn);
looperBtn = (Button) findViewById(R.id.looperBtn);
quitBtn = (Button) findViewById(R.id.quitBtn);
show = (TextView) findViewById(R.id.show);
uiBtn.setOnClickListener(this);
looperBtn.setOnClickListener(this);
quitBtn.setOnClickListener(this);
// 创建和启动looper线程
looperThread = new MyLooperThread(this,show);
looperThread.start();
}
@Override
public void onClick(View v) {
Button btn = (Button) v;
switch (v.getId()) {
case R.id.uiBtn:
// 在ui中自己修改textview
show.setText(btn.getText());
break;
case R.id.quitBtn:
// 终止looper线程
looperThread.getLooper().quit();
break;
case R.id.looperBtn:
// 获取looper中的handler,发送消息让handler处理,更新ui
looperThread.getHandler().obtainMessage(200, btn.getText()).sendToTarget();
break;
default:
break;
}
}
}
MyLooperThread,含有Looper的线程代码:
/**
* 用来处理looper循环的线程 用于测试,在looper线程中看能否修改ui
*
* @author duke
*/
public class MyLooperThread extends Thread {
private static final String TAG = MyLooperThread.class.getSimpleName();
// 子线程的looper
private Looper myLooper;
// 子线程的handler
private Handler mHandler;
// 用于测试的textview
private TextView testView;
private Activity activity;
public Looper getLooper() {
return myLooper;
}
public Handler getHandler() {
return mHandler;
}
public MyLooperThread(Context context, TextView view) {
this.activity = (Activity) context;
testView = view;
}
@Override
public void run() {
super.run();
// 调用了此方法后,当前线程拥有了一个looper对象
Looper.prepare();
Log.v(TAG, "消息循环开始");
if (myLooper == null) {
while (myLooper == null) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 调用此方法获取当前线程的looper对象
myLooper = Looper.myLooper();
}
}
// 当前handler与当前线程的looper关联
mHandler = new Handler(myLooper) {
@Override
public void handleMessage(Message msg) {
Log.v(TAG, "处理消息:" + msg.obj);
//this thread's TextView
addTextViewInChildThread().setText(String.valueOf(msg.obj));
//main thread's TextView
//testView.setText(String.valueOf(msg.obj));
}
};
Looper.loop();
Log.v(TAG, "looper消息循环结束,线程终止");
}
/**
* 创建TextView
* http://blog.csdn.net/imyfriend/article/details/6877959
* @return
*/
private TextView addTextViewInChildThread() {
TextView textView = new TextView(activity);
//ViewGroup.LayoutParams txparams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
//textView.setLayoutParams(txparams);
textView.setBackgroundColor(Color.GRAY);
textView.setGravity(Gravity.CENTER);
textView.setTextSize(20);
WindowManager windowManager = activity.getWindowManager();
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,//width of textView
WindowManager.LayoutParams.WRAP_CONTENT,//height of textView
0, 0,//x,y of textView in screen
WindowManager.LayoutParams.FIRST_SUB_WINDOW,
WindowManager.LayoutParams.TYPE_TOAST,
PixelFormat.TRANSPARENT);
windowManager.addView(textView, params);
return textView;
}
}
xml页面代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/uiBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="UI线程修改文本" />
<Button
android:id="@+id/looperBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/uiBtn"
android:text="Looper线程修改文本" />
<Button
android:id="@+id/quitBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/looperBtn"
android:text="Looper quit()" />
<TextView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/quitBtn"
android:gravity="center"
android:text="显示结果"
android:textSize="30sp" />
</RelativeLayout>
说明:
上例中,主要是自定义一个线程,在线程中调用prepare()和loop()方法,启动消息循环,让当前线程成为looper线程。那么这个线程就专门负责消息处理了。在其内部定义了一个handler对象,关联当前looper。在主线程中创建和启动looper线程对象,获取looper线程的handler发送消息。消息发出后就有handler关联的looper从其关联的MessageQueue中抽取并,派遣到宿主handler执行。由于handler是在looper线程中创建的,且有实现方法handleMessage,故在handleMessage方法执行消息。这也就是looper线程的用法,比较少见。上例中还有一个疑问,到底哪些线程可以更新ui?有人说是主线程,有人说只要是looper线程都可以。
这个问题,上例中隐约说明了:
1、从MainActivity中传递了textView的引用到looperThread中,在looperThread中尝试修改textView的text属性。以前我在较低的版本试过没问题;现在再是,仍然可以修改,只不过会抛异常CalledFromWrongThreadException,故我强行try catch了。
2、参考文章 http://blog.csdn.net/imyfriend/article/details/6877959和根据具体异常信息的提示,发现跟ui创建的位置有关。如果ui是在main线程创建的,则在子线程中不可以更改此ui;如果在含有looper的子线程中创建的ui,则可以任意修改。参见上例,针对这个问题以及ViewRoot问题,若有高见欢迎探讨。由于篇幅问题,不在此多聊了。