Kotlin协程和在Android中的使用总结(七 一些使用协程的三方库)

在这里插入图片描述

1 依赖注入Kodin

实际上这部分跟协程没有什么关系,只是觉得这篇文章主要介绍一些Kotlin的三方库,所以将其纳入。

如果还不清楚依赖注入是什么的话,可以先看一下这篇文章:Java:控制反转(IoC)与依赖注入(DI)
提供Android中的依赖注入,大家都会想到Dragger,Android官方也推荐使用它来进行依赖注入。
在官方文档的最佳做法部分,依赖注入模块提到来Dragger:
在Android应用中使用Dragger
在 Android 中使用依赖注入 | AndroidDevSummit 中文字幕视频
虽然Android对Java版本的Dragger做了一些包装来简便使用,但是毕竟还是学习成本太高,一大堆的概念。
但是在Kotlin时代,如何能把依赖注入这件事做得更简单一点呢,于是就有了Kodin。Kodein 全名为 KOtlin DEpendency INjection,实际上它不是一个依赖注入框架,准确的说是一个依赖检索容器,通过后面的代码详细说明。

talk is cheap,show me the code:

val di = DI {
    bind<Dice>() with provider { RandomDice(0, 5) }
    bind<DataSource>() with singleton { SqliteDS.open("path/to/file") }
}

class Controller(private di: DI) {
    private val ds: DataSource by di.instance()
}

没错,就是这样简单,把枯燥无味的流程变得简单就是使用它的原因,再也不用管Dragger中的Module、Component等概念了。

官方介绍了它的优点:

  • 它提出了一个非常简单易读的声明性DSL
  • 它不受类型擦除的限制(对比Java)
  • 它与Android完美集成
  • 它提出了一个非常Kotlin风格的惯用API
  • 它快速且经过优化(广泛使用内联)
  • 可以在纯Java中使用

要求:
Java 8+

这里推荐阅读清梅大神的文章:
Android开发从Dagger2迁移至Kodein的感受
告别Dagger2,Android的Kotlin项目中使用Kodein进行依赖注入
官方文档参考:
Kodein DI on Android
Kodein DI 官网: https://kodein.org/di/
Kodein Github : https://github.com/Kodein-Framework/Kodein-DI


2 图片加载框架 Coil

提到Android中的图片加载框架,大家都会想到UIL(Universal Image Loader)、Fresco、Picasso、Glide等。各种对比的文章在网上都能找到,为什么还要搞一个图片加载库呢?

Coil官网说明:Image loading for Android backed by Kotlin Coroutines.
Coil is an acronym for: Coroutine Image Loader

上代码:

// URL
imageView.load("https://www.example.com/image.jpg")

// Resource
imageView.load(R.drawable.image)

// File
imageView.load(File("/path/to/image.jpg"))

imageView.load("https://www.example.com/image.jpg") {
    crossfade(true)
    placeholder(R.drawable.image)
    transformations(CircleCropTransformation())
}

官方介绍的特点:

  • 快速:Coil执行了许多优化,包括内存和磁盘缓存,对内存中的图像进行重采样,bitmap的复用,自动暂停/取消请求等等。
  • 轻量级:Coil为您的APK添加了约1500种方法(对于已经使用OkHttp和Coroutines的应用程序),与Picasso相当,远小于Glide和Fresco。
  • 易于使用:Coil的API利用Kotlin的语言功能来简化和最小化样板代码。
  • 现代的:Coil使用Kotlin,并使用包括Coroutines,OkHttp,Okio和AndroidX Lifecycles在内的现代库。

要求:
AndroidX ;Min SDK 14+;Compile SDK: 29+;Java 8+

目前使用的比较多的 Glide 官网文档:http://bumptech.github.io/glide/
对应的中文版本:https://muyangmin.github.io/glide-docs-cn/

3 权限申请

关于Android的动态权限申请,可能大家已经广泛使用了RxPremissions,那为啥还要介绍协程版本的权限申请框架呢?
当然是有了协程后,可以充分利用协程的优势了。
看下使用RxPremissions的使用:

final RxPermissions rxPermissions = new RxPermissions(this); // where this is an Activity or Fragment instance

// Must be done during an initialization phase like onCreate
rxPermissions
    .request(Manifest.permission.CAMERA)
    .subscribe(granted -> {
        if (granted) { // Always true pre-M
           // I can control the camera now
        } else {
           // Oups permission denied
        }
    });
    

简单的还行,如果是多个权限同时申请,那么代码就会像下面这样:

public void requsetPermissions(Activity activity) {
        //RxPermission在目标activity里面添加了一个fragment用于拦截权限申请结果
        RxPermissions permissions = new RxPermissions(activity);
        permissions.setLogging(true);
        permissions.requestEach(Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.CALL_PHONE)
                .subscribe(new Consumer<Permission>() {
                    @Override
                    public void accept(Permission permission) throws Exception {
                        //申请和返回的权限名
                        if (permission.name.equalsIgnoreCase(Manifest.permission.READ_EXTERNAL_STORAGE)) {
                            if (permission.granted) {
                                //权限被用户通过
                            } else if (permission.shouldShowRequestPermissionRationale){
                                //权限被用户禁止,但未选中‘不在提示’,则下次涉及此权限还会弹出权限申请框
                            }else {
                                //权限被用户禁止,且选择‘不在提示’,当下次涉及此权限,不会弹出权限申请框
                            }
                        }
                        if (permission.name.equalsIgnoreCase(Manifest.permission.CALL_PHONE)) {
                            if (permission.granted) {
                            
                            } else if (permission.shouldShowRequestPermissionRationale){
                               
                            }else {
                                
                            }
                        }
                    }
                });
    }

//检查某个权限是否被申请
 permissions.isGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)

