android中经常会用到多线程,因为安卓中如果5S中没有响应就会出现ANR,因此耗时的任务必须放在子线程中使用。这就涉及到UI线程(main线程)和子线程的交互。其二者之间的交互涉及到Handler,Looper,message。
每次创建线程都有系统资源的开销,不可能无限的创建线程,线程过多最终会带来系统的反应速度下降,因此就必须控制线程数量,控制线程的最好办法就是设计线程池,让线程可以重复利用。
下面将会通过三种方式来创建安卓的线程池。
1、通过静态列表缓存10个子线程,创建一个主线程,每个线程中创建唯一Handler,放在子线程的代码。
2、自定义线程池中还是设计一个主线程(UI线程),同时缓存10个子线程,当然这里创建的子线程采用android提供的HandlerThread来代替上面自定义的线程。采用HandlerThread会更加简便,后续会通过代码进行说明。
3、通过系统提供的ThreadPoolExecutor来设计线程池。
一、自定义Thread来创建线程池。
public class ThreadExtractors {
public class WorkThead extends Thread {
private boolean mIsQuiting = false;
private boolean mIsLoopEnd = false;
Handler myHandler = null;
private final String mThreadId;
public WorkThead(String threadId){
mThreadId = threadId;
}
@Override
public void run() {
//Looper.prepare() 和 Looper.loop()成对存在,创建Handler必须指定looper否则会抛异常
//需要通过synchronized来保证线程安全
Looper.prepare();
synchronized (this){
myHandler = new Handler();
//myHandler创建成功通知所有等待的线程
notifyAll();
}
Looper.loop();
}
public Handler getHandler(){
synchronized (this) {
while (null == myHandler) {
try {
//如果myHandler没创建成功,就需要等待,直到myHandler被创建成功
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
return myHandler;
}
public void quit(){
//退出时需要清除句柄,避免内存泄露
if (mIsQuiting)
return;
mIsQuiting = true;
if (null != myHandler){
//清除message队列及其回调
myHandler.removeCallbacksAndMessages(null);
myHandler.post(new Runnable() {
@Override
public void run() {
Looper.myLooper().quit();
mIsLoopEnd = true;
}
});
}
}
public boolean isQuiting(){
return mIsQuiting;
}
public boolean isLoopEnd(){
return this.mIsLoopEnd;
}
}
//最大线程数量
private static final int MAX_TREAD_COUNT = 10;
private static final int minThreadCount = 5;
//当前线程数
private static int currentThreadCount = 0;
//保留的线程数
private static int RETENTION_THREAD_COUNT = 10;
private WorkThead[] retentionThreads;
private static ThreadExtractors THREAD_POOL = null;
private static Handler mainHandler = null;
private ThreadExtractors(){
mainHandler = new Handler(Looper.getMainLooper());
//子线程数目为10个
retentionThreads = new WorkThead[RETENTION_THREAD_COUNT];
for(int i = 0; i < RETENTION_THREAD_COUNT; i ++){
retentionThreads[i] = newThread();
}
}
private WorkThead newThread(){
String threadId = UUID.randomUUID().toString();
WorkThead workThead = new WorkThead(threadId);
workThead.start();
return workThead;
}
public void OnDestroy(){
if (null == retentionThreads)
return;
for (int i = 0, length = retentionThreads.length; i < length; i++){
retentionThreads[i].quit();
}
mainHandler.removeCallbacksAndMessages(null);
retentionThreads = null;
THREAD_POOL = null;
}
public static void extractMainThread(Runnable runnable){
THREAD_POOL.extractMainThread(runnable, 0);
}
public synchronized static void extractMainThread(Runnable runnable, int delayTime){
if (null == THREAD_POOL)
THREAD_POOL = new ThreadExtractors();
THREAD_POOL.mainHandler.postDelayed(runnable, delayTime);
}
public static void extractInThread(Runnable runnable){
THREAD_POOL.extractThread(runnable, 0);
}
public synchronized static void extractThread(Runnable runnable, int delayTime){
if (null == THREAD_POOL)
THREAD_POOL = new ThreadExtractors();
currentThreadCount++;
if(currentThreadCount >= MAX_TREAD_COUNT)
currentThreadCount = 0;
THREAD_POOL.retentionThreads[currentThreadCount].getHandler().postDelayed(runnable, delayTime);
}
private HandlerThread newHandlerThread(){
HandlerThread handlerThread = new CusmHandlerThread(UUID.randomUUID().toString());
handlerThread.start();
return handlerThread;
}
/** HandlerThread内部中已经封装好了线程安全,因此不必担心多线程问题
*
*/
public class CusmHandlerThread extends HandlerThread{
private Handler mMyHandler = null;
public CusmHandlerThread(String name) {
super(name);
mMyHandler = new Handler(this.getLooper());
}
public void setHandler(Handler handler){
mMyHandler = handler;
}
public Handler getHandler(){
return this.mMyHandler;
}
}
}
对以上线程池代码进行讲解:
1、从66行 -75行可以看出,本线程池定义了1个主线程,10个子线程。
2、110行 extractMainThread(Runnable runnable, int delayTime)方法中加上同步关键字synchronized,是为了避免多线程安全问题。
3、需要注意子线程类WorkThead的定义,本线程只会创建Handler,创建Handler的时候请注意,每个Handler必须制定唯一的looper,因此如果需要自己创建非主线程的Handler,则必须将其创建放在Looper.prepare() 和 Looper.loop()之间,直到创建完成为止。
4、注意23行的getHandler()加了synchronized,是因为避免获取的时候其还没创建成功。
5、注意37行的quit()函数,退出的时候需要清理资源,避免内存泄露。
有没发现自定义线程WorkThead的实现相对来说比较繁琐,有没有更简便的方法呢?当然是有的,这就需要用到安卓封装的HandlerThread。
1、请查看129行~152行的HandlerThread定义,其功能完全可以代替WorkThead的功能,并且不用理会线程安全及其清理工作, HandlerThread内部会自动的去处理。当然HandlerThread内部也实现了线程安全问题。
下面看看以上设计的线程怎样调用:
ThreadExtractors.extractMainThread(() ->{
System.out.println("这里的代码是在UI线程执行的,本消息会在主线程的Handler的消息队列中排队处理");
});
ThreadExtractors.extractInThread(() -> {
System.out.println("这里的代码是在非主线程中执行的,本消息会在非主线程的Handler的消息队列中排队处理");
});
本代码中采用了JAVA8中的新特性拉姆表达式,不熟悉的谷歌一下。
需要在主线程或者子线程执行,直接套用以上方法即可,当然每个线程post的messge会在当前线程的handler中进行排队,并且是顺序执行的。