一、線程
主線程
- 主線程是指進程中所擁有的線程,在Java中默認情況下一個進程只有一個線程,這個線程就是主線程。
- 主線程主要作用是運行四大組件以及處理它們和用戶的交互,因為用戶隨時會和界面發生交互,所以主線程在任何時候都必須有較高的響應速度,否則會產生一種界面卡頓的感覺,極大的影響用戶體驗。
子線程
- 工作線程,除了主線程以外的線程都是子線程。
- 子線程的作用是執行耗時任務,比如網絡請求、I/O操作等,從Android 3.0開始系統要求網絡訪問必須在子線程中進行,否則網絡訪問將會失敗并拋出NetWorkOnMainThreadException這個異常,這樣做是為了避免主線程由于被耗時操作所阻塞從而出現ANR現象。
ANR異常
-
什么是ANR?
- 應用程序無響應(Application Not Responding)。
- 應用程序的響應性是由Activity Manager和WindowManager系統服務監視的。
-
造成ANR的主要原因:
- 主線程被IO操作(從4.0之后網絡IO不允許在主線程中)阻塞。
- 主線程中存在耗時的計算。
-
Android中哪些操作是在主線程的?
- Activity的所有生命周期回調都是在主線程執行的。
- Service默認是執行在主線程的。
- BroadcastReceiver的onReceive回調是在主線程執行的。
- 沒有使用子線程的looper的Handler的handleMessage,post(Runnable)是在主線程執行的。
- AsyncTask的回調中除了doInBackground,其他都是在主線程執行的。
-
如何解決ANR?
- 使用Asynctask處理耗時IO操作。
- 使用Thread或者HandlerThread提高優先級。
- 使用Handler來處理工作線程的耗時任務。
- Activity的onCreate和onResume回調盡量避免耗時的代碼。
二、Android中的線程形態
Thread
AsyncTask
HandlerThread
- HandlerThread繼承了Thread,它是一種可以使用Handler的Thread,它的實現也很簡單,就是在run方法中通過Looper.prepare()來創建消息隊列,并通過Looper.loop()來開啟消息循環,這樣在實際的使用中就允許在HandlerThread中創建Handler。HandlerThread的run方法如下所示:
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
- 從HandlerThread的實現來看,普通Thread主要用于在run方法中執行一個耗時任務,而HandlerThread在內部創建了消息隊列,外界需要通過Handler的消息方式來通知HanlderThread執行一個具體的任務。
- HandlerThread的run方法是一個無限循環,因此當明確不需要再使用HandlerThread時,可以通過它的quit或者quitSafely方法來終止線程的執行,這是一個良好的編程習慣。
- HandlerThread在Android中的一個具體的使用場景是IntentService。
IntentService
- IntentService是一種特殊的Service,它繼承了Service并且它是一個抽象類,因此必須創建它的子類才能使用IntentService。
- IntentService可用于執行后臺耗時的任務,當任務執行后它會自動停止,同時由于IntentService是服務的原因,這導致它的優先級比單純的線程要高很多,所有IntentService比較適合執行一些高優先級的后臺任務,因為它優先級高不容易被系統殺死。
- IntentService封裝了HandlerThread和Handler,這一點可以從它的onCreate方法中看出來,如下所示:
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
- 當IntentService被第一次啟動時,它的onCreate方法會被調用,onCreate方法會創建一個HandlerThread,然后使用它的Looper來構造一個Handler對象mServiceHandler,這樣通過mServiceHandler發送的消息最終都會在HandlerThread中執行,從這個角度來看,IntentService也可以用于執行后臺任務。每次啟動IntentService,它的onStartCommand方法就會調用一次,IntentService在onStartCommand中處理每個后臺任務的Intent。
@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
- onStartCommand方法處理外界的Intent,是通過調用了onStart方法,具體實現如下所示:
@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
- 可以看出,IntentService僅僅是通過mServiceHandler發送了一個消息,這個消息會在HandlerThread中被處理,mServiceHandler收到消息后,會將Intent對象傳遞給onHandlerIntent方法去處理,注意這個Intent對象的內容和外界的startService(intent)中的intent的內容是完全一致的,通過這個Intent對象即可解析出外界啟動IntentService時所傳遞的參數,通過這些參數就可以區分具體的后臺任務,這樣在onHandlerIntent方法中就可以對不同的后臺任務做處理了。當onHandlerIntent方法執行結束后,IntentService會通過stopSelf(int startId)方法來嘗試停止服務。這里之所以采用stopSelf(int startId)而不是stopSelf()來停止服務,那是因為stopSelf()會立刻停止服務,而這個時候可能還有其他消息未處理,stopSelf(int startId)則會等待所有的消息都處理完畢后才終止服務。一般來說,stopSelf(int startId)在嘗試停止服務之前會判斷最近啟動服務的次數是否和startId相等,如果相等就立刻停止服務,不相等則不停止服務,這個策略可以從AMS的stopServiceToken方法的實現中找到依據,ServiceHandler的實現如下所示:
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);
}
}
IntentService的onHandlerIntent方法是一個抽象方法,它需要我們在子類中實現,它的作用從Intent參數中區分具體的任務并執行這些任務。如果目前只存在一個后臺任務,那么onHandleIntent方法執行完這個任務后,stopSelf(int startId)就會直接停止服務。如果目前存在多個后臺任務,那么當onHandleIntent方法執行完最后一個任務時,stopSelf(int startId)才會直接停止服務。另外,由于每執行一個后臺任務就必須啟動一次IntentService,而IntentService內部則通過消息的方式向HandlerThread請求執行任務,Handler中的Looper是順序處理消息的,這就意味著IntentService也是順序執行后臺任務的,當有多個后臺任務同時存在時,這些后臺任務會按照外界發起的順序排隊執行。
- 下面通過一個示例來進一步說明IntentService的工作方式,首先派生一個IntentService的子類,比如LocalIntentService,它的實現如下所示:
public class LocaIntentService extends IntentService {
private static final String TAG = "LocalIntentService";
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public LocaIntentService(String name) {
super(name);
}
@Override
protected void onHandleIntent(@Nullable Intent intent) {
String action = intent.getStringExtra("task_action");
Log.d(TAG, "receive task:" + action);
SystemClock.sleep(3000);
if ("com.ryq.action.TASK1".equals(action)) {
Log.d(TAG, "handle task:" + action);
}
}
@Override
public void onDestroy() {
Log.d(TAG, "service destroyed.");
super.onDestroy();
}
}
這里對LocalIntentService的實現做一下簡單的說明,在onHandleIntent方法中會從參數中解析出后臺任務的標識,即task_action字段所代表的內容,然后根據不同的任務標識來執行具體的后臺任務,這里為了簡單起見,直接通過SystemClock.sleep(3000)來休眠3000毫秒從而模擬一種耗時的后臺任務,另外為了驗證IntentService的停止時機,這里在onDestroy()中打印了一句日志,LocalIntentService實現完成了以后,就可以在外界請求執行后臺任務,在下面的代碼中先后發起了3個后臺任務的請求:
Intent service = new Intent(this, LocaIntentService.class);
service.putExtra("task_action", "com.ryq.action.TASK1");
startService(service);
service.putExtra("task_action", "com.ryg.action.TASK2");
startService(service);
service.putExtra("task_action", "com.ryg.action.TASK3");
startService(service);
-
運行程序,觀察日志,如下所示。
從上面的日志可以看出,三個后臺任務是排隊執行的,它們的執行順序就是它們發起的請求對的順序,即TASK1、TASK2、TASK3。另外一點就是當TASK3執行完畢后,LocalIntentService才真正地停止,從日志中可以看出LocalIntentService執行了onDestroy(),這也意味著服務正在停止。
三、Android中的線程池
線程池的優點有3點:
- 重用線程池中的線程,避免因為線程的創建和銷毀所帶來的性能開銷。
- 能有效控制線程池的最大并發數,避免大量的線程之間因互相搶占系統資源而導致的阻塞現象。
- 能夠對線程進行簡單的管理,并提供定時執行以及指定間隔循環執行等功能。
ThreadPoolExecutor
- Android中的線程池的概念來源于Java中的Executor,Executor是一個接口,真正的線程池實現為ThreadPoolExecutor。
- ThreadPoolExecutor提供了一系列參數來配置線程池,通過不同的參數可以創建不同的線程池。
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
corePoolSize
- 線程池的核心線程數,默認情況下,核心線程會在線程池中一直存活,即使它們處于閑置狀態。如果將ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true,那么閑置的核心線程在等待新任務到來時會有超時策略,這個時間間隔由keepAliveTime所指定,當等待時間超過keepAliveTime所指定的時長后,核心線程就會被終止。
maximumPoolSize
- 線程池所能容納的最大線程數,當活動線程數達到這個數值后,后續的新任務將會被阻塞。
keepAliveTime
- 非核心線程閑置時的超時時長,超過這個時長,非核心線程就會被回收。當ThreadPoolExecutor的allowCoreThreadTimeOut屬性設置為true時,keepAliveTime同樣會作用于核心線程。
unit
- 用于指定keepAliveTime參數的時間單位,這是一個枚舉,常用的有TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)以及TimeUnit.MINUTES(分鐘)等。
workQueue
- 線程池中的任務隊列,通過線程池的execute方法提交的Runnable對象會存儲在這個參數中。
threadFactory
- 線程工廠,為線程提供創建新線程的功能。ThreadFactory是一個接口,它只有一個方法:Thread newThread(Runnable r)。
RejectedExecutionHandler handler。
- 這是ThreadPoolExecutor的一個不常用的參數,當線程無法執行新任務時,這可能是由于任務隊列已滿或者是無法成功執行任務,這個時候ThreadPoolExecutor會調用handler的rejectedExecution方法來通知調用者,默認情況下rejectedExecutor方法會直接拋出一個RejectedExecutionException。ThreadPoolExecutor為RejectedExecutionHandler提供了幾個可選值:
- CallerRunsPolicy
- AbortPolicy
- 默認值,它會直接拋出RejectedExecutionException。
- DiscardPolicy
- DiscardOldestPolicy
ThreadPoolExecutor執行任務時大致遵循如下規則:
如果線程池中的線程數量未達到核心線程的數量,那么會直接啟動一個核心線程來執行任務。
如果線程池中的線程數量已經達到或者超過核心線程的數量,那么任務會被插入到任務隊列中排隊等待執行。
如果在步驟2中無法將任務插入到任務隊列中,這往往是由于任務隊列已滿,這個時候如果線程數量未達到線程池規定的最大值,那么會立刻啟動一個非核心線程來執行任務。
如果步驟3中的線程數量已經達到線程池規定的最大值,那么就拒絕執行此任務,ThreadPoolExecutor會調用RejectedExecutionHandler的rejectedExecution方法來通知調用者。
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// We want at least 2 threads and at most 4 threads in the core pool,
// preferring to have 1 less than the CPU count to avoid saturating
// the CPU with background work
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
/**
* An {@link Executor} that can be used to execute tasks in parallel.
*/
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
- 從上面的代碼可以知道,AsyncTask對THREAD_POOL_EXECUTOR這個線程池進行了配置,配置后的線程池規格如下:
- 核心線程數等于CPU 核心數+1。
- 線程池的最大線程數為CPU核心數的2倍+1。
- 核心線程無超時機制,非核心線程在閑置時的超時時間為1秒。
- 任務隊列的容量為128。
線程池的分類
FixedThreadPool
- 通過Executors的newFixedThreadPool方法來創建。
- 它是一種線程數量固定的線程池,當線程處于空閑狀態時,它們并不會被回收,除非線程池被關閉了。當所有的線程都處于活動狀態時,新任務都會處于等待狀態,直到有線程空閑出來。
- 它只有核心線程并且這些核心線程不會被回收,能夠更加快速地響應外界的請求。
newFixedThreadPool方法的實現如下,可以發現FixedThreadPool中只有核心線程并且這些核心線程沒有超時機制,任務隊列沒有大小限制。
public static ExecutorService newFixedThreadPool(int nThreads){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
CachedThreadPool
- 通過Executors的newCachedThreadPool方法來創建的。
- 它是一種線程數量不定的線程池。
- 它只有非核心線程,并且最大線程數為Integer.MAX_VALUE。由于Integer.MAX_VALUE是一個很大的數,實際上就相當于最大線程數可以任意大。
- 當線程池中的線程都處于活動狀態時,線程池會創建新的線程來處理新任務,否則就會利用空閑的線程來處理新任務。線程池中的空閑線程都有超時機制,這個超時時長為60秒,超過60秒閑置線程就會被回收。
- 和FixedThreadPool不同的是,CachedThreadPool的任務隊列其實相當于一個空集合,這將導致任何任務都會立即被執行,因為在這種場景下SynchronousQueue是無法插入任務的。
- SynchronousQueue是一個非常特殊的隊列,在很多情況下可以把它簡單理解為一個無法存儲元素的隊列,由于它在實際中較少使用,這里就不深入探討它了。
- 從CachedThreadPool的特性來看,這類線程池比較適合執行大量的耗時較少的任務,當整個線程池都處于閑置狀態時,線程池中的線程都會超時而被停止,這個時候CackedThreadPool之中實際上是沒有任何線程的。它幾乎是不占用任何系統資源。newCachedThreadPool方法的實現如下所示:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ScheduledThreadPool
- 通過Executors的newScheduledThreadPool方法來創建的。
- 它的核心線程數量是固定的,而非核心線程數是沒有限制的,并且當非核心線程閑置時會被立即回收。
- ScheduledThreadPool這類線程池主要用于執行定時任務和具有固定周期的重復任務,newScheduledThreadPool方法的實現如下所示:
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize,Integer.MAX_VALUE,0,NANOSECONDS,new DelayedWorkQueue());
}
SingleThreadExecutor
- 通過Executors的newSingleThreadExecutor方法來創建。
- 這類線程池內部只有一個核心線程,它確保所有的任務都在同一個線程中按順序執行。
- SingleThreadExecutor的意義在于統一所有的外界任務到一個線程中,這使得在這些任務之間不需要處理線程同步的問題,newSingleThreadExecutor方法的實現如下所示:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(
new ThreadPoolExecutor(
1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>())
);
);
}
系統預置的4種線程池的典型使用方法,如下所示:
Runnable command = new Runnable() {
@Override
public void run() {
SystemClock.sleep(2000);
}
};
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(4);
fixedThreadPool.execute(command);
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
cachedThreadPool.execute(command);
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(4);
//2000ms后執行command
scheduledThreadPool.schedule(command, 2000, TimeUnit.MILLISECONDS);
//延遲10ms后,每隔1000ms執行一次command
scheduledThreadPool.scheduleAtFixedRate(command, 10, 1000, TimeUnit.MILLISECONDS);
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
singleThreadExecutor.execute(command);