概述:
通常, 如果将长时间运行的, 以及数据密集型的操作切割成更小的操作并放在多线程中运行, 都会提高速度和效率. 如果在一个设备上存在多核心的CPU, 那么系统可以并行的运行线程, 而不是让每个子操作一直等待机会才能执行. 栗如, 如果需要解码多个图片文件以便在缩略图中显示, 那么如果把它们放在单独的线程中执行, 将会使得运行速度大幅加快.
指定代码运行在线程中:
本节将介绍如何实现Runnable类, 它将会在Runnable.run()方法中执行代码, 这些代码会运行在一个独立的线程里. 你还可以传递一个Runnable到另一个可以连接到一个线程的对象, 并运行它. 一个或更多的Runnable执行某个特殊的操作, 有些时候被称为任务(Task).
Thread和Runnable都是基础类, 它们单独使用的时候, 只有有限的能力. 但是它们是强力Android类的基础, 比如HandlerThread,AsyncTask和IntentService. Thread和Runnable还是ThreadPoolExecutor类的基础类. 该类会自动管理线程和任务队列, 还可以并行运行多个线程.
定义一个类来实现Runnable:
Runnable的实现是很直截了当的, 比如:
public class PhotoDecodeRunnable implements Runnable {
...
@Override
public void run() {
/*
* Code you want to run on the thread goeshere
*/
...
}
...
}
实现Run方法:
Runnable.run()包含了要执行的代码. 通常, 任何事情都可以在Runnable中运行. 但是还是要记住, Runnable不会运行在UI线程中, 所以它不能直接修改UI对象, 比如View对象.
在run()方法的开头, 通过Process.setThreadPriority()方法设置线程的后台优先级, 参数是THREAD_PRIORITY_BACKGROUND.这种方法可以解决Runnable对象线程和UI线程之间的资源竞争. 你还应该通过Thread.currentThread()方法在Runnable中保存一个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());
...
}
...
}
创建一个多线程管理器:
前面是如何在一个独立的线程中执行代码. 如果只是想要一次性的运行任务, 这可能已经够了. 但是如果你想要重复的运行某个任务, 并指定不同的数据, 但是一次只执行一个任务, 那么IntentService符合你的要求. 要作为资源自动运行任务变得可行, 或者要允许多个任务同时运行, 你需要提供一个线程的集合. 想实现这一点, 就得使用一个ThreadPoolExecutor实例, 当它的(线程)池变得空闲时, 它就会从队列中运行一个任务. 要执行一个任务, 所有你要做的就是将其添加到队列中.
一个线程池可以运行多个并行的任务实例, 所以你应该确保你的代码是线程安全的. 可以被多个线程访问的变量应该加synchronized锁. 这种方法将会组织线程访问一个正在被访问的变量, 但是它也发生在仅一次实例化的任何对象中.
定义线程池类:
在自己的类中实例化ThreadPoolExecutor, 在这个类内部, 做这些事情:
l 为线程池使用静态变量: 为了拥有一个单一的控制点(由于CPU和网络资源的限制), 你可能只是想要一个线程池的单一实例. 如果你拥有了不同的Runnable类型, 你可能会想要为每种类型指定一个线程池, 但是每个都可以是单一的实例. 栗如, 你可以添加这些代码作为你的全局域声明的一部分:
public class PhotoManager {
...
static {
...
// Creates a single static instance of PhotoManager
sInstance = new PhotoManager();
}
...
l 使用一个私有的构造方法: 使用私有的构造方法可以保证它是唯一的(singleton), 这意味着你不必使用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() {
...
}
l 通过线程池类中的方法来启动你的任务: 在线程池类中定义一个方法来用于将任务添加到线程池队列中, 栗如:
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());
...
}
l 在构造方法中实例化一个Handler并关联它到APP的UI线程: 一个Handler可以让你的APP安全的调用UI对象的方法, 比如View对象. 大多数的UI对象可能只在UI线程中修改才安全. 栗如:
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对象, 你需要这些值:
l 初始化线程池的大小和最大容量: 线程池中可以拥有的线程数主要取决于设备可用的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会根据系统负载来关闭一个或者多个核心. 对于这些设备, availableProcessors()会返回可用的核心数, 它可能比总共的核心数要小.
l 保持存活时间和时间单元: 线程在关闭之前会保持空闲一段时间, 这段时间的长度将由时间单元的值决定, 这是一个定义在TimeUnit中的常量.
l 一个任务队列: ThreadPoolExecutor中的输入队列, 需要Runnable对象. 要在一个线程中启动一段代码,线程池管理器会从一个先进先出的队列中取得一个Runnable对象并将其与一个线程关联. 当你创建线程池的时候需要提供这个队列的对象, 使用任何实现了BlockingQueue接口的队列都可以. 要匹配你的APP的需求, 你可以从可用队列实现中选择; 想了解更多关于它们的信息可以参考ThreadPoolExecutor.下面的栗子使用的是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);
}
在线程池的线程中运行代码:
要实现在一个线程池中运行任务, 你需要添加任务到线程池的工作队列中. 当一个线程变得可用, ThreadPoolExecutor会从队列中获取一个任务并在线程中运行它.
在线程池中的线程内运行一个任务:
要启动一个线程池中的任务对象, 需要给ThreadPoolExecutor.execute()方法传递一个Runnable. 该操作会添加一个任务到线程池的工作队列中. 当一个空闲的线程变得可用了, 管理器将会启动那个等待时间最长的任务, 并将其放入线程中执行.
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());
...
}
...
}
...
}
当ThreadPoolExecutor启动一个Runnable的时候, 它会自动调用对象的run()方法.
中断运行的代码:
要停止一个任务, 你需要中断任务线程. 想要实现这个, 你需要在创建任务的时候保存一个handler在任务线程中. 栗如:
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()方法会直接停止线程. 但是, 它只会停止那些在等待的线程, 而不会中断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);
...
与UI线程通信:
这里将介绍如何跟UI进程通信, 它可以让你的任务在结束后台任务之后将结果移交给UI元素, 比如bitmap. 每个APP都有它自己独立的线程来运行UI对象, 比如View对象. 这个线程被称为UI线程. 只有运行在UI线程的对象可以访问该线程的其它对象. 因为在线程池中运行的任务并不运行在UI线程中, 所以它们不能访问UI对象. 要将后台线程中的数据传给UI线程, 需要用到一个在UI线程中的Handler.
在UI线程中定义一个Handler:
Handler是Android系统框架用来管理线程的一部分. 一个Handler对象接收消息并运行代码来处理消息. 通常, 你需要为一个新的线程创建一个Handler, 但是你还可以创建一个Handler来连接到一个已经存在的线程. 当你连接一个Handler到你的UI线程时, 处理消息的代码将会运行在UI线程中.
为一个创建了线程池的类实例化一个Handler对象, 并将对象保存在全局变量中. 通过Handler(Looper)构造方法将该对象与UI线程连接. 这个构造方法使用了一个Looper对象, Looper对象也是Android系统的线程管理框架的一部分. 当你使用一个特定的Looper实例化一个Handler的时候, Handler跟Looper运行在同一个线程中. 栗如:
private PhotoManager() {
...
// Defines a Handler object that's attached to the UI thread
mHandler = new Handler(Looper.getMainLooper()) {
...
在Handler中, 重写handleMessage()方法. 当收到新的消息时, Android系统调用该方法. 所有相同Thread的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;
...
}
...
}
}
从一个任务发送数据到UI线程:
要从一个后台线程的任务对象发送数据给UI线程, 首先要在任务对象中保存数据的引用和UI对象. 下一步, 将任务对象和状态码发送给实例化的Handler对象. 在这个对象中, 发送一个包含状态和任务对象的Message给Handler. 因为Handler运行在UI线程中, 所以消息就可以发送给UI线程了.
在任务对象中保存数据:
栗如, 下面是一个Runnable, 运行在一个后台进程中, 它解码了一个Bitmap并保存它在父对象PhotoTask中. Runnable还保存了状态码DECODE_STATE_COMPLETED.
// 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的引用在同一个对象中, 你还是不能关联Bitmap到ImageView中, 因为现在它们不在UI线程中.
发送状态:
PhotoTask是在层次中更高的下一个对象. 它保存了解码数据和用于显示数据的View对象的引用. 它从PhotoDecodeRunnable中接收状态码并将其传给保存线程池的对象和实例化的Handler:
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);
}
...
}
发送数据给UI:
PhotoManager对象从PhotoTask对象接收状态码和一个PhotoTask的handler. 如果状态码是TASK_COMPLETE,就创建一个包含状态和任务对象的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()检查每个收到的消息的状态码. 如果状态码符合TASK_COMPLETE,那么就表示任务完成, 在消息中的PhotoTask对象包含了Bitmap和ImageView. 因为handleMessage()已经是运行在UI线程中的方法, 所以它可以安全的操作ImageView了:
private PhotoManager() {
...
mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message inputMessage) {
// Gets the task from the incoming Message object.
PhotoTask photoTask = (PhotoTask) inputMessage.obj;
// Gets the ImageView for this task
PhotoView localView = photoTask.getPhotoView();
...
switch (inputMessage.what) {
...
// The decoding is done
case TASK_COMPLETE:
/*
* Moves the Bitmap from the task
* to the View
*/
localView.setImageBitmap(photoTask.getImage());
break;
...
default:
/*
* Pass along other messages from the UI
*/
super.handleMessage(inputMessage);
}
...
}
...
}
...
}
...
}
参考: https://developer.android.com/shareables/training/ThreadSample.zip
https://developer.android.com/training/multiple-threads/index.html