Android:异步处理之Handler、Looper、MessageQueue之间的恩怨(三)
前言
如果你在阅读本文之前,你不知道Handler在Android中为何物,我建议你先看看本系列的第一篇博文《Android:异步处理之Handler+Thread的应用(一)》;我们都知道在Android系统中不能在子线程中直接更新UI界面,所以我们一般借助Handler+Thread或者AsyncTask这两种方法来实现UI界面的更新。而Handler+Thread这方法其实就是子线程向UI主线程进行消息传递,通知UI主线程去更新界面的一套机制。因为有时候面试官比较喜欢和蔼可亲的考你Handler的这套机制,所以我们结合源代码深入的研究这套通讯机制是灰常有必要的,你想想如果能鄙视一下面试官,呵呵o(╯□╰)o。。
概述
谷歌的这套消息机制是参考windows设计的,姑爷微爷之间有啥专利官司咱也不关心。一般来说,线程都会通过Looper来建立自己的消息循环,并且锁定一个FIFO的消息队列MessageQueue,Handler通过Looper来实现Message(消息)在MessageQueue中的存取。每一个Hanlder在实例化的时候都会自动或者手动绑定一个Looper,间接向一个MessageQueue发送Message,所以Handler也封装了消息发送和接收的接口。
入门例子
看概述好闷的,琢磨文字不说,晦涩又难懂,记得住又成一个大问题。来不如来个例子瞧瞧比较实在,所以我在这里给大家写了一个向子线程发送消息并显示输出的例子,强调一下下哦,是向子线程哟。
主要代码如下:
public class MainActivity extends ActionBarActivity { private Handler handler;
private Button btn; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); btn = (Button) findViewById(R.id.sendmsg); new HandlerThread().start();//启动子线程 btn.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
handler.sendEmptyMessage(0);//向子线程发送消息
}
});
} class HandlerThread extends Thread{
@Override
public void run() {
//开始建立消息循环
Looper.prepare();//初始化Looper
handler = new Handler(){//默认绑定本线程的Looper
@Override
public void handleMessage(Message msg) {
switch(msg.what){
case 0:
Toast.makeText(MainActivity.this, "子线程收到消息", Toast.LENGTH_SHORT).show();
}
}
};
Looper.loop();//启动消息循环
}
}
}
布局文件:
<LinearLayout 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"
android:orientation="vertical"> <Button
android:id="@+id/sendmsg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="向子线程放炮!"
/> </LinearLayout>
我们只需要点击按钮,发送成功。。。。。
我在里简单说一下消息发送的过程:
1、启动一个子线程,并在子线程初始化一个Looper。
2、在HandlerThread中实例化Handler,Handler自动绑定上当前线程的Looper。
3、重写Handler里面的消息处理方法。
4、执行Looper.loop()启动消息循环,子线程进入等待消息状态。
做个小研究
当然,由例子入手讲解才容易理解。我们就通过上面梳理好的消息发送流程,结合源代码来探究消息循环的建立、消息的分发和处理的原理。
1、Looper的初始化
我们进入Looper中查看源码:
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));
}
在我们调用Looper的prepare这个静态方法的时候,我们发现这个线程创建了一个Looper实例,并将其赋值给sThreadLocal这个线程的局部变量中,当然我们可以肯定这个sThreadLocal是当前的线程私有的,不信自己度娘去。我们接下来就要看Looper的构造方法。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
相信大家的眼睛都是雪亮的吧?!在Looper()中,实例化了一个消息队列(MessageQueue)!并且如我们所愿的绑定到了mQueue这个局部变量上,在这里我们可以得出这么一个结论:调用Looper. prepare()的线程就建立起一个消息循环的对象,但是!并还没有开始展开消息循环这件大事件。
2、实例化Handler并绑定当前线程的Looper
我们可以看看Handler的源代码——Handler的构造方法
public Handler() {
this(null, false);
} 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 = new Handler() 调用到了Handler(Callback callback, boolean async)这个方法;我们发现mLooper = Looper.myLooper()把线程中的Looper绑定到了Handler上,通过mQueue = mLooper.mQueue获取了线程的消息队列,我当然也可以换句话说:Handler已经绑定到了创建此Handler对象的线程的消息队列上了,所以咱们可以开始干坏事了。。。。
3、重写Handler的handleMessage()方法
public void handleMessage(Message msg) {}
没啥好说的,一个空方法而已,提供我们override的入口函数。
4、通过Looper.loop()启动消息循环
还是上面的思路,我们进入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;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity(); for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
return;
} 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);
} 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();
}
}
在loop()的这个静态方法中,我们可以注意到for (;;)这个方法,这是死胡同死循环,所以我们将其称作为“消息循环”,说起来挺形象滴。在消息循环中会调用queue.next()来获取消息队列中排队等待处理的消息,并将其赋值到msg这个变量上;接下来就判断如果msg != null 就开始分发消息,也就是执行msg.target.dispatchMessage(msg)。在分发消息结束后,将会回收掉这个消息,体现在msg.recycle()这个函数上。
msg.target是一个handler对象,表示需要处理这个消息的handler对象,所以我们回到Handler看看dispatchMessage()这个方法了:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
不知道大家有没有一眼发现handleMessage()这个方法,这可不是我们在第三步重写Handler中的方法么。真相大白,当 msg.callback != null 并且 mCallback != null 时将会调用 handleMessage(msg) 来处理其他线程发送来的消息,我们通过覆盖这个方法来实现我们具体的消息处理过程;这也就是Handler消息处理机制的全部内容。
做个小结吧
通读全文,我们可以知道消息循环机制的核心就是Looper,因为Looper持有了MessageQueue的对象,并且可以被一个线程设为该线程的一个局部变量,我们可以这么认为这个线程通过Looper拥有了一个消息队列。而Handler的用处就是封装了消息发送和消息处理的方法,在线程通信中,线程可以通过Handler发送消息给创建Handler的线程,通过Looper将消息放入进入消息接收线程的消息队列,等待Looper取出消息并在最后交给Handler处理具体消息。
再说一句
我们会发现在Activity中实例化一个Handler并不需要Looper.prepare()来初始化一个Looper和Looper.loop()来启动消息循环,因为Activity在构造过程中已经对Looper进行了初始化并且建立了消息循环,参见ActivityThread.java中的代码:
public final class ActivityThread {
public static final void main(String[] args) {
......
Looper.prepareMainLooper();
......
ActivityThread thread = new ActivityThread();
thread.attach(false);
......
Looper.loop();
......
thread.detach();
......
}
}
Android应用程序进程在启动的时候,会在进程中加载ActivityThread类,并且执行这个类的main函数,应用程序的消息循环过程就是在这个main函数里面实现的;如果大家想要更深入了解的话,建议大家去研究下Activity的启动机制哈。
作者:enjoy风铃
出处:http://www.cnblogs.com/net168/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则下次不给你转载了。
Android:异步处理之Handler、Looper、MessageQueue之间的恩怨(三)的更多相关文章
- Handler Looper MessageQueue 之间的关系
Handler Looper MessageQueue 之间的关系 handler在安卓开发中常用于更新界面ui,以及其他在主线程中的操作.内部结构大概图为: 1.handler持有一个Looper对 ...
- 讲讲Handler+Looper+MessageQueue 关系
Handler+Looper+MessageQueue这三者的关系其实就是Android的消息机制.这块内容相比开发人员都不陌生,在面试中,或者日常开发中都会碰到,今天就来讲这三者的关系. 概述: H ...
- Handler+Looper+MessageQueue深入详解
概述:Android中的异步处理机制由四部分组成:Handler+Looper+MessageQueue+message,用于实现线程间的通信. 用到的概念: Handler: 主要作用是发送消息和处 ...
- Android异步处理三:Handler+Looper+MessageQueue深入详解
在<Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面>中,我们讲到使用Thread+Handler的方式来实现界面的更新,其实是在非UI线程发送消息到U ...
- Android之消息机制Handler,Looper,Message解析
PS:由于感冒原因,本篇写的有点没有主干,大家凑合看吧.. 学习内容: 1.MessageQueue,Looper,MessageQueue的作用. 2.子线程向主线程中发送消息 3.主线程向子线程中 ...
- Handler,Looper,MessageQueue流程梳理
目的:handle的出现主要是为了解决线程间通讯. 举个例子,android是不允许在主线程中访问网络,因为这样会阻塞主线程,影响性能,所以访问网络都是放在子线程中执行,对于网络返回的结果则需要显示在 ...
- Android消息机制探索(Handler,Looper,Message,MessageQueue)
概览 Android消息机制是Android操作系统中比较重要的一块.具体使用方法在这里不再阐述,可以参考Android的官方开发文档. 消息机制的主要用途有两方面: 1.线程之间的通信.比如在子线程 ...
- android学习11——Handler,Looper,MessageQueue工作原理
Message是Handler接收和处理的消息对象. 每个线程只能拥有一个Looper.它的loop方法读取MessageQueue中的消息,读到消息之后就把消息交给发送该消息的Handler进行处理 ...
- Android异步处理系列文章四篇之一使用Thread+Handler实现非UI线程更新UI界面
目录: Android异步处理一:使用Thread+Handler实现非UI线程更新UI界面Android异步处理二:使用AsyncTask异步更新UI界面Android异步处理三:Handler+L ...
随机推荐
- 18) maven 项目结构:继承
Project Inheritance [ɪn'herɪt(ə)ns] https://maven.apache.org/guides/introduction/introduction-to-the ...
- Hdu1054 Strategic Game(最小覆盖点集)
Strategic Game Problem Description Bob enjoys playing computer games, especially strategic games, bu ...
- hdu 3015
这个题给你一堆树,每棵树的位置x和高度h都给你 f[i]代表这棵树的位置排名,s[i]代表这棵树的高度排名 问你任意两棵树的(f[i] - f[j])*min(s[i],s[j])和 (f[i]-f[ ...
- 普通用户开放 sudo 权限
大家都知道 linux 每个目录都是有权限的,所以如果要想在此目录下读写,则要有这个目录的权限,或者就是有 sudo 权限,那怎么给普通用户赋予 sudo 权限呢,下面我们来看一下: 1.先用 roo ...
- 调用 TBrowseForFolder 的正确姿势
[教程]调用 TBrowseForFolder 的正确姿势 2017-08-22 • C++ Builder.Delphi.教程 • 暂无评论 • swish •浏览 562 次 TBrowseFor ...
- [leed code 179] Largest Number
1 题目 Given a list of non negative integers, arrange them such that they form the largest number. For ...
- 【VB.NET】利用纯真IP数据库查询IP地址及信息
几年前从某个博客抄来的,已经忘记原地址了,如果需要C#版的,可以在博客园搜到吧.我因为自己用,所以转换为了VBNET代码,而且也放置了很久,今天无意间翻出来,就分享给大家吧. 首先,先下载 纯真数据库 ...
- ASP.NET MVC5 高级编程-学习日记-第二章 控制器
2.1 控制器的角色 MVC模式中的控制器(Controller)主要负责响应用户的输入,冰球在响应时修改模型(Model).通过这种方式,MVC模式中的控制器主要关注的是应用程序流.输入数据的处理, ...
- 【文文殿下】[AH2017/HNOI2017]礼物
题解 二项式展开,然后暴力FFT就好了.会发现有一个卷积与c无关,我们找一个最小的项就行了. Tips:记得要倍长其中一个数组,防止FFT出锅 代码如下: #include<bits/stdc+ ...
- Linux系统用户与属组管理(3)
好了,终于要到了管理 Linux 账号的时刻了,对于 Linux 有一定的熟悉度之后,再来就是要管理连上 Linux 的账号问题了,这个账号的问题可大可小,大到可以限制他使用 Linux 主机的各项资 ...