提到Handler大家一定都不陌生,通常我们都是在子线程通过在主线程中创建的Handler对象切换到主线程中去更新View的显示内容,然而很少接触Looper更没有感觉到MessageQueue的存在,然而三者是密切相关的,那么接下来分别介绍他们的作用、使用以及原理来加深对他们的理解,来达到熟练使用Handler的目的。
一、Handler的作用
1、定时处理消息或执行任务
2、在其他线程中处理消息或执行任务
文档描述如下:
There are two main uses for a Handler:
(1) to schedule messages and runnables to be executed as some point in the future; and
(2) to enqueue an action to be performed on a different thread than your own.
可以通过Handler以下的方法发送消息或任务:
post(Runnable)
, postAtTime(Runnable, long)
,postDelayed(Runnable, long)
,sendEmptyMessage(int)
,sendMessage(Message)
,sendMessageAtTime(Message, long)
, 和sendMessageDelayed(Message, long)
;
与sendMessage(Message)类似的方法允许发送一个Messenge对象到消息队列,包含一些数据的Messenge对象将被Handler子类重写后的
handleMessage(Message)方法处理;
与post(Runnable)类似的方法允许发送一个Runnable对象到消息队列,当消息队列接受到时该对象时候将会被调用。
Handler是串行处理任务或者消息的,因为在某个创建了Looper消息循环的线程中,通过Looper维系着一个任务队列MessageQueue并使用Handler向
队列中插入消息或任务,通过死循环来取出队列中的任务再由Handler来执行具体的处理逻辑,即Handler负责消息或任务的发送和处理。这就是Handler、
Looper和MessageQueue的实现逻辑。
二、Handler的使用
Handler的使用步骤:
1、通过Looper.prepare()方法创建消息循环对象。
2、创建Handler对象,并通过重写Handler的handleMessage(Message msg)方法或者在创建Handler对象时传入Callback的实现类来实现消息处理逻辑。
3、通过Looper.loop()启动消息循环。
以上三个步骤必须在同一个线程中完成。
a、下面是一个典型的的创建并初始化Handler过程的例子:
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
//1、通过Looper.prepare()方法创建消息循环对象
Looper.prepare();
//2、创建Handler对象,并通过重Handler的handleMessage(Message msg)方法
//或者在创建Handler对象时传入Callback的实现类来实现消息处理逻辑。
mHandler = new Handler() {
public void handleMessage(Message msg) {
// process incoming messages here
}
};
//3、通过Looper.loop()启动消息循环。
Looper.loop();
}
}
这个典型的Handler的实现例子,利用Looper.prepare()和Looper.loop()创建并初始化一个Handler与Looper交互。它仅仅展示了最基本的创建过程;
b、HandlerThread与Handler结合使用,作用是将任务切换到子线程中执行
在实际应用中可以通过HandlerThread和Handler配合使用来达到在子线程处理消息或任务的效果。HandlerThread继承Thread并且内部已经创建了Looper,HandlerThread的关键代码如下:
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* This method returns the Looper associated with this thread. If this thread not been started
* or for any reason is isAlive() returns false, this method will return null. If this thread
* has been started, this method will block until the looper has been initialized.
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
从代码的实现可以看出,当线程运行后就创建了Looper(在prepare()方法中创建的,后面会说到)并启动循环,通过getLooper()方法获得该线程中的Looper对象,我们可以通过Looper对象来创建Handler对象,最后通过Handler中的方法将任务发送到该线程中执行。给出HandlerThread与Handler结合的使用方法,代码如下:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.util.Log;
public class HandlerActivity extends Activity {
protected static final String TAG = "HandlerActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
testHandler();
}
private void testHandler() {
// 创建HandlerThread 内部已经构建了Looper消息循环
HandlerThread handlerThread = new HandlerThread("HandlerActivity-HandlerThread");
// 启动线程,并启动Looper消息循环
handlerThread.start();
// 用HandlerThread对象中的Looper对象创建并初始化Handler对象
Handler handler = new Handler(handlerThread.getLooper()) {
@Override
public void handleMessage(Message msg) {
Log.d(TAG, "我是一个消息" + msg.what + " 我运行在" + Thread.currentThread().getName() + "线程中");
}
};
// 发送一个消息
handler.sendEmptyMessage(0x123);
// 发送一个任务
handler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "我是一个任务" + " 我运行在" + Thread.currentThread().getName() + "线程中");
}
});
}
}
运行结果:
c、在主线程中使用Handler,主要作用在子线程中将更新View任务切换到主线程中执行
Android是单线程模式,且只允许在UIThread(即主线程)来更新View,所以当在子线程(UIThread之外的线程)中更新View时候会抛出CalledFromWrongThreadException异常。
通过Handler可以将更新View的操作切换到主线程中,这样就可以子线程进行耗时任务然后将结果通过Handler发送到UIThread中更换新View。
有两种方式创建Handler对象:
i、直接在主线程中创建Handler对象即可,此方式比较简洁,简洁单不简单。
ii、在子线程中使用Looper.getMainLooper()得到主线程的消息循环对象,然后通过主线程的消息循环对象创建Handler对象,此方式比较灵活,可以在线程中创建Handler对象。
方式一:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.widget.TextView;
public class HandlerActivity extends Activity {
protected static final String TAG = "HandlerActivity";
//直接在主线程中创建Handler
Handler handler = new Handler();
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv = new TextView(this);
tv.setText("正在执行中...");
setContentView(tv);
testHandler();
}
private void testHandler() {
new Thread() {
@Override
public void run() {
try {
// 模拟耗时任务
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新View
handler.post(new Runnable() {
@Override
public void run() {
tv.setText("模拟耗时任务的结果:123");
}
});
}
}.start();
}
}
以上的例子已经足以在实际开发中使用了,但是我们不仅要知其然,还要知其所以然才能更好的理解运用Handler。下面为Handler的原理探索部分(仅知道如何使用的话可以直接跳到方法二):
没有看见调用Looper的prepare()与loop()方法来创建消息循环与Handler关联,为什么在主线程中创建的Handler直接可以处理任务呢?首先我们来看一下Handler的构造函数,该无参构造最终会调用Handler(Callback callback, boolean async)这个构造函数,源代码如下:
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;
}
从该构造方法中可以看出,如果mLooper为null会抛出RuntimeException让我们先调用Looper.prepare()方法。但是我们的例子能正常运行这说明mLooper不为空,继续看看Looper在哪里创建的,Handler是在Activity中创建的查看下Activity相关源码:
public void recreate() {
if (mParent != null) {
throw new IllegalStateException("Can only be called on top-level activity");
}
if (Looper.myLooper() != mMainThread.getLooper()) {
throw new IllegalStateException("Must be called from main thread");
}
mMainThread.requestRelaunchActivity(mToken, null, null, 0, false, null, false);
}
不用管这个代码是实现什么功能的但是我们可以看出Looper是在mMainThread这个线程中创建的(即ActivityThread主线程)。
方式二:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.widget.TextView;
public class HandlerActivity extends Activity {
protected static final String TAG = "HandlerActivity";
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
tv = new TextView(this);
tv.setText("正在执行中...");
setContentView(tv);
testHandler();
}
private void testHandler() {
new Thread() {
// 通过Looper.getMainLooper()得到主线程的消息循环对象来创建Handler对象
Handler handler = new Handler(Looper.getMainLooper());
@Override
public void run() {
try {
// 模拟耗时任务
Thread.sleep(3 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 更新View
handler.post(new Runnable() {
@Override
public void run() {
tv.setText("模拟耗时任务的结果:123");
}
});
}
}.start();
}
}
三、Handler、MessengeQueue与Looper的原理
1、ThreadLocal
了解他们三者关系前先了解下ThreaLoacl。当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。一般来说,当某些数据是以线程为作用域并且不同线程具有不同发数据副本的时候,可以考虑使用ThreadLoacl。此处只说用法想了解原来的可以查看源代码,ThreadLoacl的实现逻辑也是比较容易理解的。
ThreadLocal类接口很简单(JDK5.0),只有4个方法,我们先来了解一下:
void set(T value)设置当前线程的线程局部变量的值。
public T get()该方法返回当前线程所对应的线程局部变量。
public void remove()将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(T)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
ThreadLocal例子:
public class ThreadLocalTest {
static int index = 1;
static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>();
public static void main(String[] args) {
// 在主线程中设置threadLocal
threadLocal.set(Integer.valueOf(0));
// 在主线程获取threadLocal的值
System.out.println(Thread.currentThread().getName() + "-threadLocal:" + threadLocal.get());
test(Integer.valueOf(1));
test(Integer.valueOf(2));
test(null);
System.out.println(Thread.currentThread().getName() + "-threadLocal:" + threadLocal.get());
}
public static void test(final Integer i) {
new Thread("Thread" + index++) {
@Override
public void run() {
if (i != null) {
// 在子线程中设置threadLocal
threadLocal.set(i);
}
// 在子线程获取threadLocal的值
System.out.println(Thread.currentThread().getName() + "-threadLocal:" + threadLocal.get());
};
}.start();
}
}
运行结果:
从运行结果可以很明显看出,在各个线程中设置的ThreadLocal的值互不影响,ThreadLocal可以简单的理解为内部采用Map来存储数据,并且其key为当前线程对象,在各个线程中设置或获取的值仅仅为当前线程对应的value。Handler、MessengeQueue与Looper就是通过ThreadLocal关联起来的。
2、MessageQueue
3、Looper
4、Handler