7、高阶函数在Android中的应用:回调简化

说实话,刚入行那会儿,我最烦的就是写各种Listener。一个按钮点击要写匿名内部类,一个网络请求回调要写一大坨接口,权限请求更是噩梦——onRequestPermissionsResult里塞满了if-else。代码又臭又长,看着就头疼。

后来我深入理解了Kotlin的高阶函数,才发现原来这些东西都可以简化。说白了,高阶函数就是「把函数当参数传」。这个特性在Android里简直是神器,能帮我们把回调代码写得又短又清晰。

核心思想:用Lambda表达式替代匿名内部类,用高阶函数封装回调逻辑。

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("用户取消了加载")
}

你看,不用再定义接口了。一个函数类型参数搞定一切。

我的习惯:如果回调只有一个方法,我肯定用高阶函数。如果有多个方法(比如onStart、onComplete、onError),我会考虑用接口或者用多个高阶函数参数。

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%。

注意:委托属性的getValue/setValue操作符是固定的签名,不能随意修改。另外,多进程场景下SharedPreferences本身就不安全,这个封装也不适用。

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) }
)

虽然嵌套还在,但至少错误处理统一了。如果再配合协程,那就更完美了。不过那是后面章节的内容。

避坑指南:我曾经在封装网络请求时忘了处理线程切换,结果在子线程更新UI直接崩溃。记得用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,清晰明了。

总结一下:高阶函数在Android中的核心价值就是「简化回调」。不管是Listener、SharedPreferences、网络请求还是权限请求,用高阶函数封装后,代码量减少、可读性提升、维护成本降低。我个人觉得,这是Kotlin相比Java在Android开发中最大的优势之一。

嗯,这里要注意一点:高阶函数虽然好用,但也不要滥用。如果一个回调有多个方法,或者回调逻辑特别复杂,还是老老实实用接口吧。工具是为人服务的,别为了炫技把代码搞复杂了。

高阶函数在Android中的应用 高阶函数 替代Listener setOnClickListener { } 自定义View回调简化 SharedPreferences DSL 委托属性 + 高阶函数 读写自动化,代码量减70% 网络请求回调封装 apiCall { } 统一错误处理 避免回调地狱 权限请求简化 request { granted, denied } 链式调用,清晰明了 核心价值:简化回调,提升代码可读性与维护性
公众号:蓝海资料掘金营,微信deep3321