7、高阶函数在Android中的应用:回调简化
说实话,刚入行那会儿,我最烦的就是写各种Listener。一个按钮点击要写匿名内部类,一个网络请求回调要写一大坨接口,权限请求更是噩梦——onRequestPermissionsResult里塞满了if-else。代码又臭又长,看着就头疼。
后来我深入理解了Kotlin的高阶函数,才发现原来这些东西都可以简化。说白了,高阶函数就是「把函数当参数传」。这个特性在Android里简直是神器,能帮我们把回调代码写得又短又清晰。
7.1 用高阶函数替代Listener
先看一个最常见的场景——点击事件。传统写法是这样的:
// 传统Java写法
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理点击
}
});
Kotlin里直接用Lambda:
// Kotlin Lambda写法
button.setOnClickListener { v ->
// 处理点击
}
这其实就是高阶函数的功劳。setOnClickListener接收的是一个函数类型(View) -> Unit。Kotlin编译器帮我们把Lambda转换成了对应的Listener实例。
我自己封装自定义View时,也喜欢用高阶函数替代Listener。比如一个自定义的加载对话框:
class LoadingDialog(context: Context) : Dialog(context) {
private var onCancel: (() -> Unit)? = null
fun setOnCancelListener(listener: () -> Unit) {
this.onCancel = listener
}
override fun onBackPressed() {
super.onBackPressed()
onCancel?.invoke()
}
}
// 使用
val dialog = LoadingDialog(this)
dialog.setOnCancelListener {
showToast("用户取消了加载")
}
你看,不用再定义接口了。一个函数类型参数搞定一切。
7.2 SharedPreferences的DSL封装
SharedPreferences的API说实话挺难用的。每次都要getSharedPreferences、edit、apply,还要处理key和默认值。我封装过一个DSL版本,用起来舒服多了。
// 先定义一个委托类
class PreferenceDelegate<T>(
private val name: String,
private val default: T,
private val prefs: SharedPreferences
) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T {
return when (default) {
is String -> prefs.getString(name, default) as T
is Int -> prefs.getInt(name, default) as T
is Boolean -> prefs.getBoolean(name, default) as T
is Float -> prefs.getFloat(name, default) as T
is Long -> prefs.getLong(name, default) as T
else -> throw IllegalArgumentException("不支持的类型")
}
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
prefs.edit().apply {
when (value) {
is String -> putString(name, value)
is Int -> putInt(name, value)
is Boolean -> putBoolean(name, value)
is Float -> putFloat(name, value)
is Long -> putLong(name, value)
}
apply()
}
}
}
// DSL封装
class Preferences(private val context: Context) {
private val prefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE)
var userName: String by PreferenceDelegate("user_name", "", prefs)
var age: Int by PreferenceDelegate("age", 0, prefs)
var isVip: Boolean by PreferenceDelegate("is_vip", false, prefs)
}
// 使用
val prefs = Preferences(this)
prefs.userName = "张三" // 自动保存
println(prefs.userName) // 自动读取
这里用到了Kotlin的委托属性,配合高阶函数,实现了读写操作的自动化。我在项目中用了这套方案后,SharedPreferences的代码量减少了至少70%。
7.3 网络请求的回调封装
网络请求的回调是最容易写出回调地狱的地方。我以前用OkHttp的时候,代码经常长这样:
// 传统回调
api.login(username, password, object : Callback<User> {
override fun onSuccess(user: User) {
api.getProfile(user.id, object : Callback<Profile> {
override fun onSuccess(profile: Profile) {
// 嵌套越来越深
}
override fun onError(e: Exception) {
// 错误处理
}
})
}
override fun onError(e: Exception) {
// 错误处理
}
})
用高阶函数封装一下,立马清爽:
// 封装一个高阶函数
inline fun <T> apiCall(
crossinline call: () -> T,
crossinline onSuccess: (T) -> Unit,
crossinline onError: (Exception) -> Unit = {}
) {
thread {
try {
val result = call()
runOnUiThread { onSuccess(result) }
} catch (e: Exception) {
runOnUiThread { onError(e) }
}
}
}
// 使用
apiCall(
call = { api.login(username, password) },
onSuccess = { user ->
apiCall(
call = { api.getProfile(user.id) },
onSuccess = { profile ->
showProfile(profile)
},
onError = { e -> showError(e.message) }
)
},
onError = { e -> showError(e.message) }
)
虽然嵌套还在,但至少错误处理统一了。如果再配合协程,那就更完美了。不过那是后面章节的内容。
runOnUiThread或者Handler切回主线程。
7.4 权限请求的简化
Android的运行时权限请求,API设计得确实不太友好。要重写onRequestPermissionsResult,还要处理各种状态。我封装过一个基于高阶函数的版本:
class PermissionHelper(private val activity: Activity) {
private var onGranted: (() -> Unit)? = null
private var onDenied: ((List<String>) -> Unit)? = null
private val requestCode = 1001
fun request(
vararg permissions: String,
granted: () -> Unit,
denied: (List<String>) -> Unit = {}
) {
this.onGranted = granted
this.onDenied = denied
val deniedList = permissions.filter {
ContextCompat.checkSelfPermission(activity, it) != PackageManager.PERMISSION_GRANTED
}
if (deniedList.isEmpty()) {
granted()
} else {
ActivityCompat.requestPermissions(activity, deniedList.toTypedArray(), requestCode)
}
}
fun handleResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (requestCode != this.requestCode) return
val deniedList = permissions.filterIndexed { index, _ ->
grantResults[index] != PackageManager.PERMISSION_GRANTED
}
if (deniedList.isEmpty()) {
onGranted?.invoke()
} else {
onDenied?.invoke(deniedList)
}
}
}
// 在Activity中使用
class MainActivity : AppCompatActivity() {
private lateinit var permissionHelper: PermissionHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
permissionHelper = PermissionHelper(this)
// 请求权限
permissionHelper.request(
Manifest.permission.CAMERA,
Manifest.permission.READ_EXTERNAL_STORAGE,
granted = {
// 权限已获取,开始拍照
openCamera()
},
denied = { deniedList ->
showToast("以下权限被拒绝:${deniedList.joinToString()}")
}
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
permissionHelper.handleResult(requestCode, permissions, grantResults)
}
}
你看,原来要写一大坨的权限请求,现在变成了一个链式调用。granted和denied两个Lambda,清晰明了。
嗯,这里要注意一点:高阶函数虽然好用,但也不要滥用。如果一个回调有多个方法,或者回调逻辑特别复杂,还是老老实实用接口吧。工具是为人服务的,别为了炫技把代码搞复杂了。