Android 开发中利用异步来优化运行速度和性能

简介: 本文讲的是Android 开发中利用异步来优化运行速度和性能,我们知道,在Android框架中提供了很多异步处理的工具类。然而,他们中大部分实现是通过提供单一的后台线程来处理任务队列的。如果我们需要更多的后台线程的时候该怎么办呢?
本文讲的是Android 开发中利用异步来优化运行速度和性能,

我们知道,在Android框架中提供了很多异步处理的工具类。然而,他们中大部分实现是通过提供单一的后台线程来处理任务队列的。如果我们需要更多的后台线程的时候该怎么办呢?

大家都知道Android的UI更新是在UI线程中进行的(也称之为主线程)。所以如果我们在UI线程中编写耗时任务都可能会阻塞UI线程更新UI。为了避免这种情况我们可以使用 AsyncTask, IntentService和Threads。在之前我写的一篇文章介绍了Android 中异步处理的8种方法。但是,Android提供的AsyncTasksIntentService都是利用单一的后台线程来处理异步任务的。那么,开发人员如何创建多个后台线程呢?

更新: Marco Kotz 指出结合使用ThreadPool Executor和AsyncTask,后台可以有多个线程(默认为5个)同时处理AsyncTask。

创建多线程常用的方法

在大多数使用场景下,我们没有必要产生多个后台线程,简单的创建AsyncTasks或者使用基于任务队列的IntentService就可以很好的满足我们对异步处理的需求。然而当我们真的需要多个后台线程的时候,我们常常会使用下面的代码简单的创建多个线程。

    String[] urls =for (final String url : urls) {
        new Thread(new Runnable() {
            public void run() {
                // 调用API、下载数据或图片
            }
        }).start();
    }

该方法有几个问题。一方面,操作系统限制了同一域下连接数(限制为4)。这意味着,你的代码并没有真的按照你的意愿执行。新建的线程如果超过数量限制则需要等待旧线程执行完毕。 另外,每一个线程都被创建来执行一个任务,然后销毁。这些线程也没有被重用。

常用方法存在的问题

举个例子,如果你想开发一个连拍应用能在1秒钟连拍10张图片(或者更多)。应用该具备如下的子任务:

  • 在一秒的时间内扑捉10张以byte[]形式储存的照片,并且不能够阻塞UI线程。
  • 将byte[]储存的数据格式从YUV转换成RGB。
  • 使用转换后的数据创建Bitmap。
  • 变换Bitmap的方向。
  • 生成缩略图大小的Bitmap。
  • 将全尺寸的Bitmap以Jpeg压缩文件的格式写入磁盘中。
  • 使用上传队列将图片保存到服务器中。

很明显,如果你将太多的子任务放在UI线程中,你的应用在性能上的表现将不会太好。在这种情况下,唯一的解决方案就是先将相机预览的数据缓存起来,当UI线程闲置的时候再来利用缓存的数据执行剩下的任务。

另外一个可选的解决方案是创建一个长时间在后台运行的HandlerThread,它能够接受相机预览的数据,并处理完剩下的全部任务。当然这种做法的性能会好些,但是如果用户想再连拍的话,将会面临较大的延迟,因为他需要等待HandlerThread处理完前一次连拍。

    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) {
                        // 业务逻辑
                    }
                    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来处理我们的图像,然后创建第三个HandlerThread来将照片写入磁盘,最后再创建第四个HandlerThread来将照片上传到服务器中。我们能够加快拍照的速度,但是,这些线程相互之间还是遵循顺序执行的规则,并不是真的并发。因为每张照片是顺序处理的,而且处理每一张照片需要一定的时间,导致用户在点击拍照按钮到显示全部缩略图的时候仍然能够明显的感觉到延迟。

使用ThreadPool并发处理任务

我们可以根据需求创建多个线程,但是创建过多的线程会消耗CPU周期影响性能,并且线程的创建和销毁也需要时间成本。所以我们不想创建多余的线程,但是又想能够充分的利用设备的硬件资源。这个时候我们可以使用ThreadPool。

通过创建ThreadPool对象的单例来在你的应用中使用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() {
                        // 做你想做的任何事情
                    }
                });
            }
            return true;
        }
    });

优化已经完成!通过下面的视频,我们观察到加载缩略图的速度提升是非常明显的。

这种做法的优点是我们可以定义线程池的大小并且指定空余线程保持活动的时间。我们也可以创建多个ThreadPools来处理多个任务或者使用单个ThreadPool来处理多个任务。但是在使用完后记得清理资源。

我们甚至可以为每一个功能创建一个独立的ThreadPool。譬如说在这个例子中我们可以创建三个ThreadPool,第一个ThreadPool负责数据转换成Bitmap,第二个ThreadPool负责写数据到磁盘中去,第三个ThreadPool上传Bitmap到服务器中去。这样做的话,如果我们的ThreadPool最大拥有4条线程,那么我们就能够同时的转换,写入,上传四张相片。用户将看到4张缩略图是同时显示而不是一个个的显示出来的。

上面这个简单例子代码可以在我的GitHub上得到,欢迎看完代码后给我反馈

另外,你也可以在Google Play上面下载演示应用。

使用ThreadPool前: 如果可以,从顶部观察计数器的变化来得知当底部缩略图从开始显示到全部显示完成所耗费的时间。在程序中除了adapter中的notifyDataSetChanged()方法外,我已经将大部分的操作从主线程中剥离,所以计数器的运行是很流畅的。

<iframe frameborder="0" allowfullscreen="1" title="YouTube video player" width="640" height="360" src=" https://www.youtube.com/embed/YmU8ogom_5g?wmode=opaque&widget_referrer=https%3A%2F%2Fmedium.com%2Fmedia%2F6a9266d6d49e3e234f9d60f5763602df%3FmaxWidth%3D640&enablejsapi=1&origin=https%3A%2F%2Fcdn.embedly.com"></iframe >

