Android实现异步的几种方式——从简单的图片加载说起

异步,在安卓开发中简直是再熟悉不过了。

说到异步,脑海中立马浮现的就是多线程开发,Thread、Handler啥的一一涌上心头…

我们知道在Android开发中不能在非UI线程中更新UI,但是,有的时候我们需要在代码中执行一些诸如访问网络、查询数据库等耗时操作,为了不阻塞UI线程,我们时常会开启一个新的线程(工作线程)来执行这些耗时操作,然后我们可能需要将查询到的数据渲染到UI组件上,那么这个时候我们就需要考虑异步更新UI的问题了。

今天我们从一个简单的业务需求,给大家介绍几种实现异步的方式,最后两个简直爽到不行。

业务是这样的:需要根据文件地址,加载本地图片,最后在ImageView上显示。当然了,从文件中加载图片,是一个耗时操作,必须在子线程中执行,ImageView显示图片呢,又属于UI操作,需要回到主线程。接下来列举几种实现方式:

Thread+Handler

使用Thread+Handler是最传统的实现异步方式了,看下代码:

        new Thread(new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap = getBitmapFromFile(PATH);
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        imageView.setImageBitmap(bitmap);
                    }
                });
            }
        }).start();

如果熟悉Lambda表达式的话,也可以这样写:

        new Thread(() -> {
            Bitmap bitmap = getBitmapFromFile(PATH);
            handler.post(() -> imageView.setImageBitmap(bitmap));
        }).start();

这样看来代码干净了许多。

除了实现Runnable,还可以继承Thread,实现run方法来做到开启子线程。但由于Java的单继承多实现,所以还是使用实现Runnable方式更实用一些。handler的post方法可以将消息发送回主线程,以实现线程间切换。

这种方式在需要的地方new一个对象,使得代码繁乱,不易管理,对系统资源也不便管理。

AsyncTask

AsyncTask提供了方便的接口实现工作线程和主线程的通信。先贴代码:

    class BitmapAsyncTask extends AsyncTask<String, Integer, Bitmap> {
        @Override
        protected void onProgressUpdate(Integer... values) {
            super.onProgressUpdate(values);
        }

        @Override
        protected Bitmap doInBackground(String... strings) {
            // 在doInBackground方法中执行耗时操作
            Bitmap bitmap = getBitmapFromFile(strings[0]);
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            // 在onPostExecute方法中进行ui操作
            imageView.setImageBitmap(bitmap);
        }
    }
new BitmapAsyncTask().execute(PATH);

AsyncTask就是一个封装过的后台任务类,顾名思义就是异步任务。AsyncTask定义了三种泛型类型 Params,Progress和Result。

doInBackground(Params…) 后台执行,比较耗时的操作都可以放在这里。注意这里不能直接操作UI。此方法在后台线程执行,完成任务的主要工作,通常需要较长的时间。在执行过程中可以调用publicProgress(Progress…)来更新任务的进度。

onPostExecute(Result) 相当于Handler 处理UI的方式,在这里面可以使用在doInBackground 得到的结果处理操作UI。 此方法在主线程执行,任务执行的结果作为此方法的参数返回。

这种方式使用了线程池+Handler实现,较好得管理分配资源,还可以拿到进度回调,有较高的拓展性。但需要创建新类,代码也会随之增加,对于简单的异步操作,这种方式有些繁琐。

RxJava

