原生机制剖析
Handler机制原理
为什么需要?
解决安卓中的多线程并发问题。
通常UI页面的操作都只能在主线程进行,安卓中将主线程称为UI线程。当我们需要创建子线程去处理耗时任务时,就需要协调UI线程和子线程之间的通信
避免ANR
ANR(Application Not Responding)即应用无响应。避免ANR最核心的一点就是在主线程减少耗时操作。
ANR出现的原因有三种:
- KeyDispatchTimeout(5 seconds)主要类型按键或触摸事件在特定时间内无响应
- BroadcastTimeout(10 seconds)BoradcastReceiver在特定的时间内无法处理
- ServiceTimeout(20 seconds)小概率类型Service在特定的时间内无法处理完成
Handler 机制
Handler机制涉及的三个核心类:Looper、Handler和Message。此外还有一个Message Queue(消息队列),但被封装到Looper里,我们并不会直接与消息队列打交道。
Looper
Looper的字面意思是“循环者”,被设计用来使一个普通线程变成Looper线程。所谓Looper线程就是循环工作的线程。在GUI开发中,通常都需要一个线程不断循环执行任务。有新任务时执行,执行完成则继续等待下一个任务。
public class LooperThread extends Thread {
@Override
public void run() {
// 将当前线程初始化为Looper线程
Looper.prepare();
// ...其他处理
// 开始循环处理消息队列
Looper.loop();
}
}
上面两行核心代码将普通线程升级为Looper线程。来看一下Looper内部源码:
public class Looper {
// 线程本地存储对象
private static final ThreadLocal sThreadLocal = new ThreadLocal();
// Looper内的消息队列
final MessageQueue mQueue;
// 当前线程
Thread mThread;
// ....省略部分源码
// 每个Looper对象中有它的消息队列,和它所属的线程
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
// 调用该方法会在调用线程的中创建Looper对象
public static final void prepare() {
if (sThreadLocal.get() != null) {
// 试图在有Looper的线程中再次创建Looper将抛出异常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
// ......
}

特别注意,一个Thread只能有一个Looper对象
Looper.loop()调用的原理

调用loop方法后,Looper线程就开始真正工作了,它不断从自己的MessageQueue中取出队头的消息(也叫任务)执行。
Looper除了prepare()和loop()方法,还提供了一些有用的方法
Looper.myLooper():获取当前线程的Looper实例对象getThread():获取Looper对象所属的线程quit():结束looper循环
Handler
Handler扮演了往MessageQueue中添加消息和处理消息的角色(只处理由自己发出的消息),即通知MessageQueue它要执行一个任务(sendMessage),并在Looper循环到该任务时,回调自己来执行该任务(handleMessage),这整个过程都是异步的。Handler创建时会关联一个Looper,默认的构造方法将关联当前线程的Looper。
一个线程可以有多个Handler,但是只能有一个Looper:

Handler提供了如下一些方法向MessageQueue中发送消息
post(Runnable)postAtTime(Runnable, long)postDelayed(Runnable, long)sendEmptyMessage(int)sendMessage(Message)sendMessageAtTime(Message, long)sendMessageDelayed(Message, long)
Handler通过核心方法dispatchMessage)(Message msg)与handleMessage)(Message msg)完成消息的处理,任务的执行。相关源码剖析如下:
// 处理消息,该方法由looper调用
public void dispatchMessage(Message msg) {
if (msg.callback != null) { // 如果message设置了callback,,处理callback
handleCallback(msg);
} else {
if (mCallback != null) { // 如果handler本身设置了callback,则执行callback
if (mCallback.handleMessage(msg)) {
return;
}
}
// 如果message没有callback,则调用handler的子类所实现的handleMessage方法
handleMessage(msg);
}
}
// 处理runnable消息
private final void handleCallback(Message message) {
message.callback.run(); //直接调用run方法
}
// 由子类实现该回调方法
public void handleMessage(Message msg) {}
Handler发送消息图示:

Handler处理消息图示:

Message
在Handle消息处理机制中,Message又被称作Task,它封装了任务携带的信息和处理该任务的Handle。
使用Message时需要注意几点:
尽管Message有默认构造方法,但通常都使用
Message.obtain()来从消息对象池中取一个空消息对象,避免每次创建新对象从而节省资源。如果Message只用来携带简单的
int信息,应优先使用Message.arg1和Message.arg2来传递信息使用
Message.what来标识消息的类型,以便处理不同类型消息。
Handler 机制整体图示:

