Android最佳实践之性能 - 多线程

在单独线程运行代码

参考地址: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是允许的。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值