在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,什么狗屎,刷这么久都没反应 ...
随机推荐
- volatile小记
1.要使volatile变量提供理想的线程安全,必须同时满足以下两个条件: 1).对变量的写操作不依赖于当前值: 2).该变量没有包含在具有其他变量的不变式中. 第一个条件的限制使volatile变量 ...
- skip list
概述 Skip list是平衡树的一种替代的数据结构,但是和红黑树不相同的是,跳表对于树的平衡的实现是基于一种随机化的算法的,这样也就是说跳表的插入和删除的工作是比较简单的.并且是Redis.Leve ...
- mq_unlink
NAME mq_unlink - 销毁一个消息队列 (REALTIME) SYNOPSIS #include <mqueue.h> int mq_unlink(const char *na ...
- 8 Types Of Friends You Need To Have in Your Life
8 Types Of Friends You Need To Have in Your Life一生中应该有的8种类型的朋友Did you know that people without frien ...
- 两个C++对象是否相等,要程序员自己下定义,通常是覆盖==操作符
我曾经好多年对Java的==和equals的区别和联系搞不清楚,后来搞清楚了,笔记在这里: http://www.cnblogs.com/findumars/p/3240761.htmlhttp:// ...
- Android笔记——四大组件详解与总结
android四大组件分别为activity.service.content provider.broadcast receiver. ------------------------------- ...
- Linux服务器沦陷为肉鸡的全过程实录
1 从防火墙瘫痪说起 2015年3月10日,还没到公司就被电话告知办公室无法正常连接互联网了,网速非常慢,无法正常浏览网页.急急忙忙感到公司,开始查找问题. 首先排除了交换机故障,因为内部局域网正常. ...
- CentOS开机自动运行程序的脚本
有些时候我们需要在服务器里设置一个脚本,让他一开机就自己启动.方法如下: cd /etc/init.dvi youshell.sh #将youshell.sh修改为你自己的脚本名编写自己的脚本后保 ...
- Data Flow ->> CDC Control Task, CDC Source, CDC Splitter
CDC Control Task可以从控制CDC数据同步,比如初始化加载.LSN范围的管理.它可以代替另一种做法,就是通过调用一批CDC函数来完成同样的事情.从SSIS的角度来完成,事情编程简单,和另 ...
- Linux上的运行的jar包
以调用json-simple为例 java程序(CsvTest.java) import org.json.simple.JSONObject; import java.util.*; public ...