Android最佳實踐之性能 - 多線程
在單獨線程執行代碼
參考地址:http://developer.android.com/training/multiple-threads/define-runnable.html
Runnable對象,是一個接口,裏面僅僅有一個run方法。它僅僅是表示一段能夠執行的代碼。
說這句話,是說明它並不一定要執行在子線程中。它也能夠執行在UI線程。
假設它用來執行一段代碼,通常被稱為一個任務(Task)。
Thread類和 Runnable類。是非常強大的基礎類,它們是強大的Android基礎類HandlerThread
這個類自己主動管理線程和任務隊列。甚至能夠並行多個異步任務。
定義一個Runnable
public class PhotoDecodeRunnable implements Runnable {
...
@Override
public void run() {
/*
* Code you want to run on the thread goes here
*/
...
}
...
}
實現run()方法
在設計上,Runnable對象一般設計在子線程中執行,比方new Thread(new Runnable{})中。
以下的演示樣例中。一開始調用Process.setThreadPriority()方法,傳入THREAD_PRIORITY_BACKGROUND。這樣能夠降低Runnable對象所在線程和UI線程的資源競爭。
你也應該調用Thread.currentThread()。保存一個引用,指向Runnable所在的線程。
class PhotoDecodeRunnable implements Runnable {
...
/*
* Defines the code to run for this task.
*/
@Override
public void run() {
// Moves the current Thread into the background
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
...
/*
* Stores the current Thread in the PhotoTask instance,
* so that the instance
* can interrupt the Thread.
*/
mPhotoTask.setImageDecodeThread(Thread.currentThread());
...
}
...
}
管理多線程
參考地址:http://developer.android.com/training/multiple-threads/create-threadpool.html
假設你僅僅執行task(Runnable)一次,那麽上一篇的內容足以;假設你要在不同的數據集中反復執行一個task,但一次也僅僅能執行一個task,IntentService滿足你的需求。為了自己主動將資源運用最大化、或同一時候執行多個task,你須要一個多線程管理對象。使用ThreadPoolExecutor,它使用閑置的線程執行隊列中的task,你須要做的事就是向隊列中加入任務。
一個線程池能夠並行執行多個task,所以你要確保你的代碼是線程安全的
定義一個線程池(Thread Pool)對象
對線程池使用靜態變量
在app中,線程池可能須要單例的:
public class PhotoManager {
...
static {
...
// Creates a single static instance of PhotoManager
sInstance = new PhotoManager();
}
...
使用private的構造方法
將構造方法私有化,則不用synchronized塊來閉包構造方法:
public class PhotoManager {
...
/**
* Constructs the work queues and thread pools used to download
* and decode images. Because the constructor is marked private,
* it‘s unavailable to other classes, even in the same package.
*/
private PhotoManager() {
...
}
使用線程池類中的方法來執行task
在線程池類中加入一個task給任務隊列:
public class PhotoManager {
...
// Called by the PhotoView to get a photo
static public PhotoTask startDownload(
PhotoView imageView,
boolean cacheFlag) {
...
// Adds a download task to the thread pool for execution
sInstance.
mDownloadThreadPool.
execute(downloadTask.getHTTPDownloadRunnable());
...
}
在UI線程初始化一個Handler
private PhotoManager() {
...
// Defines a Handler object that‘s attached to the UI thread
mHandler = new Handler(Looper.getMainLooper()) {
/*
* handleMessage() defines the operations to perform when
* the Handler receives a new Message to process.
*/
@Override
public void handleMessage(Message inputMessage) {
...
}
...
}
}
確定線程池參數
初始化一個ThreadPoolExecutor對象,須要以下這些參數:
1、池的初始化size和最大的池size
在線程池中能夠使用的線程的數量主要取決於你的設備可用CPU內核的數量:
public class PhotoManager {
...
/*
* Gets the number of available cores
* (not always the same as the maximum number of cores)
*/
private static int NUMBER_OF_CORES =
Runtime.getRuntime().availableProcessors();
}
這個數字可能不反映設備物理CPU內核的數量。
一些設備依據系統負載已經關閉一個或多個內核的cpu,對於這些設備,availableProcessors()返回的是可用的內核數,這個數字一般小於內核總數。
2、活躍時間和時間單位
活躍時間指一個線程在關閉之前保持空暇的時間。這個時間的單位由TimeUnit中的常量決定。
3、任務隊列
ThreadPoolExecutor持有的任務隊列裏面是Runnable對象。初始化ThreadPoolExecutor時要傳入一個實現了BlockingQueue接口的隊列。為滿足app需求。你能夠選擇已有的實現了這個接口的類,以下是LinkedBlockingQueue的樣例:
public class PhotoManager {
...
private PhotoManager() {
...
// A queue of Runnables
private final BlockingQueue<Runnable> mDecodeWorkQueue;
...
// Instantiates the queue of Runnables as a LinkedBlockingQueue
mDecodeWorkQueue = new LinkedBlockingQueue<Runnable>();
...
}
...
}
創建一個線程池
調用ThreadPoolExecutor的構造方法ThreadPoolExecutor()來創建一個線程池:
private PhotoManager() {
...
// Sets the amount of time an idle thread waits before terminating
private static final int KEEP_ALIVE_TIME = 1;
// Sets the Time Unit to seconds
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
// Creates a thread pool manager
mDecodeThreadPool = new ThreadPoolExecutor(
NUMBER_OF_CORES, // Initial pool size
NUMBER_OF_CORES, // Max pool size
KEEP_ALIVE_TIME,
KEEP_ALIVE_TIME_UNIT,
mDecodeWorkQueue);
}
完整代碼下載ThreadSample.zip
在線程池的一個線程上執行代碼
參考地址:http://developer.android.com/training/multiple-threads/run-code.html#StopThread
你加入了一個task到任務隊列中,當線程池中有線程空暇時,則會執行隊列中的task。為節省CPU資源,也能夠中斷正在執行的線程。
在池裏一個線程中執行代碼
傳一個Runnable到ThreadPoolExecutor.execute()方法中,可開始執行一個任務。這種方法將task加入到這個線程池的工作隊列中,當有空暇的線程時,就會取出隊列中的任務進行執行:
public class PhotoManager {
public void handleState(PhotoTask photoTask, int state) {
switch (state) {
// The task finished downloading the image
case DOWNLOAD_COMPLETE:
// Decodes the image
mDecodeThreadPool.execute(
photoTask.getPhotoDecodeRunnable());
...
}
...
}
...
}
中斷(Interrupt)執行代碼
要結束一個task。你須要中斷這個task所在的線程。為了能這樣做。在創建task的時候你要保存task所在線程的句柄:
class PhotoDecodeRunnable implements Runnable {
// Defines the code to run for this task
public void run() {
/*
* Stores the current Thread in the
* object that contains PhotoDecodeRunnable
*/
mPhotoTask.setImageDecodeThread(Thread.currentThread());
...
}
...
}
調用Thread.interrupt()來中斷一個線程。註意,Thread對象是由系統控制的。能夠在app進程之外來改動它們。因此,你須要在中斷它之前鎖住線程中的訪問,在訪問的地方加synchronized代碼塊。比如:
public class PhotoManager {
public static void cancelAll() {
/*
* Creates an array of Runnables that‘s the same size as the
* thread pool work queue
*/
Runnable[] runnableArray = new Runnable[mDecodeWorkQueue.size()];
// Populates the array with the Runnables in the queue
mDecodeWorkQueue.toArray(runnableArray);
// Stores the array length in order to iterate over the array
int len = runnableArray.length;
/*
* Iterates over the array of Runnables and interrupts each one‘s Thread.
*/
synchronized (sInstance) {
// Iterates over the array of tasks
for (int runnableIndex = 0; runnableIndex < len; runnableIndex++) {
// Gets the current thread
Thread thread = runnableArray[taskArrayIndex].mThread;
// if the Thread exists, post an interrupt to it
if (null != thread) {
thread.interrupt();
}
}
}
}
...
}
大多數情況下,Thread.interrupt()會直接停止線程。然而,它僅僅會停止waiting的線程。而不會中斷正使用CPU和網絡任務的線程。為了防止拖慢或鎖定系統,你應該在執行某個操作前推斷是否中斷了:
/*
* Before continuing, checks to see that the Thread hasn‘t
* been interrupted
*/
if (Thread.interrupted()) {
return;
}
...
// Decodes a byte array into a Bitmap (CPU-intensive)
BitmapFactory.decodeByteArray(
imageBuffer, 0, imageBuffer.length, bitmapOptions);
...
完整代碼下載ThreadSample.zip
UI線程的交互
參考地址:http://developer.android.com/training/multiple-threads/communicate-ui.html
在Android中一般使用Handler,在子線程中將結果發送到UI線程,然後在UI線程操作UI。
在UI線程上定義一個Handler
Handler是Android Framework中管理線程的一部分。一個Handler對象接收消息然後執行一些代碼處理這些消息。
一般,在一個新線程中創建一個Handler,你也能夠在一個已有的線程中創建Handler。當在UI線程創建Handler。那麽 它處理的代碼也執行在UI線程。
Looper類也是Android系統管理線程的一部分。在Handler的構造方法中傳入這個Looper對象,這個Handler將執行在Looper所在的線程。
private PhotoManager() {
...
// Defines a Handler object that‘s attached to the UI thread
mHandler = new Handler(Looper.getMainLooper()) {
...
覆寫handleMessage()方法,來處理handler從一個線程中發送的消息。
/*
* handleMessage() defines the operations to perform when
* the Handler receives a new Message to process.
*/
@Override
public void handleMessage(Message inputMessage) {
// Gets the image task from the incoming Message object.
PhotoTask photoTask = (PhotoTask) inputMessage.obj;
...
}
...
}
}
The next section shows how to tell the Handler to move data.
將數據從Task移動到UI線程
為了將數據從執行在後臺線程的task中移動到UI線程,一開始我們要在task類中存數據和UI對象的引用。然後。將task對象和狀態碼傳給被Handler實例化的對象中。然後發送一個包括task和狀態碼的Message給handler。
由於Handler執行在UI線程。所以它能夠將數據送到UI對象中。
// A class that decodes photo files into Bitmaps
class PhotoDecodeRunnable implements Runnable {
...
PhotoDecodeRunnable(PhotoTask downloadTask) {
mPhotoTask = downloadTask;
}
...
// Gets the downloaded byte array
byte[] imageBuffer = mPhotoTask.getByteBuffer();
...
// Runs the code for this task
public void run() {
...
// Tries to decode the image buffer
returnBitmap = BitmapFactory.decodeByteArray(
imageBuffer,
0,
imageBuffer.length,
bitmapOptions
);
...
// Sets the ImageView Bitmap
mPhotoTask.setImage(returnBitmap);
// Reports a status of "completed"
mPhotoTask.handleDecodeState(DECODE_STATE_COMPLETED);
...
}
...
}
...
PhotoTask中也有ImageView和Bitmap的句柄。
雖然它們在同一個對象中,也不能將Bitmap給到ImageView顯示,由於它們不在UI線程。
public class PhotoTask {
...
// Gets a handle to the object that creates the thread pools
sPhotoManager = PhotoManager.getInstance();
...
public void handleDecodeState(int state) {
int outState;
// Converts the decode state to the overall state.
switch(state) {
case PhotoDecodeRunnable.DECODE_STATE_COMPLETED:
outState = PhotoManager.TASK_COMPLETE;
break;
...
}
...
// Calls the generalized state method
handleState(outState);
}
...
// Passes the state to PhotoManager
void handleState(int state) {
/*
* Passes a handle to this task and the
* current state to the class that created
* the thread pools
*/
sPhotoManager.handleState(this, state);
}
...
}
PhotoManager接收一個狀態碼和一個PhotoTask對象的句柄,由於狀態是TASK_COMPLETE,創建一個包括狀態和Task對象的Message然後發給Handler:
public class PhotoManager {
...
// Handle status messages from tasks
public void handleState(PhotoTask photoTask, int state) {
switch (state) {
...
// The task finished downloading and decoding the image
case TASK_COMPLETE:
/*
* Creates a message for the Handler
* with the state and the task object
*/
Message completeMessage =
mHandler.obtainMessage(state, photoTask);
completeMessage.sendToTarget();
break;
...
}
...
}
最後,在Handler.handleMessage()中檢測Message中的狀態。假設狀態是TASK_COMPLETE,則表示task已完畢。PhotoTask中的ImageView須要顯示當中的Bitmap對象。由於Handler.handleMessage()執行在UI線程,如今ImageView顯示bitmap是同意的。
Android最佳實踐之性能 - 多線程