不知不觉在Android这个行业行走了有三年时间了。过程坎坷而充沛,想想一路走来自己的经验丰富了,但这些随着时间的流失,我所记忆的知识点有时在慢慢的淡忘,所以今年起开通了优快云博客,记录自己行走的点点滴滴...
线程在Android中是一个很重要的概念,从用处来说线程可以分为主线程和子线程。主线程处理和界面有关的事情,子线程则用于执行耗时操作。我们都知道Android的特性,如果在主线程中执行耗时操作就会导致ANR,因此耗时操作必须放在子线程中去执行。除了Thread本身以外,扮演线程角色的还有很多,比如AsyncTask和IntentService,同时HandlerThread 也是一种特殊的线程,但是它们的本质仍然是传统的线程。对于AsyncTask来说,它的底层用到了线程池,对于IntentService和HandlerThread来说,它们的底层则直接使用了线程。
以上说的这些虽然都是线程,但是仍然具有不同的特征和使用场景。AsyncTask封装了线程和Handler,它主要是为了在子线程中更新UI。HandlerThread是一种具有消息循环的线程,在它的内部可以使用Handler。IntentService是一个服务,系统对其进行了封装使其更方便的执行后台任务,IntentService内部采用HandlerThread来执行任务,当任务执行完毕后IntentService会自动退出。IntentService的优点:它是一种服务,不容易被系统杀死从而保证任务的执行。
在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限制的系统资源,线程不可能一直产生,线程的创建和销毁都有一定的开销。如何做到高效呢,--采用线程池。一个线程池中会缓存一定数量的线程,通过线程池就可以避免因为频繁创建和销毁线程所带来的系统开销。Android线程池来源于Java,主要通过Executor来派生特定类型的线程池,不同种类的线程池又具有各自的特性。
一:Android中的线程形态
1:AsyncTask
AsyncTask是一种轻量级的异步任务类,他可以在线程池中执行后台任务,然后把执行的进度和最终结果传递给主线程并在主线程中更新UI。从实现上来说,AsyncTask封装了Thread和Handler,通过AysncTask可以更加方便执行后台任务以及在主线程中访问UI,但是AsyncTask并不合适进行特别耗时的后台任务,对于特别耗时的任务来说,还是建议使用线程池。关于AsyncTask的使用我在之前的博客中有提到,感兴趣的可以看下:Android之AsyncTask学习
2:HandlerThread
HandlerThread 继承自Thread,内部封装了Looper。它的实现就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环。
首先Handler和HandlerThread的主要区别是:Handler与Activity在同一个线程中,HandlerThread与Activity不在同一个线程,而是别外新的线程中(Handler中不能做耗时的操作)。
更具体点就是说普通的Handler主要用于在run方法中执行一个耗时任务,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式来通知HandlerThread执行一个具体的任务。特别注意的是:HandlerThread的run方法是一个无限循环,因此当不需要使用的时候一定要通过quit或者quitSafely方法来终止线程的执行。否则长期运行容易产生内存泄漏!
3:IntentService
IntentService是一种特殊的Service,他继承了Service并且它是一个抽象类,因此必须创建它的子类才能使用。IntentService可用于执行后台耗时的任务,当任务执行后它会自动停止,同时它是服务所以它的优先级比单纯的线程高很多,这样来说,IntentService比较适合执行一些优先级高的后台任务,因为它的优先级高不容易被杀死。
IntentService:异步处理服务,新开一个线程:handlerThread在线程中发消息,然后接受处理完成后,会清理线程,并且关掉服务。
IntentService有以下特点:
(1)它创建了一个独立的工作线程来处理所有的通过onStartCommand()传递给服务的intents。
(2)创建了一个工作队列,来逐个发送intent给onHandleIntent()。
(3)需要主动调用stopSelft()来结束服务。因为,在所有的intent被处理完后,系统会自动关闭服务。
(4)默认实现的onBind()返回null
(5)默认实现的onStartCommand()的目的是将intent插入到工作队列中
继承IntentService的类至少要实现两个函数:构造函数和onHandleIntent()函数。要覆盖IntentService的其它函数时,注意要通过super调用父类的对应的函数。
当IntentService第一次启动以后,onCreate方法会创建一个HandlerThread,然后利用它Loop来创建一个mServiceHandler,这样通过mServiceHandler发送的消息都会交给HandlerThread来处理。每次启动IntentService都会调用它的startCommand方法,startCommand调用onStart。
<span style="font-size:14px;"> @Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
} </span>
可以看到IntentService通过mServiceHandler发送了一条消息,这个消息会被HandlerThread处理,mServiceHandler收到消息后会将intent传给onHandleIntent方法处理。注意这个intent对象和外界startService传递的intent是一样的,通过这个intent可以解析出外界传递给IntetnService的参数,通过这些参数就可以区分具体的后台任务,onHandleIntent就可以进行针对性的处理。执行完毕后,IntentService会通过stopSelf(startId)来尝试停止服务,如果此时还有别的消息未处理,stopSelf(startId)会等待全部处理完毕再关闭。(通过判断最近启动服务次数和startId是否相等)
<span style="font-size:14px;">private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
} </span>
IntentService中的onHandleIntent方法需要我们继承之后自己实现,它可以区分intent中的参数来执行具体任务。每启动一次IntentService就等同于内部向HandlerThread发送一次请求,而Handler中的Looper是顺序执行任务的,所以intentService也是顺序执行后台任务的。
二:Android中的线程池
先说一下线程池的优点:
(1) 复用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销。
(2) 能有效控制线程池的最大并发数,避免大量的线程之间因相互抢占系统资源而导致的阻塞现象。
(3) 能够对线程进行简单的管理,并提供定时执行以及指定间隔循环执行等功能。
在Android中当同时并发多个网络线程时,引入线程池技术会极大地提高APP的性能。Android中的线程池的概念来源Java中的Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor。ThreadPoolExecutor提供了一系列参数来配置线程池,通过不同的参数可以创建不同的线程池。
详细的介绍可以参考后面转载的文章:Android 线程池的使用
线程池例子:
1)JDK自身带有线程池的实现类ThreadPoolExecutor
2)下面是一个模拟ThreadPoolExecutor的例子,以加深对原理的理解
public final class ThreadPool {
// 线程池中默认线程的个数为5
private static int worker_num = 5;
// 工作线程
private WorkThread[] workThreads;
// 任务队列,作为一个缓冲,List线程不安全
private List<Runnable> taskQueue = new LinkedList<Runnable>();
private static ThreadPool threadPool;
// 创建具有默认线程个数的线程池
private ThreadPool() {
this(5);
}
// 创建线程池,worker_num为线程池中工作线程的个数
private ThreadPool(int worker_num) {
ThreadPool.worker_num = worker_num;
workThreads = new WorkThread[worker_num];
for (int i = 0; i < worker_num; i++) {
workThreads[i] = new WorkThread();
workThreads[i].start();// 开启线程池中的线程
}
}
// 单态模式,获得一个默认线程个数的线程池
public static ThreadPool getThreadPool() {
return getThreadPool(ThreadPool.worker_num);
}
// 单态模式,获得一个指定线程个数的线程池,worker_num(>0)为线程池中工作线程的个数
// worker_num<=0创建默认的工作线程个数
public static ThreadPool getThreadPool(int worker_num1) {
if (threadPool == null)
threadPool = new ThreadPool(worker_num1);
return threadPool;
}
// 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定
public void addTask(Runnable task) {
synchronized (taskQueue) {
taskQueue.add(task);
taskQueue. notifyAll();
}
}
// 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁
public void destroy() {
while (!taskQueue.isEmpty()) {// 如果还有任务没执行完成,就先睡会吧
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 工作线程停止工作,且置为null
for (int i = 0; i < worker_num; i++) {
workThreads[i].stopWorker();
workThreads[i] = null;
}
threadPool=null;
taskQueue.clear();// 清空任务队列
}
/**
* 内部类,工作线程
*/
private class WorkThread extends Thread {
// 该工作线程是否有效,用于结束该工作线程
private boolean isRunning = true;
/*
* 关键所在啊,如果任务队列不空,则取出任务执行,若任务队列空,则等待
*/
@Override
public void run() {
Runnable r = null;
while (isRunning) {// 注意,若线程无效则自然结束run方法,该线程就没用了
synchronized (taskQueue) {
while (isRunning && taskQueue.isEmpty()) {// 队列为空
try {
taskQueue.wait(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!taskQueue.isEmpty())
r = taskQueue.remove(0);// 取出任务
}
if (r != null) {
r.run();// 执行任务
}
r = null;
}
}
// 停止工作,让该线程自然执行完run方法,自然结束
public void stopWorker() {
isRunning = false;
}
}
}
------------------ 参考文献:《Android开发艺术探索》