主要还是用到了RxJava的Scheduler(调度器)来实现线程切换,看下代码:

        Observable observable = Observable.create(new Observable.OnSubscribe<Bitmap>() {
            @Override
            public void call(Subscriber<? super Bitmap> subscriber) {
                Bitmap bitmap = getBitmapFromFile(PATH);
                subscriber.onNext(bitmap);
            }
        });
        observable.subscribeOn(Schedulers.io()) // 指定 subscribe() 发生在 IO 线程
                .observeOn(AndroidSchedulers.mainThread()) // 指定 Subscriber 的回调发生在主线程
                .subscribe(new Subscriber<Bitmap>() {
                    @Override
                    public void onCompleted() {

                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onNext(Bitmap bitmap) {
                        imageView.setImageBitmap(bitmap);
                    }
                });

使用Observable.create创建Observable,在call方法中进行耗时操作,执行完成后发送消息,在观察者中的onNext中处理。

使用subscribeOn和observeOn进行线程切换。

使用RxJava的好处是很轻松得实现线程切换,还可以指定线程,有异常捕获机制。但对于不熟悉RxJava的朋友来说会有些…

Kotlin协程

最后要安利一个非常酷炫的方式,那就是Kotlin协程。

越来越多的公司和项目开始使用Kotlin编码,毕竟Kotlin得到了谷歌爸爸的支持,而且Kotlin的优秀语言特性,使得它受到开发者的广泛欢迎。

今天介绍Kotlin的一个概念,叫做协程。协程是由程序直接实现的,是一种轻量级线程,kotlin也为此提供了标准库和额外的实验库。标准库为kotlin.coroutines.experimental(写作时使用kotlin-1.20版本),可见仍然还是一个实验性功能。

看下代码

先定义一个后台CoroutineContext,协程上下文,很容易理解,就是执行环境。

val Background = newFixedThreadPoolContext(2, "bg")
mWriteJob = launch(Background) {
            var bitmap = getBitmapFromFile(PATH);
            launch(UI){
                imageView.setImageBitmap(bitmap)
            }
        }

最后会返回一个Job对象,可以调用方法将其任务停止:

        if (mWriteJob != null && mWriteJob!!.isActive) {
            mWriteJob!!.cancel()
        }

不由得想感叹一下,使用协程做轻量的异步操作,简直爽到不行。

但毕竟协程可能还是了解不多,不免会有一些坑的出现,但多去了解和使用,想必也是很酷的。

小结

从个人感觉来说,我比较推荐使用RxJava和协程来实现,处理周密的话,轻松避免资源浪费和内存泄漏。

Android中的异步操作,实现方式有好多种,各有利弊,就需要我们针对具体业务需求来选择合适的方式,使得功能完成的前提下,优化性能,优化代码。

原文发布于微信公众号 - Android机动车(JsAndroidClub)

原文发表时间:2018-09-27

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏三流程序员的挣扎

Android 优化——内存优化

在 GC 的过程中,其它在工作的线程会暂停,包括负责绘制的 UI 线程,并且在不同区域的内存释放速度也有一定的差异,但不管在哪个区域,都要到这次 GC 内存回收...

30310
来自专栏技术博文

excel导入与导出

基本上导出的文件分为两种: 1:类Excel格式,这个其实不是传统意义上的Excel文件,只是因为Excel的兼容能力强,能够正确打开而已。修改这种文件后再保存...

31360
来自专栏项勇

笔记37 | Android App优化之ANR详解

25360
来自专栏我就是马云飞

RxJava2 实战知识梳理(3) - 优化搜索联想功能

应用场景 几乎每个应用程序都提供了搜索功能,某些应用还提供了搜索联想。对于一个搜索联想功能,最基本的实现流程为:客户端通过EditText的addTextCha...

28170
来自专栏Kubernetes

原 荐 深度解析Kubernetes Pod

Author: xidianwangtao@gmail.com PDB的应用场景 大概在Kubernetes 1.4新增了PodDisruptionBudge...

1.3K130
来自专栏小樱的经验随笔

【批处理学习笔记】第十四课:常用DOS命令(4)

系统管理 at 安排在特定日期和时间运行命令和程序 shutdown立即或定时关机或重启 taskkill结束进程(WinXPHome版中无该命令) taskl...

26630
来自专栏肖蕾的博客

关于AndroidStudio混淆打包 proguard-rules.pro 的配置关于AndroidStudio混淆打包 proguard-rules.pro 的配置

18220
来自专栏项勇

笔记16 | 解析和练习AsyncTask

17860
来自专栏向治洪

带三方登录(qq,微信,微博)

实现QQ、微信、新浪微博和百度第三方登录(Android Studio) 前言: 对于大多数的APP都有第三方登录这个功能,自己也做过几次,最近又有一个...

1.3K50
来自专栏向治洪

android 线程那点事

在操作系统中,线程是操作系统调度的最小单元,同时线程又是一种受限的系统资源,即线程不可能无限制的产生,并且线程的创建和销毁都会有相应的开销,当系统中存在大量的线...

26750

扫码关注云+社区

领取腾讯云代金券