安卓开发异步框架

异步开发是安卓开发中基本的一个技术问题,本文会对比各种异步库的基本使用方式和实现原理。

其实异步开发主要是面对两个场景: 1.类A在新线程调用了类B的方法,获取方法执行结果之后再执行类A的方法;类B需要主动通知类A执行某个方法。以上两个场景可以分别用下面两个例子来代表: 例1:UI层在子线程获取到数据之后再主线程通知UI更新。 例2:数据层监听到数据库变化,主动通知UI层更新。

本文对比分析四种方式:回调,RxJava, kotlin协程,Eventbus

1. 回调

回调是最容易被想到的一种异步实现方法,当然异步一般都是涉及到多线程, 所以回调一般会结合线程池一起使用。

针对上面的例1,实现方式如下,数据层主要是Model类,先在Model类中生命回调接口,然后把UI层(MainActivity)创建回调接口的实例,传给Model类,Model类获取到数据之后调用UI层接口实例的方法即可。

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onResume() {
    // 创建IO线程池
        val ioExecutorService = Executors.newFixedThreadPool(1)
        //创建回调实例
        val listener = object : Model.ModelListner {
            override fun onDataGot(data: String) {
                runOnUiThread { 
                    // 在主线程更新界面
                }
            }
        }
        ioExecutorService.submit {
            Model().getData(listener)
        }
    }
}

// 数据类
class Model {
    fun getData(modelListner: ModelListner){
        // 获取数据
        val result =  "hello"

        // 执行回调
        modelListner.onDataGot(result)
    }

    interface ModelListner{
        fun onDataGot(data: String);
    }
}

例2的实现也是如此。当然,在kotlin当中可以用高阶函数来实现回调,这样就不需要单独声明接口了。

2. RxJava

RxJava是一个用于异步编程的 Java 库,它基于响应式编程范式。用观察者模式实现了消息的通知。

2.1 用法

有两套模型来实现消息的通知,一是Observable和Observer, 而是Flowable和Consumer. 二者的差异就是后者支持背压。 考虑用Observable来解决例1的问题就是:首先在数据层创建一个Observable实例,UI层创建Observer实例,并监听数据层的Observable, 然后数据层获取到数据后,通过Observable来通知UI层。同样的,例2也是如此实现。

2.2 实现原理

线程切换

在事件的分发流程中,subscribOn和observeOn方法都可以切换线程.调用这些方法之后,后面的逻辑会在新的线程上执行。 l SubscribOn流程 如下图所示:

调用subsribOn 之后,会创建一个ObservableSubscribeOn( Observable的子类), 然后调用subscribe方法会在指定的线程上执行之后的逻辑 2. observeOn流程与subscribOn同理,只不过让observer的onNext方法在指定线程执行

调度策略

默认提供4个调度器: AndroidSchedulers.mainThread: 安卓平台特有,对应主线程。 Schedulers.NEW_THREAD:适合用于需要在独立线程上执行的短期任务,每次调度的时候创建一个ScheduledThreadPoolExecutor, 核心线程数 1, 最大线程数 Interger.max Schedulers.COMPUTATION:适用于计算密集型的任务,这个调度器对应n(cup核心数)个ScheduledThreadPoolExecutor,每个Executor核心线程数为1,有任务的时候轮流安排到Excutor Schedulers.IO: 适用于IO任务,这个调度器只对应一个ScheduledThreadPoolExecutor,核心线程数为1,

3. kotlin 协程

协程概念是跟着go语言火起来的,但是kotlin中的协程有别于go语言中的协程,go语言中的协程可以直接被go虚拟机调度, 而kotlin中的协程概念可以理解为一个异步库,每个协程相当于线程池中的一个任务。 kotlin协程相比于回调的优点是可以用同步的方式写异步代码,很大程度方便了开发 一些重要的概念:

  • CoroutineScope: 协程的作用域,控制协程的生命周期。
  • Job: 协程的句柄,可以用来取消协程。
  • Deferred: 一个带有返回值的 Job。
  • Dispatcher: 协程的调度器,决定协程在哪个线程或线程池中执行。

3.1 用法