使用ThreadPool后: 通过顶部的计数器,我们发现使用了ThreadPool后,照片的缩略图加载速度明显变快。

<iframe frameborder="0" allowfullscreen="1" title="YouTube video player" width="640" height="360" src=" https://www.youtube.com/embed/77Lh9XpXArw?wmode=opaque&widget_referrer=https%3A%2F%2Fmedium.com%2Fmedia%2F53c35a233037c20ad1c4f2cba7528580%3FmaxWidth%3D640&enablejsapi=1&origin=https%3A%2F%2Fcdn.embedly.com"></iframe >




原文发布时间为:2016年04月20日

本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。
目录
相关文章
|
4天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
24 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
22天前
|
移动开发 Java Android开发
构建高效Android应用:探究Kotlin与Java的性能差异
【4月更文挑战第3天】在移动开发领域,性能优化一直是开发者关注的焦点。随着Kotlin的兴起,其在Android开发中的地位逐渐上升,但关于其与Java在性能方面的对比,尚无明确共识。本文通过深入分析并结合实际测试数据,探讨了Kotlin与Java在Android平台上的性能表现,揭示了在不同场景下两者的差异及其对应用性能的潜在影响,为开发者在选择编程语言时提供参考依据。
|
26天前
|
缓存 监控 Java
构建高效Android应用:从优化用户体验到提升性能
在竞争激烈的移动应用市场中,为用户提供流畅和高效的体验是至关重要的。本文深入探讨了如何通过多种技术手段来优化Android应用的性能,包括UI响应性、内存管理和多线程处理。同时,我们还将讨论如何利用最新的Android框架和工具来诊断和解决性能瓶颈。通过实例分析和最佳实践,读者将能够理解并实施必要的优化策略,以确保他们的应用在保持响应迅速的同时,还能够有效地利用系统资源。
|
24天前
|
Java Android开发 开发者
构建高效Android应用:Kotlin协程的实践与优化
在响应式编程范式日益盛行的今天,Kotlin协程作为一种轻量级的线程管理解决方案,为Android开发带来了性能和效率的双重提升。本文旨在探讨Kotlin协程的核心概念、实践方法及其在Android应用中的优化策略,帮助开发者构建更加流畅和高效的应用程序。通过深入分析协程的原理与应用场景,结合实际案例,本文将指导读者如何优雅地解决异步任务处理,避免阻塞UI线程,从而优化用户体验。
|
1天前
|
数据库 Android开发 开发者
安卓应用开发:构建高效用户界面的策略
【4月更文挑战第24天】 在竞争激烈的移动应用市场中,一个流畅且响应迅速的用户界面(UI)是吸引和保留用户的关键。针对安卓平台,开发者面临着多样化的设备和系统版本,这增加了构建高效UI的复杂性。本文将深入分析安卓平台上构建高效用户界面的最佳实践,包括布局优化、资源管理和绘制性能的考量,旨在为开发者提供实用的技术指南,帮助他们创建更流畅的用户体验。
|
1天前
|
移动开发 Java Android开发
构建高效Android应用:采用Kotlin协程优化网络请求
【4月更文挑战第24天】 在移动开发领域,尤其是对于Android平台而言,网络请求是一个不可或缺的功能。然而,随着用户对应用响应速度和稳定性要求的不断提高,传统的异步处理方式如回调地狱和RxJava已逐渐显示出局限性。本文将探讨如何利用Kotlin协程来简化异步代码,提升网络请求的效率和可读性。我们将深入分析协程的原理,并通过一个实际案例展示如何在Android应用中集成和优化网络请求。
|
7天前
|
缓存 移动开发 Android开发
构建高效Android应用:从优化用户体验到提升性能表现
【4月更文挑战第18天】 在移动开发的世界中,打造一个既快速又流畅的Android应用并非易事。本文深入探讨了如何通过一系列创新的技术策略来提升应用性能和用户体验。我们将从用户界面(UI)设计的简约性原则出发,探索响应式布局和Material Design的实践,再深入剖析后台任务处理、内存管理和电池寿命优化的技巧。此外,文中还将讨论最新的Android Jetpack组件如何帮助开发者更高效地构建高质量的应用。此内容不仅适合经验丰富的开发者深化理解,也适合初学者构建起对Android高效开发的基础认识。
5 0
|
13天前
|
存储 数据库 Android开发
构建高效安卓应用:采用Jetpack架构组件优化用户体验
【4月更文挑战第12天】 在当今快速发展的数字时代,Android 应用程序的流畅性与响应速度对用户满意度至关重要。为提高应用性能并降低维护成本,开发者需寻求先进的技术解决方案。本文将探讨如何利用 Android Jetpack 中的架构组件 — 如 LiveData、ViewModel 和 Room — 来构建高质量的安卓应用。通过具体实施案例分析,我们将展示这些组件如何协同工作以实现数据持久化、界面与逻辑分离,以及确保数据的即时更新,从而优化用户体验并提升应用的可维护性和可测试性。
|
18天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
20天前
|
监控 算法 Android开发
安卓应用开发:打造高效启动流程
【4月更文挑战第5天】 在移动应用的世界中,用户的第一印象至关重要。特别是对于安卓应用而言,启动时间是用户体验的关键指标之一。本文将深入探讨如何优化安卓应用的启动流程,从而减少启动时间,提升用户满意度。我们将从分析应用启动流程的各个阶段入手,提出一系列实用的技术策略,包括代码层面的优化、资源加载的管理以及异步初始化等,帮助开发者构建快速响应的安卓应用。