1. 程式人生 > >Android最佳實踐之性能 - 多線程

Android最佳實踐之性能 - 多線程

ndt andro 單位 多線程 same Coding amount other err

在單獨線程執行代碼

參考地址:http://developer.android.com/training/multiple-threads/define-runnable.html
Runnable對象,是一個接口,裏面僅僅有一個run方法。它僅僅是表示一段能夠執行的代碼。

說這句話,是說明它並不一定要執行在子線程中。它也能夠執行在UI線程

假設它用來執行一段代碼,通常被稱為一個任務(Task)。
Thread類和 Runnable類。是非常強大的基礎類,它們是強大的Android基礎類HandlerThread

, AsyncTaskIntentService的基礎,也是ThreadPoolExecutor的基礎。

這個類自己主動管理線程和任務隊列。甚至能夠並行多個異步任務。

定義一個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資源,也能夠中斷正在執行的線程。

在池裏一個線程中執行代碼

傳一個RunnableThreadPoolExecutor.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最佳實踐之性能 - 多線程