在Android中使用并发来提高速度和性能
Android框架提供了很实用的异步处理类。然而它们中的大多数在一个单一的后台线程中排队。当你需要多个线程时你是怎么做的?
众所周知,UI更新发生在UI线程(也称为主线程)。在主线程中的任何操作都会阻塞UI更新,因此当需要大量计算时可以使用AsyncTask, IntentService 和 Threads。事实上,在不久前我写了在android中异步处理的8种方式。然而,Android中的AsyncTasks运行在一个单一后台线程并且IntentService也同样如此。因此,开发者应该怎么做?
更新:Marco Kotz 指出,你可以通过ThreadPool Executor和AsyncTask使用多个后台线程。
大多数开发者所做的
在大多数情况下,你不需要多个线程,简单地分离AsyncTasks 或者在IntentService 中排队操作就足够用了。然而,当你真正需要多个线程时,通常,我看到的开发者只是简单地平分旧的线程。
String[] urls = …
for (final String url : urls) {
new Thread(new Runnable() {
public void run() {
//Make API call or, download data or download image
}
}).start();
}
使用这种方法有几个问题。第一个是操作系统限制了相同的域名的连接数量为4(我相信)。意思是这段代码不会按照你想的那样去执行。它创建的线程在开始执行操作之前不得不等待另一个线程执行完毕。还有就是每个线程被创建,用来执行一个任务,然后被销毁。这个没有被重用。
这为什么是一个问题?
让我们说一个例子,你想开发一个急速连拍应用,从Camera 预览每秒捕获10张照片或更多。应用的功能如下:
- 用byte[]存储10张照片,且不能阻塞UI。
- 转换每个byte[]的格式从YUV 到RGB 。
- 使用转换后的数组创建一个Bitmap 。
- 修复Bitmap 的方向。
- 生成一个缩略图大小的Bitmap 。
- 把完整大小的Bitmap压缩成Jpeg写到磁盘上。
- 排队把完整图片上传到服务器。
可以理解的是,如果你在主UI线程做所有的操作,你的应用性能将会很低下。唯一的方法是当UI空闲时缓存camera预览数据并处理它。
另一种可能是创建一个一直运行的HandlerThread,可以用来在后台线程接收camera预览并做这些所有的处理。虽然这样会更好,但在随后的急速连拍之间将会有太多的延迟,因为所有的操作都需要处理。
public class CameraHandlerThread extends HandlerThread
implements Camera.PictureCallback, Camera.PreviewCallback {
private static String TAG = "CameraHandlerThread";
private static final int WHAT_PROCESS_IMAGE = 0;
Handler mHandler = null;
WeakReference<CameraPreviewFragment> ref = null;
private PictureUploadHandlerThread mPictureUploadThread;
private boolean mBurst = false;
private int mCounter = 1;
CameraHandlerThread(CameraPreviewFragment cameraPreview) {
super(TAG);
start();
mHandler = new Handler(getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == WHAT_PROCESS_IMAGE) {
//Do everything
}
return true;
}
});
ref = new WeakReference<>(cameraPreview);
}
...
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
if (mBurst) {
CameraPreviewFragment f = ref.get();
if (f != null) {
mHandler.obtainMessage(WHAT_PROCESS_IMAGE, data)
.sendToTarget();
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (f.isAdded()) {
f.readyForPicture();
}
}
if (mCounter++ == 10) {
mBurst = false;
mCounter = 1;
}
}
}
}
注意:如果你想了解更多HandlerThreads知识和怎样使用它,可以阅读我发表的关于HandlerThreads文章。
因为一切都是在一个后台线程完成的,我们的主要性能优势是我们的线程是长时间运行并且没有被销毁和重新创建。然而,许多耗时的操作只能通过线性方式在共享的线程中执行。
我们可以创建第二个HandlerThread 处理图片和第三个将它们写到磁盘和第四个上传到服务器。我们可以快速捕获图片,然而,这些线程仍然将以线性的方式依赖其它的线程。这不是真正的并发。我们可以快速捕获图片,然而,因为处理每个图片需要时间,当用户点击按钮和缩略图被显示之间用户仍能感受到很大的滞后。
使用线程池提高性能
虽然我们可以根据需要创建很多线程,但创建线程和销毁它是一个时间成本。我们也不想创建不需要的线程并且想要充分利用可用的硬件。太多的线程会通过消耗CPU周期影响性能。解决方案是使用一个线程池(ThreadPool)。
在应用中创建一个直接使用的线程池,首先为你的线程池创建一个单例。
public class BitmapThreadPool {
private static BitmapThreadPool mInstance;
private ThreadPoolExecutor mThreadPoolExec;
private static int MAX_POOL_SIZE;
private static final int KEEP_ALIVE = 10;
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
public static synchronized void post(Runnable runnable) {
if (mInstance == null) {
mInstance = new BitmapThreadPool();
}
mInstance.mThreadPoolExec.execute(runnable);
}
private BitmapThreadPool() {
int coreNum = Runtime.getRuntime().availableProcessors();
MAX_POOL_SIZE = coreNum * 2;
mThreadPoolExec = new ThreadPoolExecutor(
coreNum,
MAX_POOL_SIZE,
KEEP_ALIVE,
TimeUnit.SECONDS,
workQueue);
}
public static void finish() {
mInstance.mThreadPoolExec.shutdown();
}
}
然后在上面的代码简单地修改Handler 的回调:
mHandler = new Handler(getLooper(), new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == WHAT_PROCESS_IMAGE) {
BitmapThreadPool.post(new Runnable() {
@Override
public void run() {
//do everything
}
});
}
return true;
}
});
这样就OK了。性能明显提升,可以看看下面的视频!
这里的优势在于我们可以定义池大小,甚至在回收之前指定线程保持多长时间。我们也可以针对不同的操作创建不同的线程池或只使用一个线程池。需要小心的是当你的线程执行完成后做适当的清理。
我们甚至可以针对不同的操作创建不同的线程池,一个线程池转换数据到Bitmaps,一个线程池写数据到磁盘,第三个线程池上传Bitmaps 到服务器。在这一过程中,如果我们的线程池最大可以有4个线程,我们可以在同一时间转换,写和上传4张图片而不是1张。用户可以在同一时间看到4张图片而不是一张。
上面是一个简化的例子,可以从GitHub上查看完整的代码然后给我一些反馈。
你也可以从Google Play下载demo应用。
实现线程池前:如果可以,当缩略图显示在底部时盯着屏幕顶部的定时器。因为我已经把除了 adapter的notifyDataSetChanged()之外的所有操作放到了主线程之外,计数器应该会运行得很流畅。
实现线程池后:屏幕顶部的定时器依然运行得很流畅,然而,图片缩略图的显示快了很多。
在Android中使用并发来提高速度和性能的更多相关文章
- SignalR 中使用 MessagePack 序列化提高 WebSocket 通信性能
It's like JSON.but fast and small. MessagePack is an efficient binary serialization format. It lets ...
- 用 Function.apply() 的参数数组化来提高 JavaScript程序性能
我们再来聊聊Function.apply() 在提升程序性能方面的技巧. 我们先从 Math.max() 函数说起, Math.max后面可以接任意个参数,最后返回所有参数中的最大值. 比如 aler ...
- 利用curl并发来提高页面访问速度
在我们平时的程序中难免出现同时访问几个接口的情况,平时我们用curl进行访问的时候,一般都是单个.顺序访问,假如有3个接口,每个接口耗时500毫 秒那么我们三个接口就要花费1500毫秒了,这个问题太头 ...
- Android中Sqlite数据库多线程并发问题
最近在做一个Android项目, 为了改善用户体验,把原先必须让用户“等待”的过程改成在新线程中异步执行.但是这样做遇到了多个线程同时需要写Sqlite数据库,导致操作数据库失败. 本人对Java并不 ...
- 并发编程之Android中AsyncTask使用详解(四)
更多Android高级架构进阶视频免费学习请点击:[https://space.bilibili.com/474380680] 在Android中我们可以通过Thread+Handler实现多线程通信 ...
- Android中数据库Sqlite的性能优化
1.索引简单的说,索引就像书本的目录,目录可以快速找到所在页数,数据库中索引可以帮助快速找到数据,而不用全表扫描,合适的索引可以大大提高数据库查询的效率.(1). 优点大大加快了数据库检索的速度,包括 ...
- Android中如何查看内存
文章参照自:http://stackoverflow.com/questions/2298208/how-to-discover-memory-usage-of-my-application-in-a ...
- Android中关于Handler的若干思考
在之前的博文中,讲过一些和Handler有关的知识,例如: Android 多线程----AsyncTask异步任务详解 Android多线程----异步消息处理机制之Handler详解 今天再把Ha ...
- 那些Android中的性能优化
性能优化是一个大的范畴,如果有人问你在Android中如何做性能优化的,也许都不知道从哪开始说起. 首先要明白的是,为什么我们的App需要优化,最显而易见的时刻:用户say,什么狗屎,刷这么久都没反应 ...
随机推荐
- JS之DOM(二)
一.DOM节点的操作 1.增加: (1). document.creatElement('标签名');创建元素节点 (2). document.creatTextNode('文本内容'):创建文本节点 ...
- 收缩SQL数据库日志文件
收缩SQL数据库日志文件 介绍具体的操作方法前,先说下我操作的实际环境和当时的状况.我的服务器是windows server 2008 R2 64位英文版,数据库是SQL server 2008英文版 ...
- http://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html
http://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html
- MongoDB (三) MongoDB 安装
MongoDB安装在Windows上 在 Windows上,首先要安装 MongoDB下载最新发布的MongoDB: http://www.mongodb.org/downloads 确保得到正确的版 ...
- 模拟登陆115网盘(MFC版)
[cpp] view plain copy // 模拟登陆115网盘 #include <afxinet.h> // 包含相关的头文件 /* 用抓包工具抓包可得到需要提交的数据,然后模拟提 ...
- PO/POJO/VO/BO/DAO/DTO
PO(persistant object) 持久对象在o/r 映射的时候出现的概念,如果没有o/r映射,就没有这个概念存在了.通常对应数据模型(数据库),本身还有部分业务逻辑的处理.可以看成是与数据库 ...
- 了解python
Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.Python的文本文件是.py文件 Python的用途: 1.做日常事务,比如自动备份你的MP3 2.可以做网站,很多著名的网站包括 ...
- python -- 一致性Hash
python有一个python模块--hash_ring,即python中的一致性hash,使用起来也挺简单. 可以参考下官方例子:https://pypi.python.org/pypi/hash_ ...
- Uubuntu 14.04 LTS反编译apk
使用apktool反编译apk 1.安装apktool apktool是Google提供的APK编译工具,能够反编译及回编译apk,需要Java环境的支持(在此不再赘述Java的安装与配置,详见< ...
- 对Java“一切皆对象”的理念的理解
在从HelloWorld到面向对象中,我们将int, float, double, boolean等称为基本类型(primitive type),也就是特殊的类.我们可以将一个整数理解称为一个int类 ...