代码实例:
public class SampleTask implements Runnable {
private static final String TAG = "SampleTask";
Handler handler;
public SampleTask(Handler handler) {
super();
this.handler = handler;
}
@Override
public void run() {
try {
Thread.sleep(5000); // 模拟执行耗时任务
// 任务完成后通知UI更新
Message msg = handler.obtainMessage();
msg.obj = "task completed!"
// 发送消息
handler.sendMessage(msg);
} catch (InterruptedException e) {
Log.d(TAG, "interrupted!");
}
}
}
在Activity中使用:
public class TestActivity extends Activity {
private TextView textview;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textview = (TextView) findViewById(R.id.textview);
// 创建并启动线程
Thread workerThread = new Thread(new SampleTask(new MyHandler()));
workerThread.start();
}
class MyHandler extends Handler {
@Override
public void handleMessage(Message msg) {
String result = (String)msg.obj;
// 更新UI
textview.setText(result);
}
}
}
注意:
Handler机制,既可以实现主线程和子线程之间的通信,也可以实现子线程和子线程之间的通信,当我们需要将主线程的任务交给子线程执行时,还可以使用
HandlerThread类来简化代码。HandlerThread继承于Thread,它本质就是个Thread。与普通Thread的差别就在于,其内部封装了Looper。安卓的主线程(UI线程)就是一个
HandlerThread,其内部已经包含了Looper由于UI线程已经包含了
Looper,因此Activity中封装了一个简化方法runOnUiThread(),该方法可在子线程中调用,将一段代码post到UI线程中执行,从而简单的完成在子线程中更新UI。// 简单示例 new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(3000); runOnUiThread(new Runnable() { @Override public void run() { // 此处更新UI text.setText("") } }); } catch (InterruptedException e) { e.printStackTrace(); } } }).start();- 在Activity中使用
Handler,要记得在onDestroy中清理消息队列,避免内存泄漏
- 在Activity中使用
线程池原理
可简单理解为装有线程的池子。我们可以把要执行的异步任务交给线程池来处理,通过维护一定数量的线程池来达到多个线程的复用。
如果不使用线程池,每个线程都要通过new Thread(xxRunnable).start()的方式来创建并运行一个线程,线程少或许不是问题,但真实环境可能会开启多个线程让系统和程序达到最佳效率,当线程数量大多时会耗尽系统的CPU和内存资源,也会造成GC频繁收集和停顿,因为每次创建和销毁一个线程都是要消耗系统资源的,如果为每个异步任务都创建线程这无疑是一个很大的性能瓶颈。所以,线程池中的线程复用极大节省了系统资源,当线程一段时间不再有任务处理时它也会自动销毁,而不会长驻内存。
JDK中的java.util.concurrent包定义了线程池,其中ThreadPoolExecutor是线程池核心类:
public ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
) {
....
}
参数的理解:
corePoolSize:线程池的核心大小,也可以理解为最小的线程池大小,即线程最大并发数maximumPoolSize:最大线程池大小,即队列满后线程能够达到的最大并发数keepAliveTime:空闲线程存活时间,即多久被回收。指的是超过corePoolSize的空闲线程达到多长时间才进行销毁unit:keepAliveTime的时间单位workQueue:存储等待执行线程的任务的队列handler:拒绝策略,当工作队列、线程池全已满时如何拒绝新任务,默认抛出异常

Executors是JDK提供的创建线程池的工厂类,它默认提供了4种线程池:
newSingleThreadExecutor创建一个单线程的线程池,核心线程数和最大线程数均为1。它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO优先级)执行。newFixedThreadPool创建一个固定线程池,核心线程数和最大线程数固定相等,可控制线程最大并发数,超出的线程会在队列中等待。newScheduledThreadPool创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行。newCachedThreadPoo创建一个带缓冲线程池,核心线程数为0,最大线程数为Integer的最大值,一旦有空闲线程就会在60秒后销毁,如果没有可用线程则新建一个线程。 需谨慎使用,易造成内存溢出。
一个示例:
// 创建固定大小的线程池
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
final int ii = i;
fixedThreadPool.execute(new Runnable(){
@Override
public void run() {
System.out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
AsyncTask
它相当于安卓中的线程池,用于更简单的执行异步任务。
首先简单理解一下异步和同步概念
- 同步:必须等前一件事做完才能做下一件事,当前线程是阻塞的。
- 异步:和同步是相对的,当我们执行某个任务后,并不想要立即得到结果,我们可以继续做其他任务,当之前的任务完成后,可以发通知来告知我们结果。实际上生活中处处是异步的例子,例如后台下载,用户不可能一直等待下载完成才去做别的事,而是开启下载就不管了,去处理自己的其他事务。
AsyncTask内部线程池参数的配置:


总结:
AsyncTask实例必须在主线程中创建execute方法必须在主线程中调用- 不要手动调用
AsyncTask中的回调(如:onPreExecute等) - Task只能被执行一次,需要多次执行,则每次都需要创建一个
AsyncTask实例 AsyncTask默认是串行执行,如需要并行执行,应调用executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)替代execute方法AsyncTask会为当前应用创建一个全局线程池,因此在任何地方调用都会使用同一个线程池- 若
AsyncTask被声明为Activity的非静态内部类,会因AsyncTask持有对Activity的引用 而导致Activity无法被及时回收,引起内存泄露问题。因此AsyncTask应被声明为Activity的静态内部类。可参考本人博客中内存泄漏部分
公众号“编程之路从0到1”