Android 的性能 IV-多线程的性能
2016-02-14 18:50
405 查看
概述:
通常, 如果将长时间运行的, 以及数据密集型的操作切割成更小的操作并放在多线程中运行, 都会提高速度和效率. 如果在一个设备上存在多核心的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 137c6 ) { ... // 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
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories