实际上这部分跟协程没有什么关系,只是觉得这篇文章主要介绍一些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等概念了。
官方介绍了它的优点:
要求:
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
提到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())
}
官方介绍的特点:
要求:
AndroidX ;Min SDK 14+;Compile SDK: 29+;Java 8+
目前使用的比较多的 Glide 官网文档:http://bumptech.github.io/glide/
对应的中文版本:https://muyangmin.github.io/glide-docs-cn/
关于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用以向用户展示需要该权限的原因,返回的结构result是AssentResult类型,可以从中获取权限申请的结果:
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...
}
网络请求当然还是Retrofit,在前文中已经介绍了协程的挂起函数和Retrofit的结合使用,这里就不多说了。Kotlin协程和在Android中的使用总结(四 协程和Retrofit、Room、WorkManager结合使用)
数据库推荐使用Android官方的Room,在前文中已经介绍了协程的挂起函数和Room的结合使用,这里就不多说了。
协程的使用能够简化代码,基本上RxJava的使用场景,都可以用协程来代替,很久以前有说法说RxJava的学习成本高,不建议使用,但是现在的Android开发者几乎都会RxJava略知一二,几个不会的操作符查一下也没什么大问题,并且RxJava也已经在很多项目中都广泛使用了,那么我们要不要把RxJava转成协程呢?
我觉得技术只是一个工具,如果原来的方式已经能很好的处理业务逻辑,那么没有特别大的必要去转换成协程,但是这并不能成为我们不学习新技术的理由,毕竟协程的优势还是摆在那的,当然协程的学习成本也很高,除了一些基本的挂起函数,flow的使用,channel的使用,selector的使用等都需要去认真学习。
当团队都能够认识到协程带来的好处,以及大家都愿意去尝试改变的时候,那么我们的代码也将变得更加的flow flow flow (666)。
如果大家知道很多协程改善代码的技巧,也欢迎告诉我,大家共同进步。