为数不多的人知道的 Kotlin 技巧及解析(三)
- 如果评论区没有及时回复,欢迎来公众号:ByteCode 咨询
- 公众号:ByteCode。致力于分享最新技术原创文章,涉及 Kotlin、Jetpack、算法、译文、系统源码相关的文章
文章中没有奇淫技巧,都是一些在实际开发中常用的技巧
Google 引入 Kotlin 的目的就是为了让 Android 开发更加方便,自从官宣 Kotlin 成为了 Android 开发的首选语言之后,已经有越来越多的团队,在项目使用 Kotlin。
众所周知 xml 十分耗时,因此在 Android 10.0 上新增加 tryInflatePrecompiled
方法,这是一个在编译期运行的一个优化,因为布局文件越复杂 XmlPullParser 解析 XML 越耗时, tryInflatePrecompiled
方法根据 XML 预编译生成 compiled_view.dex
, 然后通过反射来生成对应的 View,从而减少 XmlPullParser 解析 XML 的时间,但是目前一直处于禁用状态。源码解析请查看 Android 资源加载源码分析一。
因此一些体量比较大的应用,为了极致的优化,缩短一点时间,对于简单的布局,会使用 Kotlin 去重写这部分 UI,但是门槛还是很高,随着 Jetpack Compose 的出现,其目的是让您更快、更轻松地构建原生 Android 应用,前不久 Google 正式发布了 Jetpack Compose 1.0。
Kotlin 优势已经体现在了方方面面,结合着 Kotlin 的高级函数的特性可以让代码可读性更强,更加简洁,但是如果使用不当会对性能造成一些损耗,更多内容可前往查看。
以上两篇文章,主要分享了 Kotlin 在实际项目中使用的技巧,以及如果使用不当会对 性能 和 内存 造成的那些影响以及如何规避这些问题等等。
通过这篇文章你将学习到以下内容:
- 什么是 Contract,以及如何使用?
- Kotlin 注解在项目中的使用?
- 一行代码接受 Activity 或者 Fragment 传递的参数?
- 一行代码实现 Activity 之间传递参数?
- 一行代码实现 Fragment 之间传递参数?
- 一行代码实现点击事件,避免内存泄露?
KtKit 仓库
这篇文章主要围绕一个新库 KtKit 来介绍一些 Kotlin 技巧,正如其名 KtKit 是用 Kotlin 语言编写的工具库,包含了项目中常用的一系列工具,是 Jetpack ktx 系列的补充,涉及到了很多从 Kotlin 源码、Jetpack ktx、anko 等等知名的开源项目中学习到的技巧,包含了 Kotlin 委托属性、高阶函数、扩展函数、内联、注解的使用等等。
如果想要使用文中的 API 需要将下列代码添加在模块级 build.gradle
文件内, 最新版本号请查看 版本记录。
implementation "com.hi-dhl:ktkit:${ktkitVersion}" |
因为篇幅原因,文章中不会过多的涉及源码分析,源码部分将会在后续的文章中分享。
什么是 Contract,以及如何使用
众所周知 Kotlin 是比较智能的,比如 smart cast 特性,但是在有些情况下显得很笨拙,并不是那么智能,如下所示。
public inline fun String?.isNotNullOrEmpty(): Boolean { |
正如你所见,只有字符串 name 不为空时,才会进入注释 1 的地方,但是以上代码却无法正常编译,如下图所示。
编译器会告诉你一个编译错误,经过代码分析只有当字符串 name 不为空时,才会进入注释 1 的地方,但是编译器却无法正常推断出来,真的是编译器做不到吗?看看官方文档是如何解释的。
However, as soon as these checks are extracted in a separate function, all the smartcasts immediately disappear:
将检查提取到一个函数中, smart cast 所带来的效果都会消失
编译器无法深入分析每一个函数,原因在于实际开发中我们可能写出更加复杂的代码,而 Kotlin 编译器进行了大量的静态分析,如果编译器去分析每一个函数,需要花费时间分析上下文,增加它的编译耗时的时间。
如果要解决上诉问题,这就需要用到 Contract 特性,Contract 是 Kotlin 提供的非常有用的特性,Contract 的作用就是当 Kotlin 编译器没有足够的信息去分析函数的情况的时候,Contracts 可以为函数提供附加信息,帮助 Kotlin 编译器去分析函数的情况,修改代码如下所示。
inline fun String?.isNotNullOrEmpty(): Boolean { |
相比于之前的代码,在 isNotNullOrEmpty()
函数中添加了 contract 代码块即可正常编译通过,这行代码的意思就是,如果返回值是 true ,this 所指向对象就不为 null。 而在 Kotlin 标准库中大量的用到 contract 特性。 上述示例的使用可前往查看 KtKit/ProfileActivity.kt。
Kotlin 注解在项目中的使用
contract 是 Kotlin 1.3 添加的实验性的 API,如果我们调用实验性的 API 需要添加 @ExperimentalContracts
注解才可以正常使用,但是如果添加 @ExperimentalContracts
注解,所有调用这个方法的地方都需要添加注解,如果想要解决这个问题。只需要在声明 contract 文件中的第一行添加以下代码即可。
@file:OptIn(ExperimentalContracts::class) |
在上述示例中使用了 inline 修饰符,但是编译器会有一个黄色警告,如下图所示。
编译器建议我们将函数作为参数时使用 Inline,Inline (内联函数) 的作用:提升运行效率,调用被 inline 修饰符的函数,会将方法内的代码段放到调用处。
既然 Inline 修饰符可以提升运行效率,为什么还给出警告,因为 Inline 修饰符的滥用会带来性能损失,更多内容前往查看 Inline 修饰符带来的性能损失。
Inline 修饰符常用于下面的情况,编译器才不会有警告:
- 将函数作为参数(例如:lambda 表达式)
- 结合 reified 实化类型参数一起使用
但是在普通的方法中,使用 Inline 修饰符,编译会给出警告,如果方法体的代码段很短,想要通过 Inline 修饰符提升性能(虽然微乎其微),可以在文件的第一行添加下列代码,可消除警告。
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") |
然后在使用 Inline 修饰符的地方添加以下注解,即可愉快的使用。
@kotlin.internal.InlineOnly |
注解 @kotlin.internal.InlineOnly
的作用:
- 消除编译器的警告
- 修改内联函数的可见性,在编译时修改成 private
// 未添加 InlineOnly 编译后的代码 |
关于注解完整的使用案例,可前往仓库 KtKit 查看。
一行代码接受 Activity 或者 Fragment 传递的参数
如果想要实现一行代码接受 Activity 或者 Fragment 传递的参数,可以通过 Kotlin 委托属性来实现,在仓库 KtKit 中提供了两个 API,根据实际情况使用即可。案例可前往查看 KtKit/ProfileActivity.kt。
class ProfileActivity : Activity() { |
一行代码实现 Activity 之间传递参数
这个思路是参考了 anko 的实现,同样是提供了两个 API , 根据实际情况使用即可,可以传递 Android 支持的任意参数,案例可前往查看 KtKit/ProfileActivity.kt。
// API: |
Activity 之间传递参数 和 并回传结果
// 方式一 |
回传结果
// 方式一 |
一行代码实现 Fragment 之间传递参数
和 Activity 一样提供了两个 API 根据实际情况使用即可,可以传递 Android 支持的任意参数,源码前往查看 KtKit/LoginFragment.kt。
// API: |
一行代码实现点击事件,避免内存泄露
KtKit 提供了常用的三个 API:单击事件、延迟第一次点击事件、防止多次点击
单击事件
view.click(lifecycleScope) { showShortToast("公众号:ByteCode" } |
延迟第一次点击事件
// 默认延迟时间是 500ms |
防止多次点击
// 默认间隔时间是 500ms |
但是 View#setOnClickListener
造成的内存泄露,如果做过性能优化的同学应该会见到很多这种 case。
根本原因在于不规范的使用,在做业务开发的时候,根本不会关注这些,那么如何避免这个问题呢,Kotlin Flow 提供了一个非常有用的 API callbackFlow
,源码如下所示。
fun View.clickFlow(): Flow<View> { |
callbackFlow
正如其名将一个 callback 转换成 flow,awaitClose
会在 flow 结束时执行。
那么 flow 什么时候结束执行
源码中我将 Flow 通过 lifecycleScope 与 Activity / Fragment 的生命周期绑定在一起,在 Activity / Fragment 生命周期结束时,会结束 flow , flow 结束时会将 Listener 置为 null,有效的避免内存泄漏,源码如下所示。
inline fun View.click(lifecycle: LifecycleCoroutineScope, noinline onClick: (view: View) -> Unit) { |
总结
仓库 KtKit 是用 Kotlin 语言编写的工具库,包含了项目中常用的一系列工具,但是目前还不是很完善,正在陆续将一些常用的功能,结合着 Kotlin 的高级函数的特性,不仅让代码可读性更强,使用更加简单,而且还可以帮助我们解决项目中常见的问题。
项目中引用了 spotless 插件,执行 ./gradlew spotlessApply
会将 Java 、Kotlin 、xml 、gradle 、md 、gitignore 等等文件按照官方标准去格式化代码。这也是 Google 提交代码的时候,推荐的方式。
全文到这里就结束了,如果这个仓库对你有帮助,请在仓库右上角帮我 star 一下,非常感谢你的支持,同时也欢迎你提交 PR ❤️❤️❤️
如果有帮助 点个赞 就是对我最大的鼓励
代码不止,文章不停
欢迎关注公众号:ByteCode,持续分享最新的技术
- 本文作者:hi-dhl
- 本文标题:为数不多的人知道的 Kotlin 技巧及解析(三)
- 本文链接:https://hi-dhl.com/2021/08/02/kotlin/11-kotlin-tip3/
- 版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 hi-dhl