用文字札记描绘自己 android学习之路
转载请保留出处 by Qiao http://blog.csdn.net/qiaoidea/article/details/45115047
Android更新Ui进阶精解(一) android ui线程检查机制
Android更新Ui进阶精解(二) android 线程更新UI机制
第一篇讲了对Ui线程更新的方法和见解,然后接着讲了线程检查机制,这里来详细分析下更新Ui的核心——Android中消息系统模型。当然,这里要讲的其实也已经不再简简单单地是更新Ui的范畴了。不过还是很值得学习和分析一下。另外,其实网上关于这方面的讲解也有很多了,本篇也是综合整理并用自己的理解加以描述和概括。同时也感谢有更高造诣的大大能予以批评指正。
Android中的消息机制主要有如下几个要点,这里也着重围绕这些内容来讲解:
1. Handler 调度消息和runnable对象在不同线程中执行相应动作。
2. Looper消息泵,用来为一个线程开启一个消息循环
3. MessageQueue 遵循FIFO先进先出规则的消息队列,以链表形式存取Message,供looper提取
为了方便分析,借用一下找到的模型图综合看一下:
首先在一个线程中初始化一个looper并prepare(准备),然后创建该looper对象的处理对象Handler,接着当需要交互变更时,可以在其他线程(或自身线程)中使用handler发消息至该消息队列MessageQueue,最后looper会自动有序抽取消息(没有消息则挂起),交给handler执行消息处理逻辑。
Orz,我的概念描述还是一塌糊涂,还是转代码说明吧:
比如我们有个线程专门负责一类处理逻辑,并且只允许该线程来处理这类逻辑,那么我们怎么做到呢?
1. 在一个线程里边定义一个Looper
Looper.prepare(); //稍微有点儿多,详细见下文
2.定义一个处理消息的Handler
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//处理逻辑
}
};
3.启动looper,并开始工作,轮询消息
Looper.loop(); //详细见下文
//要停止的话,使用Looper.quit();
4.在其他线程将要处理的数据data或回调对象callback以消息Message模式通过Handler发至该消息队列MessageQueue
handler.sendMessage(msg)
5.Looper的loop()方法会从消息队列中取到该消息并唤醒处理逻辑
//即loop()方法中的代码
for (;;) { //显然这个死循环一直在等待消息到来并处理
Message msg = queue.next(); // 取一条消息
if (msg == null) {
return;
}
msg.target.dispatchMessage(msg); //调用消息绑定的handler执行处理逻辑
//other code....
}
6.handler跳转到执行处理逻辑的过程
public void dispatchMessage(Message msg) {
if (msg.callback != null) { //如果有回调,则调用
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
//调用回调方法
private static void handleCallback(Message message) {
message.callback.run();
}
以上便是整个消息系统的过程,后边我们会逐个分析精讲。
在ActivityManagerService为Android应用程序创建新的进程并启动activity时候,主线程ActivityThread首先被创建。该进程 Process.java@start(“android.app.ActivityThread”,…)会加载执行ActivityThread的静态成员函数main,打开该方法:
public static void main(String[] args) {
//other code.. 我们只看有用的部分,其他暂略过
Looper.prepareMainLooper(); //准备looper,注,绑定的为当前主线程
ActivityThread thread = new ActivityThread();//开启一个新ActivityThread线程
thread.attach(false);//最后执行到activity
//other code..
Looper.loop(); //启动looper
这个静态函数做了两件事情,一是在主线程中创建了一个ActivityThread实例,二是通过Looper类使主线程进入消息循环。
然后,代码经过一系列逻辑( ActivityThread.attach->IActivityManager. attachApplication -> attachApplicationApplicationThread.scheduleLaunchActivity ->… ->ActivityThread.performLaunchActivity ),最终会调用activity的attach方法。
我们打开activity类。可以看到,它定义了uiThread和Handler参数
ActivityThread mMainThread;//对应上边的ActivityThread线程
private Thread mUiThread;//主Ui线程
final Handler mHandler = new Handler();//这个handler就是activity用来处理Ui的了。我们自己定义的handler其实等于重新定义了这个mHandler;
我们来看activity的attach方法:
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, IVoiceInteractor voiceInteractor) {
mUiThread = Thread.currentThread();//当前主线程Ui线程
mMainThread = aThread; //对应上边的ActivityThread线程
}
所以,当我们要更新UI的时候,都会用到sendMessage,比如使用runOnUiThread,来看下这个方法
public final void runOnUiThread(Runnable action) {
/**
*如果当前线程不为Ui主线程则使用定义好的mHandler
*/
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
打开post方法:
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
还是熟悉的配方,还是熟悉的味道。。封装成消息,然后发送出去。好,我们再回头看看最初第一篇文章里的四种方法:
1.new 一个handler来 sendMessage();
2.利用new handler来post
不过是把上边已经定义好Activity的mHandler重新定义了一遍,然后封装成消息发送出去;
3.runOnUiThread
同样不过是用了Activity的mHandler发送消息;
4.view.post
稍微看一下代码:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Assume that post will succeed later
ViewRootImpl.getRunQueue().post(action);
return true;
}
对于AttachInfo应该不算陌生吧,附加view到Activity的时候会把activity的一些属性附加给AttachInfo,这里同样调用取得mHandler然后再post。。绕了一圈又回来了。
综上看来,整个UI更新机制其实就是Android消息系统模型的一个简单实现。至此,我们的更新UI部分也算讲完了,那么作为补充部分,还是从源码上完整细致的过一下整个消息系统模型吧。
这里通过剖析源码来理解各部分的具体实现,再结合前面讲的内容加以融会贯通,便于深入理解最终达到在不同场景的熟练使用。
我们将按照顺序来逐个查看。
首先说说消息对象,毕竟其他类操作的最基本元素也都是它。
public final class Message implements Parcelable {
//继承Parcelable 用于数据传递
/**几种数据类型**/
public int arg1;
public int arg2;
public Object obj;
Bundle data;
public int what;//供handler处理的消息识别标识身份
long when;//什么时候处理该消息
Handler target;//处理该消息的目标handler
Runnable callback; //回调方法
int flags;//标签标识
static final int FLAG_IN_USE = 1 << 0;//是否可用(回收利用)
static final int FLAG_ASYNCHRONOUS = 1 << 1;
static final int FLAGS_TO_CLEAR_ON_COPY_FROM = FLAG_IN_USE;
public Messenger replyTo;//可选对象,可以用来记录发送方或接收者
Message next;//这条消息的下一条消息
/**
*开一个消息池,便于循环利用消息,避免生成新对象并分配内存
*/
private static final Object sPoolSync = new Object();
private static Message sPool;
private static int sPoolSize = 0;
private static final int MAX_POOL_SIZE = 50;
}
既然提到消息池又在前面讲了利用obtain()节省内存资源,那么我们就看下这个obtain()
/**
*从消息池中返回一个新的消息实例,避免我们通常情况下分配新对象。
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
sPoolSize--;
return m;
}
}
return new Message();
}
然后就是基于此方法的一系列运用:先调用obtain()方法,然后把获取的Message实例的 各种参数赋值传参。
//取一个消息对象,把已存在的消息内容赋值过去
public static Message obtain(Message orig) {
Message m = obtain();
m.what = orig.what;
m.arg1 = orig.arg1;
m.arg2 = orig.arg2;
m.obj = orig.obj;
m.replyTo = orig.replyTo;
if (orig.data != null) {
m.data = new Bundle(orig.data);
}
m.target = orig.target;
m.callback = orig.callback;
return m;
}
public static Message obtain(Handler h, int what,
int arg1, int arg2, Object obj) {
Message m = obtain();
m.target = h;
m.what = what;
m.arg1 = arg1;
m.arg2 = arg2;
m.obj = obj;
return m;
}
//调用obtain并赋值,不再一一列出
public static Message obtain(Handler h) {//..}
public static Message obtain(Handler h, Runnable callback) {//..}
public static Message obtain(Handler h, int what) {//...}
public static Message obtain(Handler h, int what, Object obj) {//...}
public static Message obtain(Handler h, int what, int arg1, int arg2) {//...}
然后就是回收释放recycle,它返回一个消息池实例。释放之后将不能再使用此方法。
public void recycle() {
clearForRecycle();
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
//清空所有数据
void clearForRecycle() {
flags = 0;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
when = 0;
target = null;
callback = null;
data = null;
}
//拷贝消息内容
public void copyFrom(Message o) {
this.flags = o.flags & ~FLAGS_TO_CLEAR_ON_COPY_FROM;
this.what = o.what;
this.arg1 = o.arg1;
this.arg2 = o.arg2;
this.obj = o.obj;
this.replyTo = o.replyTo;
if (o.data != null) {
this.data = (Bundle) o.data.clone();
} else {
this.data = null;
}
}
后边就是get和set方法以及Parcelable 的读写。
先看他所定义的属性:
public class Looper {
private static final String TAG = "Looper";
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper;//唯一对应一个主线程的looper静态实例
final MessageQueue mQueue;//消息队列
final Thread mThread; //当前绑定线程
volatile boolean mRun; //是否允许退出
private Printer mLogging;//日志打印
//....各种方法体....
}
通常操作系统都为线程提供了内部存储空间,一个线程对应一个内存空间,因此这里很方便的为一个线程定义唯一对应的looper实例:ThreadLocal< Looper > 这个有点类似C中申请内存大小 *malloc(sizeof Looper),或者我们可以简单理解为只作用于当前线程的new Looper.
而sMainLooper是当前应用程序的主线程looper,区别是适用于主线程。
我们再看他的构造方法:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
此构造方法是私有的,即不允许在外部实例化,这样通过单例模式保证外部从该线程取得looper唯一。另外它主要初始化了mQueue 、mRun 和 mThread 几个属性,并绑定了当前线程。找一下它调用实例化的部分:
//重载,主要看下边的prepare(boolean quitAllowed)方法
public static void prepare() {
prepare(true);
}
/**
*初始化当前线程作为Looper并存为本地变量,
*并由此来创建handler和处理程序
*
*@quitAllowed 是否允许退出(循环取消息)
*通过调用loop()和quit()来开启和结束循环
*/
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { //保证一个线程唯一对应一个Looper
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//在线程中初始化一个实例
}
sThreadLocal的get和set,负责在内存中存取与线程唯一对应的looper。
同时我们会注意到有两个方法prepareMainLooper和getMainLooper:
/**
*初始化当前线程作为Looper并作为android应用的取消息逻辑,
*是由当前运行环境来创建,不需要手动调用
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) { //加锁,保证实例化唯一一个looper
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
/**
* 返回当前应用程序主线程的looper实例
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
这部分是共应用程序初始化的时候调用的,我们一般用不到,不过也可以看出只是初始化了主线程的looper。
好的,基本的初始化工作也已经完成了,来看该类中的核心部分,循环取消息的loop()
/**
* 启动队列的循环取消息操作,直到调用quit()退出
*/
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(); //取下一条消息
if (msg == null) {
// 如果消息队列没有消息则挂起
return;
}
// 打印日志部分
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
//调用消息处理逻辑(回调和执行handler处理)
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();
}
}
基本都有备注,不用过多解释了。msg.target.dispatchMessage前面已经讲过,后便可能会在拉出来遛遛。
其他再就是基本的get和打印和异常捕获相关的了,有兴趣的可以自己去看一下。
原文地址:http://blog.csdn.net/qiaoidea/article/details/45135731