Kotlin 回调函数在的奇妙玩法

本文涉及到的知识点有:扩展函数、Lambda 表达式的高级应用

在 Android 6.0 之后系统加强了对敏感权限的管理,一些敏感权限必须要通过动态权限申请来获得,本文的内容就从这里展开;

一个正常的权限申请流程大致是这样的:

  1. 检查是否存在权限
  2. 如果不存在则申请,存在则进入功能
  3. 如果用户拒绝则弹出对话框告知用户权限的用处,并提供跳转到设置页面的功能;

我使用的是网上比较流行的一个权限申请框架 # tbruyelle/RxPermissions ,当然本文的重点并不是如何使用这个库。

如上所述,我们在一个应用中可能会有很多需要申请不同权限的位置,我们应该为每处需要敏感权限的位置做类似的处理。虽然我们使用了 RxPermissions,但是还是需要在用户拒绝的位置写大量重复的弹窗提示代码,这一点也不优雅。

我们都知道在 Kotlin 中函数也是可以作为参数传入的,说道到这里不知道你有没有想起点什么,我们在使用 Java 编写 Android 代码时时常使用的各种 Listener,不就是类似这样的一个情况么?

我们调用 setXXXListener 函数,并且传入一个接口的匿名内部类实现,这样的操作我们称之为回调,我们传入的这个 Listener 被中的方法称为回调函数,在 Kotlin 里我们也可以按照这种方式来书写:

1
2
3
4
5
6
//Java中的匿名内部类,在 Kotlin 中可以使用 object 实现
mBtnCallback.setOnClickListener(object :View.OnClickListener{
override fun onClick(v: View?) {
println("onclick")
}

也许你在书写类似代码时已经发现了一些端倪:
Lambda 表达式

所有的类似接口,在 Kotlin 中都有一些新的签名,这是因为在 Kotlin 里函数也是参数的一种,在 Java 中只包含一个方法的接口,在 Kotlin 中都可以使用 Lambda 表达式来达成一样的效果。

这样做最大的好处就是简化代码,当我们在阅读代码时更加简洁易读,比如上述代码完全可以简化成:

1
mBtnCallback.setOnClickListener { println("onclick") }

非但如此,以前我们在写一些耗时操作时,通常需要申明一些接口作为回调函数使用,在调用时再用匿名内部类来操作得到的结果,现在你可以这样书写了:

1
2
3
4
5
6
7
8
9
fun doSomeThingNeedTime(call: (response:String) -> Unit) {
Thread.sleep(4000)
var response = "xxxx" //我们假装这里进行了网络请求
call(response)
}

doSomeThingNeedTime { response->
println(response)
}

实际应用

在我们实际应用时其实十分简单,下面的代码也许可以帮你加深对其了解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//传入权限与权限描述,在需要权限的功能打开之前调用
fun Activity.rxRequestPermissions(vararg permissions: String, describe: String, onGranted:()->Unit) {
val keylistener = DialogInterface.OnKeyListener { _, keyCode, event ->
keyCode == KeyEvent.KEYCODE_BACK && event.repeatCount == 0
}
var dialog = AlertDialog.Builder(this)
.setTitle("权限申请")
.setMessage("${describe}为必选项,开通后方可正常使用APP,请在设置中开启。")
.setOnKeyListener(keylistener)
.setCancelable(false)
.setPositiveButton("去开启") { _, _ ->
// JumpPermissionManagement.GoToSetting(this)
finish()
}
.setNegativeButton("结束") { _, _ ->
Toast.makeText(this, "${describe}权限未开启,不能使用该功能!", Toast.LENGTH_SHORT).show()
finish()
}
.create()
val rxPermissions = RxPermissions(this)
//传递kotlin的可变长参数给Java的可变参数的时候需要使用修饰符 * ;这个修饰符叫做Speread Operator
// 它只支持展开的Array 数组,不支持List集合,它只用于变长参数列表的实参,不能重载,它也不是运算符;
rxPermissions.request(*permissions)
.subscribe {granted ->
if (granted) {
onGranted()
} else {
dialog.show()
}
}
}

上述代码就是我写的 Activity 的一个扩展函数,用于实现我们前文提到的更方便的动态申请权限,请看我们的函数申明:

1
2
fun Activity.rxRequestPermissions(vararg permissions: String, describe: String, onGranted:()->Unit) {
}

我们传入的前三个参数是分别是:要申请的权限(对于我们的扩展函数而言是一个可变长参数),第二个参数我们使用“key = value” 这种形式传递,第三个参数就一个回调函数onGranted:()->Unit

其中 onGranted,是我们自己命名的函数名,冒号后面是我们的函数描述即:没有传入参数,返回值类型为 Unit。在我们的扩展函数体中直接将他作为一个函数调用即可,需要注意的是必须要填写括号,AS的自动补全并不会补全这个括号,没有括号时编译器也不会报错。

如何使用

在打开需要申请权限的功能位置,我们只要写下以下的数行代码即可:

1
2
3
4
5
mBtnRecord.setOnClickListener {
rxRequestPermissions(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, describe = "相机、存储、录音") {
startActivityForResult(Intent(this@MainActivity, VideoRecordActivity::class.java), REQUEST_VIDEO)
}
}

首先传入需要申请的权限、权限描述(key = value),第三个参数为一个Lambda表达式,这里进行的是存在权限时需要执行的操作。Lambda 表达式作为函数的最后的一个参数时,我们可以把它放在圆括号外书写。

最终使用时的效果如下:
点击时请求权限

未授予全部权限