没错,好多嵌套{ }在里面,代码结构变得不清晰,那么下面看一下协程版本的权限申请使用。
GitHub上搜索协程的权限申请有很多,这里介绍一个:
Assent :https://github.com/afollestad/assent

在Module的build.gradle添加依赖:

dependencies {
  //core API   
  implementation 'com.afollestad.assent:core:3.0.0-RC4'
  //协程调用方式API
  implementation 'com.afollestad.assent:coroutines:3.0.0-RC4'
  //Google建议在用户可能不明白为什么需要该权限时,显示权限的基本原理。
  implementation 'com.afollestad.assent:rationales:3.0.0-RC4'
  
}

在Activity中使用:

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    val rationaleHandler = createSnackBarRationale(rootView) {
      onPermission(READ_CONTACTS, "Test rationale #1, please accept!")
      onPermission(WRITE_EXTERNAL_STORAGE, "Test rationale #2, please accept!")
      onPermission(READ_SMS, "Test rationale #3, please accept!")
    }

    requestPermissionButton.clicks()//
        .debounce(200L)
        .onEach {
          val result = awaitPermissionsResult(
              READ_CONTACTS, WRITE_EXTERNAL_STORAGE, READ_SMS,
              rationaleHandler = rationaleHandler
          )
          statusText.text = result.toString()
        }
        .launchIn(lifecycleScope)
  }

上面代码中使用了clicks()方法,将点击事件转换为一个flow,然后可以通过debounce(200L)来去除抖动,防止快速点击,用到的库是flowBinding“io.github.reactivecircus.flowbinding:flowbinding-android:0.9.0”,通过createSnackBarRationale()方法传建的rationaleHandler用以向用户展示需要该权限的原因,返回的结构resultAssentResult类型,可以从中获取权限申请的结果:

val result: AssentResult = // ...

val permissions: List<Permission> = result.permissions
val grantResults: List<GrantResult> = result.grantResults

// Takes a single permission and returns if this result contains it in its set
val containsPermission: Boolean = result.containsPermission(WRITE_EXTERNAL_STORAGE)

// You can pass multiple permissions as varargs
val permissionGranted: Boolean = result.isAllGranted(WRITE_EXTERNAL_STORAGE)

// You can pass multiple permissions as varargs
val permissionDenied: Boolean = result.isAllDenied(WRITE_EXTERNAL_STORAGE)

// Returns GRANTED, DENIED, or PERMANENTLY_DENIED
val writeStorageGrantResult: GrantResult = result[WRITE_EXTERNAL_STORAGE]

val granted: Set<Permission> = result.granted()

val denied: Set<Permission> = result.denied()

val permanentlyDenied: Set<Permission> = result.permanentlyDenied()

其中Permission和GrantResult是枚举类型:

@SuppressLint("InlinedApi")
enum class Permission(val value: String) {
  UNKNOWN(""),

  READ_CALENDAR(Manifest.permission.READ_CALENDAR),

  WRITE_CALENDAR(Manifest.permission.WRITE_CALENDAR),

  CAMERA(Manifest.permission.CAMERA),

  READ_CONTACTS(Manifest.permission.READ_CONTACTS),
  WRITE_CONTACTS(Manifest.permission.WRITE_CONTACTS),
  GET_ACCOUNTS(Manifest.permission.GET_ACCOUNTS),
  .
  .
  .
  }

enum class GrantResult {
  GRANTED,
  DENIED,
  PERMANENTLY_DENIED
}

如果不想使用上面的flowBinding库,直接在协程中调用也是可以的:
首先awaitPermissionsResult(…)等同于askForPermissions(…),只是以协程形式调用:

// Launch a coroutine in some scope...
launch {
   val result: AssentResult = awaitPermissionsResult(
       READ_CONTACTS, WRITE_EXTERNAL_STORAGE, READ_SMS,
       rationaleHandler = rationaleHandler
   )
   // Use the result...
}

第二,awaitPermissionsGranted(…)等同于runWithPermissions(…)的协程形式:

// Launch a coroutine in some scope...
launch {
   awaitPermissionsGranted(
       READ_CONTACTS, WRITE_EXTERNAL_STORAGE, READ_SMS,
       rationaleHandler = rationaleHandler
   )
   // All three permissions were granted...
}

4 网络请求

网络请求当然还是Retrofit,在前文中已经介绍了协程的挂起函数和Retrofit的结合使用,这里就不多说了。Kotlin协程和在Android中的使用总结(四 协程和Retrofit、Room、WorkManager结合使用)

5 数据库

数据库推荐使用Android官方的Room,在前文中已经介绍了协程的挂起函数和Room的结合使用,这里就不多说了。

6 更多

协程的使用能够简化代码,基本上RxJava的使用场景,都可以用协程来代替,很久以前有说法说RxJava的学习成本高,不建议使用,但是现在的Android开发者几乎都会RxJava略知一二,几个不会的操作符查一下也没什么大问题,并且RxJava也已经在很多项目中都广泛使用了,那么我们要不要把RxJava转成协程呢?

我觉得技术只是一个工具,如果原来的方式已经能很好的处理业务逻辑,那么没有特别大的必要去转换成协程,但是这并不能成为我们不学习新技术的理由,毕竟协程的优势还是摆在那的,当然协程的学习成本也很高,除了一些基本的挂起函数,flow的使用,channel的使用,selector的使用等都需要去认真学习。

当团队都能够认识到协程带来的好处,以及大家都愿意去尝试改变的时候,那么我们的代码也将变得更加的flow flow flow (666)。

如果大家知道很多协程改善代码的技巧,也欢迎告诉我,大家共同进步。

发布了82 篇原创文章 · 获赞 86 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/unicorn97/article/details/105242686