如何让你的回调更具Kotlin风味,2024年字节跳动74道高级程序员面试

  • 1、(只有一个回调函数简写形式)OnClickListener回调Kotlin改造

//只有一个回调函数普通简写形式: OnClickListener的使用
mBtnSubmit.setOnClickListener { view ->
//add your logic code
}

//针对OnClickListener监听设置Coroutine协程框架中onClick扩展函数的使用
mBtnSubmit.onClick { view ->
//add your logic code
}

//Coroutine协程框架: onClick的扩展函数定义
fun android.view.View.onClick(
context: CoroutineContext = UI,
handler: suspend CoroutineScope.(v: android.view.View?) -> Unit
) {
setOnClickListener { v ->
launch(context) {
handler(v)
}
}
}

复制代码

  • 2、(多个回调函数object表达式)TextWatcher回调的Kotlin改造(object对象表达式)

mEtComment.addTextChangedListener(object: TextWatcher{
override fun afterTextChanged(s: Editable?) {
//add your logic code
}

override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
//add your logic code
}

override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
//add your logic code
}

})
复制代码

关于object对象表达式实现的Kotlin中回调,有不少的Kotlin的小伙伴在公众号留言向我吐槽过,感觉这样的写法是直接从Java中的翻译过来的一样,完全看不出Kotlin的优势在哪。问我有没有什么更加具有Kotlin风味的写法,当然是有的,请接着往下看。

三、进一步让你的回调更具Kotlin风味(DSL配置回调)

其实如果你看过很多国外大佬的有关Koltin项目的源码,你就会发现他们写回调很少去使用object表达式去实现回调,而是采用另一种方式去实现,并且整体写法看起来更具有Kotlin风味。即使内部用到object表达式,暴露给外层中间都会做一层DSL配置转换,让外部调用起来更加Kotlin化。以Github中的MaterialDrawer项目(目前已经有1W多star)中官方指定MatrialDrawer项目Kotlin版本实现的MaterialDrawerKt项目中间一段源码为例:

  • 1、DrawerImageLoader 回调定义

//注意: 这个函数参数是一个带返回值的lambda表达式
public fun drawerImageLoader(actions: DrawerImageLoaderKt.() -> Unit): DrawerImageLoader.IDrawerImageLoader {
val loaderImpl = DrawerImageLoaderKt().apply(actions).build() //
DrawerImageLoader.init(loaderImpl)
return loaderImpl
}

//DrawerImageLoaderKt: DSL listener Builder类
public class DrawerImageLoaderKt {
//定义需要回调的函数lamba成员对象
private var setFunc: ((ImageView, Uri, Drawable?, String?) -> Unit)? = null
private var placeholderFunc: ((Context, String?) -> Drawable)? = null

internal fun build() = object : AbstractDrawerImageLoader() {

private val setFunction: (ImageView, Uri, Drawable?, String?) -> Unit = setFunc
?: throw IllegalStateException(“DrawerImageLoader has to have a set function”)

private val placeholderFunction = placeholderFunc
?: { ctx, tag -> super.placeholder(ctx, tag) }

override fun set(imageView: ImageView, uri: Uri, placeholder: Drawable?, tag: String?) = setFunction(imageView, uri, placeholder, tag)

override fun placeholder(ctx: Context, tag: String?) = placeholderFunction(ctx, tag)

}

//暴露给外部调用的回调函数,在构建类中类似setter,getter方法
public fun set(setFunction: (imageView: ImageView, uri: Uri, placeholder: Drawable?, tag: String?) -> Unit) {
this.setFunc = setFunction
}

public fun placeholder(placeholderFunction: (ctx: Context, tag: String?) -> Drawable) {
this.placeholderFunc = placeholderFunction
}
复制代码

  • 2、DrawerImageLoader回调使用

drawerImageLoader {
//内部的回调函数可以选择性重写
set { imageView, uri, placeholder, _ ->
Picasso.with(imageView.context)
.load(uri)
.placeholder(placeholder)
.into(imageView)
}

cancel { imageView ->
Picasso.with(imageView.context)
.cancelRequest(imageView)
}
}
复制代码

可以看到使用DSL配置的回调更加具有Kotlin风味,让整个回调看起来非常的舒服,那种效果岂止丝滑。

四、DSL配置回调基本步骤

在Kotlin的一个类中实现了DSL配置回调非常简单主要就三步:

  • 1、定义一个回调的Builder类,并且在类中定义回调lamba表达式对象成员,最后再定义Builder类的成员函数,这些函数就是暴露给外部回调的函数。个人习惯把它作为一个类的内部类。类似下面这样

class AudioPlayer(context: Context){
//other logic …

inner class ListenerBuilder {
internal var mAudioPlayAction: ((AudioData) -> Unit)? = null
internal var mAudioPauseAction: ((AudioData) -> Unit)? = null
internal var mAudioFinishAction: ((AudioData) -> Unit)? = null

fun onAudioPlay(action: (AudioData) -> Unit) {
mAudioPlayAction = action
}

fun onAudioPause(action: (AudioData) -> Unit) {
mAudioPauseAction = action
}

fun onAudioFinish(action: (AudioData) -> Unit) {
mAudioFinishAction = action
}
}
}

复制代码

