/*
* 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());
…
}
…
}
管理多线程
=========
如果你只运行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 mDecodeWorkQueue;
…
// Instantiates the queue of Runnables as a LinkedBlockingQueue
mDecodeWorkQueue = new LinkedBlockingQueue();
…
}
…
}
创建一个线程池
调用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
在线程池的一个线程上运行代码
==================
你添加了一个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线程的交互
===========
在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线程
最后
对于很多初中级Android工程师而言,想要提升技能,往往是自己摸索成长。而不成体系的学习效果低效漫长且无助。时间久了,付出巨大的时间成本和努力,没有看到应有的效果,会气馁是再正常不过的。
所以学习一定要找到最适合自己的方式,有一个思路方法,不然不止浪费时间,更可能把未来发展都一起耽误了。
如果你是卡在缺少学习资源的瓶颈上,那么刚刚好我能帮到你。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》,点击传送门,即可获取!