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中使用并发来提高速度和性能的更多相关文章

  1. SignalR 中使用 MessagePack 序列化提高 WebSocket 通信性能

    It's like JSON.but fast and small. MessagePack is an efficient binary serialization format. It lets ...

  2. 用 Function.apply() 的参数数组化来提高 JavaScript程序性能

    我们再来聊聊Function.apply() 在提升程序性能方面的技巧. 我们先从 Math.max() 函数说起, Math.max后面可以接任意个参数,最后返回所有参数中的最大值. 比如 aler ...

  3. 利用curl并发来提高页面访问速度

    在我们平时的程序中难免出现同时访问几个接口的情况,平时我们用curl进行访问的时候,一般都是单个.顺序访问,假如有3个接口,每个接口耗时500毫 秒那么我们三个接口就要花费1500毫秒了,这个问题太头 ...

  4. Android中Sqlite数据库多线程并发问题

    最近在做一个Android项目, 为了改善用户体验,把原先必须让用户“等待”的过程改成在新线程中异步执行.但是这样做遇到了多个线程同时需要写Sqlite数据库,导致操作数据库失败. 本人对Java并不 ...

  5. 并发编程之Android中AsyncTask使用详解(四)

    更多Android高级架构进阶视频免费学习请点击:[https://space.bilibili.com/474380680] 在Android中我们可以通过Thread+Handler实现多线程通信 ...

  6. Android中数据库Sqlite的性能优化

    1.索引简单的说,索引就像书本的目录,目录可以快速找到所在页数,数据库中索引可以帮助快速找到数据,而不用全表扫描,合适的索引可以大大提高数据库查询的效率.(1). 优点大大加快了数据库检索的速度,包括 ...

  7. Android中如何查看内存

    文章参照自:http://stackoverflow.com/questions/2298208/how-to-discover-memory-usage-of-my-application-in-a ...

  8. Android中关于Handler的若干思考

    在之前的博文中,讲过一些和Handler有关的知识,例如: Android 多线程----AsyncTask异步任务详解 Android多线程----异步消息处理机制之Handler详解 今天再把Ha ...

  9. 那些Android中的性能优化

    性能优化是一个大的范畴,如果有人问你在Android中如何做性能优化的,也许都不知道从哪开始说起. 首先要明白的是,为什么我们的App需要优化,最显而易见的时刻:用户say,什么狗屎,刷这么久都没反应 ...

随机推荐

  1. 自制 JS.format带分页索引

    //第一参数是:Json对象,第二个是   序号  第三个   页数     第四  当前页数String.prototype.format = function (args, sid, pagesi ...

  2. centOS学习part3:远程工具VNC的安装与配置

    0 上一篇(http://www.cnblogs.com/souvenir/p/3875484.html)我们介绍了通过yum安装JDK的实例,初步见识了yum命令的强大.本章我们将继续使用yum命令 ...

  3. Android核心分析之二十一Android应用框架之AndroidApplication

    Android Application Android提供给开发程序员的概念空间中Application只是一个松散的表征概念,没有多少实质上的表征.在Android实际空间中看不到实际意义上的应用程 ...

  4. iOS开发--沙盒路径与操作文件

    获取应用沙盒根路径: -(void)dirHome{ NSString *dirHome=NSHomeDirectory(); NSLog(@"app_home: %@",dirH ...

  5. 替换应用程序exe图标,主要使用BeginUpdateResource,UpdateResource API函数

    替换应用程序exe图标,主要使用的API函数是BeginUpdateResource(),UpdateResource(),EndUpdateResource()来使用自定义的ico文件类替换exe程 ...

  6. Android 时间戳简单转化

    时间戳就是如1377216000000 这种格式我们在mysql数据库中会经常用到把时间转换成时间戳或把时间戳转换成日期格式了,下面我来介绍安卓中时间戳操作转换方法. 一.原理 时间戳的原理是把时间格 ...

  7. git - 简易指南

    http://www.bootcss.com/p/git-guide/ git - 简易指南

  8. Cocos2dx+lua中Color参数的坑

    cocos2x的lua中有如下几种颜色定义 --Color3B function cc.c3b( _r,_g,_b ) return { r = _r, g = _g, b = _b } end -- ...

  9. DeepLearning常用库简要介绍与对比

    网上近日流传一张DL相关库在Github上的受关注度对比(数据应该是2016/03/15左右统计的): 其中tensorflow,caffe,keras和Theano排名比较靠前. 今日组会报告上tj ...

  10. USACO Section 2.1: Hamming Codes

    挺简单的一道题 /* ID: yingzho1 LANG: C++ TASK: hamming */ #include <iostream> #include <fstream> ...