  • 2、然后,在类中声明一个ListenerBuilder的实例引用,并且暴露一个设置该实例对象的一个方法,也就是我们常说的注册事件监听或回调的方法,类似setOnClickListenter这种。但是需要注意的是函数的参数是带ListenerBuilder返回值的lamba,类似下面这样:

class AudioPlayer(context: Context){
//other logic …

private lateinit var mListener: ListenerBuilder
fun registerListener(listenerBuilder: ListenerBuilder.() -> Unit) {//带ListenerBuilder返回值的lamba
mListener = ListenerBuilder().also(listenerBuilder)
}
}
复制代码

  • 3、最后在触发相应事件调用Builder实例中lamba即可

class AudioPlayer(context: Context){
//other logic …
val mediaPlayer = MediaPlayer(mContext)
mediaPlayer.play(mediaItem, object : PlayerCallbackAdapter() {
override fun onPlay(item: MediaItem?) {
if (::mListener.isInitialized) {
mListener.mAudioPlayAction?.invoke(mAudioData)
}
}

override fun onPause(item: MediaItem?) {
if (::mListener.isInitialized) {
mListener.mAudioPauseAction?.invoke(mAudioData)
}
}

override fun onPlayCompleted(item: MediaItem?) {
if (::mListener.isInitialized) {
mListener.mAudioFinishAction?.invoke(mAudioData)
}
}
})
}
复制代码

  • 4、外部调用

val audioPlayer = AudioPlayer(context)
audioPlayer.registerListener {
//可以任意选择需要回调的函数,不必要完全重写
onAudioPlay {
//todo your logic
}

onAudioPause {
//todo your logic
}

onAudioFinish {
//todo your logic
}
}
复制代码

相比object表达式回调写法,有没有发现DSL回调配置更懂Kotlin. 可能大家看起来确实不错,但是不知道它具体原理,毕竟这样写法太语法糖化,不太好理解,让我们接下来一起揭开它的糖衣。

五、揭开DSL回调配置的语法糖衣

  • 1、原理阐述

DSL回调配置其实挺简单的,实际上就一个Builder类中维护着多个回调lambda的实例,然后在外部回调的时候再利用带Builder类返回值实例的lamba特性,在该lambda作用域内this可以内部表达为Builder类实例,利用Builder类实例调用它内部定义成员函数并且赋值初始化Builder类回调lambda成员实例,而这些被初始化过的lambda实例就会在内部事件被触发的时候执行invoke操作。如果在该lambda内部没有调用某个成员方法,那么在该Builder类中这个回调lambda成员实例就是为null,即使内部事件触发,为空就不会回调到外部。

换句话就是外部回调的函数block块会通过Builder类中成员函数初始化Builder类中回调lambda实例(在上述代码表现就是mXXXAction实例),然后当内部事件触发后,根据当前lambda实例是否被初始化,如果初始化完毕,就是立即执行这个lambda也就是执行传入的block代码块

  • 2、代码拆解 为了更加清楚论证上面的阐述,我们可以把代码拆解一下:

mAudioPlayer.registerListener({
//registerListener参数是个带ListenerBuilder实例返回值的lambda
//所以这里this就是内部指代为ListenerBuilder实例
this.onAudioPlay ({
//logic block
})
this.onAudioPause ({
// logic block
})
this.onAudioFinish({
// logic block
})
})
复制代码

onAudioPlay为例其他同理,调用ListenerBuilderonAudioPlay函数,并传入block块来赋值初始化ListenerBuilder类中的mAudioPlayActionlambda实例,当AudioPlayer中的onPlay函数被回调时,就执行mAudioPlayActionlambda。

貌似看起来object对象表达式回调相比DSL回调表现那么一无是处,是不是完全可以摒弃object对象表达式这种写法呢?其实不然,object对象表达式这种写法也是有它优点的,具体有什么优点,请接着看它们两种形式对比。

六、object对象表达式回调和DSL回调对比

  • 1、调用写法上对比

//使用DSL配置回调
val audioPlayer = AudioPlayer(context)
audioPlayer.registerListener {
//可以任意选择需要回调的函数,不必要完全重写
onAudioPlay {
//todo your logic
}

onAudioPause {
//todo your logic
}

onAudioFinish {
//todo your logic
}
}

//使用object对象表达式回调
val audioPlayer = AudioPlayer(context)
audioPlayer.registerListener(object: AudioPlayListener{
override fun onAudioPlay(audioData: AudioData) {
//todo your logic
}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
img

总结

算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。

这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。

部分资料展示:




有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

资料获取方法:点赞+关注+转发,然后进入我的【GitHub】,里面有免费获取途径

…(img-QFkusH3p-1710694590657)]
[外链图片转存中…(img-CF3X9DIS-1710694590658)]
[外链图片转存中…(img-ZlRSbIrU-1710694590658)]
[外链图片转存中…(img-9Kc3peN4-1710694590658)]

有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂Offer的距离更加近。

资料获取方法:点赞+关注+转发,然后进入我的【GitHub】,里面有免费获取途径

  • 29
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值