启动协程有很多方法, 可以用launch,async, runBlocking, withContext方法来启动。有所区别的是 launch: 是最常用的协程构建器,用于启动一个新的协程。它返回一个 Job对象,可以用于取消协程或检查其状态。 async: 也是一种协程构建器,但它返回一个 Deferred 对象,可以用于获取协程的结果。async 通常用于需要并行执行并返回结果的任务。 withContext: 用于在指定的协程上下文中执行代码块,并返回其结果。它通常用于切换协程上下文,例如从主线程切换到 IO 线程。 runBlocking: 是一种阻塞当前线程的协程构建器,通常用于测试或顶层函数中。它会阻塞当前线程,直到协程内部的所有代码执行完毕。

dart 复制代码
使用实例: lauch和async启动一个协程
实现原理: 创建一个协程; 交给调度器; 调度器会选择一个线程执行
launch {
        println("World!")
    }

val deferred = async {
        "Hello, World!"
    }

来看上面提到的例1,子线程获取到数据之后在主线程通知UI更新。

kotlin 复制代码
class MainActivity : AppCompatActivity() {
    override fun onResume() {
        super.onResume()

        // 使用协程在 IO 线程中获取数据,然后在主线程中更新 UI
        CoroutineScope(Dispatchers.Main).launch {
            // 在 IO 线程中获取数据, 这个协程会调到IO调度器去执行
            val data = withContext(Dispatchers.IO) {
                Model().getData()
            }

            // 在主线程中更新 UI
            updateUI(data)
        }
    }
    

class Model {
    suspend fun getData(): String{
        // 获取数据
        val result =  "hello"

        return result
    }
}
    

例2的实现需要借助协程中的flow概念,flow的概念类似RxJava中的flowable和Observable. 例2的实现方法是先在数据层创建一个flow对象,然后UI层去监听这个对象,在数据层监听到数据库数据变化的之后通过flow通知UI层

3.2 实现原理

挂起恢复原理

像例子子线程获取到数据之后再主线程通知UI更新这种情况,如果要在协程1中要在其他线程执行协程2, 得到协程2的结果之后再继续执行协程1。 那么这个过程的实现原理是:把当前协程设置为新协程的回调,执行新协程;新新城执行完之后执行上一个协程,上一个协程的执行位置用状态机保存。

调度策略

kotlin协程默认提供了3种调度器, 用户也可自定义调度器。 Dispatchers.Default: 适用于 CPU 密集型任务。 它是协程的默认调度器。 Dispatchers.IO:适用于 I/O 密集型任务,例如网络请求、文件读写等 Dispatchers.Main: 主要用于与 UI 交互的任务,确保协程在主线程中运行

4 EventBus

eventbus虽然可以很好得解决回调地狱的问题,但是消息的发送和处理难以跟踪和管理也一个问题,而且随意的发送消息也会导致软件架构遭到破坏。而且由于eventbus的方便性,可以很方便地从一个类发送消息给另一个类,所以导致有些场景下,开发者不愿意进行好地软件设计,而偷懒采用eventbus。

总结

回调的方法显得比较臃肿,eventbus会导致消息的难以跟踪和管理,协程相比于rxjava更加原生,且用同步的方式写异步的代码也是更加友好的,类似本文提到的两个例子,在这种一般情况下建议使用kotlin协程作为异步开发框架,其他情况再具体分析。

相关推荐
花花鱼4 分钟前
vue3 axios ant-design-vue cdn的方式使用
前端·javascript·vue.js
GoppViper38 分钟前
uniapp中实现<text>文本内容点击可复制或拨打电话
前端·后端·前端框架·uni-app·前端开发
Sam90291 小时前
【Webpack--007】处理其他资源--视频音频
前端·webpack·音视频
Code成立1 小时前
HTML5精粹练习第1章博客
前端·html·博客·html5
架构师ZYL1 小时前
node.js+Koa框架+MySQL实现注册登录
前端·javascript·数据库·mysql·node.js
一只小白菜~2 小时前
实现实时Web应用,使用AJAX轮询、WebSocket、还是SSE呢??
前端·javascript·websocket·sse·ajax轮询
晓翔仔2 小时前
CORS漏洞及其防御措施:保护Web应用免受攻击
前端·网络安全·渗透测试·cors·漏洞修复·应用安全
GISer_Jing4 小时前
【前后端】大文件切片上传
前端·spring boot
csdn_aspnet4 小时前
npm 安装 与 切换 淘宝镜像
前端·npm·node.js
GHUIJS4 小时前
【Echarts】vue3打开echarts的正确方式
前端·vue.js·echarts·数据可视化