前置说明
Android 的UI操作并非线程安全的,所以Android制定了一条规则:只能在主线程中更新UI.
当然这条规则存在一些例外:
http://www.zhihu.com/question/24764972
关于这个例外,大致就是如果在onCreate期间在非主线程更新了UI,基于一些时序的情况,有可能是不报错更新UI成功的;另外progressBar等一些UI组件的方法加了synchronized方法,是可以在非主线程中更新的;而Toast的显示虽然也是操作了textView等UI组件,但是它的机制使用了binder跟系统进程交互,机制完全不同,不展开讨论.
Handler Looper
- Handler 它的作用主要就是收发消息,它把消息发给发送给Looper管理的MessageQueue,并负责处理Looper发给它的消息.所以如果想使用Handler,所在线程必须有Looper.
- Looper Looper负责管理MessageQueue,会循环的从MessageQueue中取出消息,并将消息分发给对应的Handler.
主UI线程中已经初始化好了Looper对象,因此可以直接使用Handler.
而对于非主线程,需要自己创建Looper对象,如下:
if (Looper.myLooper() == null) {
Looper.prepare();
}
调用prepare方法即可,但是一个线程只能有一个Looper,如果重复调用prepare会报错:
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之前加Looper.myLooper() == null判断,防止FC出现.
prepare方法调用后,调用Looper的loop方法,会循环取出消息并分发:
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();
}
关于Looper和Handler小结一下:
首先Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
Looper.loop()会让当前线程进入一个无限循环,不断从MessageQueue的实例中读取消息,然后回调msg.target.dispatchMessage(msg)方法。
Handler的构造方法,会首先得到当前线程中保存的Looper实例,进而与Looper实例中的MessageQueue想关联。
Handler的sendMessage方法,会给msg的target赋值为handler自身,然后加入MessageQueue中。
在构造Handler实例时,我们会重写handleMessage方法,也就是msg.target.dispatchMessage(msg)最终调用的方法。
Handler的leak问题
Handler会经常遇到一个leak的警告:This Handler class should be static or leaks might occur
最简单的处理方式是:将Handler声明为static并持有其外部类的WeakReference(弱引用).
代码如下:
static class MHandler extends Handler {
WeakReference<OuterClass> outerClass;
MHandler(OuterClass activity) {
outerClass = new WeakReference<OuterClass>(activity);
}
@Override
public void handleMessage(android.os.Message msg) {
OuterClass theClass = outerClass.get();
switch (msg.what) {
case 0: {
//使用theClass访问外部类成员和方法
break;
}
default: {
Log.w(TAG, "未知的Handler Message:" + msg.what);
}
}
}
}
AsyncTask
- AsyncTask是另外一种后台执行程序回到前台更新UI的实现方法,AsyncTask是一个抽象类,我们可以自己继承实现,根据需要,复写如下方法即可:
onPreExecute 在执行前调用
doInBackground 后台执行操作
onProgressUpdate更新进度,在doInBackground 中调用publishProgress方法即会触发
onPostExecute 后台操作执行完成后调用
注意:上面四个方法,只有doInBackground 是在非UI线程执行的,其余均在主线程中执行,所以耗时操作都需要放在doInBackground 中;AsyncTask的实例必须在UI线程创建,execute方法必须在主线程中调用.