安卓开发异步框架

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

其实异步开发主要是面对两个场景: 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协程作为异步开发框架,其他情况再具体分析。

相关推荐
0wioiw02 分钟前
Flutter基础(前端教程④-组件拼接)
前端·flutter
花生侠27 分钟前
记录:前端项目使用pnpm+husky(v9)+commitlint,提交代码格式化校验
前端
一涯34 分钟前
Cursor操作面板改为垂直
前端
我要让全世界知道我很低调41 分钟前
记一次 Vite 下的白屏优化
前端·css
1undefined243 分钟前
element中的Table改造成虚拟列表,并封装成hooks
前端·javascript·vue.js
蓝倾1 小时前
淘宝批量获取商品SKU实战案例
前端·后端·api
comelong1 小时前
Docker容器启动postgres端口映射失败问题
前端
花海如潮淹1 小时前
硬件产品研发管理工具实战指南
前端·python
用户3802258598241 小时前
vue3源码解析:依赖收集
前端·vue.js