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. html + css + js注释规范

    添加注释到代码中,是一个很好的习惯,而且极大的提高了代码的可读性 1.HTML <!--commentContent--> 2.CSS //commentContent /*comment ...

  2. oracle 密码忘记 找回密码

    生活中,容易忘记Oracle数据库system用户的密码,怎么办呢,小生带你一步步重新登上Oracle ,及时你密码忘记了. 1.打开cmd窗口,输入 sqlplus / as sysdba 2.运行 ...

  3. 545B. Equidistant String

    题目链接 输入两个只含有01的字符串,s,t 求一个字符串p使到s,t的距离一样 这里的距离是指对应位置:0-0的距离是0 ,o-1的距离是1 ,1-1的距离是0,1-0的距离是1 这里只要求找出满足 ...

  4. C++标准文档下载

    C++真正正式公布的标准只有三个:C++98.C++03.C++11. C++98是第一个正式的C++标准, C++03是在C++98上面进行了小幅度的修订, C++11则是一次全面的大进化(之前称C ...

  5. IOS 视频分解图片、图片合成视频

    在IOS视频处理中,视频分解图片和图片合成视频是IOS视频处理中经常遇到的问题,这篇博客就这两个部分对IOS视频图像的相互转换做一下分析. (1)视频分解图片 这里视频分解图片使用的是AVAssetI ...

  6. PHP使用CURL详解

    CURL是一个非常强大的开源库,支持很多协议,包括HTTP.FTP.TELNET等,我们使用它来发送HTTP请求.它给我 们带来的好处是可以通过灵活的选项设置不同的HTTP协议参数,并且支持HTTPS ...

  7. *在Win7中安装JDK1.7并配置环境变量

    安装的过程就不废话了. 下面是环境变量的配置. 1. 配置环境变量 单机右键‘计算机--属性’  2.点击高级系统设置  3.点击‘环境变量’ 4.增加"JAVA_HOME"系统变 ...

  8. GLSL基础

    GLSL基础 OpenGL Shading Language GLSL作为一种着色语言是纯粹的和GPU打交道的计算机语言.因为GPU是多线程并行处理器,所以GLSL直接面向SIMD模型的多线程计算.G ...

  9. mysql 日期

    数据类型 数据类型 格式 date YYYY-MM-DD datetime YYYY-MM-DD HH:MM:SS timestamp YYYY-MM-DD HH:MM:SS year YYYY 或 ...

  10. Java实现图片压缩代码,图片大小转换

    在很多项目中我们会把上传的图片做处理,比较图片上传过多对服务器的容量和带宽有很多的浪费,如果不是必须的高清图片,我们可以通过代码来做压缩.在我的项目中我们压缩图片的目的是让web页面打开的速度很快,并 ...