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会自加一次。并显示