本文没有什么奇淫技巧,都是一些在实际开发中常用的技巧

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 技巧及解析(二)
  • 为数不多的人知道的 Kotlin 技巧及解析(一)

以上两篇文章,主要分享了 Kotlin 在实际项目中使用的技巧,以及如果使用不当会对 性能内存 造成的那些影响以及如何规避这些问题等等。

通过这篇文章你将学习到以下内容:

  • 什么是 Contract,以及如何使用?
  • Kotlin 注解在项目中的使用?
  • 一行代码接受 Activity 或者 Fragment 传递的参数?
  • 一行代码实现 Activity 之间传递参数?
  • 一行代码实现 Fragment 之间传递参数?
  • 一行代码实现点击事件,避免内存泄露?

KtKit 仓库

这篇文章主要围绕一个新库 KtKit 来介绍一些 Kotlin 技巧,正如其名 KtKit 是用 Kotlin 语言编写的工具库,包含了项目中常用的一系列工具,是 Jetpack ktx 系列的补充,涉及到了很多从 Kotlin 源码、Jetpack ktx、anko 等等知名的开源项目中学习到的技巧,包含了 Kotlin 委托属性、高阶函数、扩展函数、内联、注解的使用等等。

  • KtKit 仓库地址:https://github.com/hi-dhl/KtKit
  • KtKit 在线阅读:https://ktkit.hi-dhl.com

如果想要使用文中的 API 需要将下列代码添加在模块级 build.gradle 文件内, 最新版本号请查看 版本记录。

implementation "com.hi-dhl:ktkit:${ktkitVersion}"

因为篇幅原因,文章中不会过多的涉及源码分析,源码部分将会在后续的文章中分享。

什么是 Contract,以及如何使用

众所周知 Kotlin 是比较智能的,比如 smart cast 特性,但是在有些情况下显得很笨拙,并不是那么智能,如下所示。

public inline fun String?.isNotNullOrEmpty(): Boolean {return this != null && !this.trim().equals("null", true) && this.trim().isNotEmpty()
}fun testString(name: String?) {if (name.isNotNullOrEmpty()) {println(name.length) // 1}
}

正如你所见,只有字符串 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 {contract {returns(true) implies (this@isNotNullOrEmpty != null)}return this != null && !this.trim().equals("null", true) && this.trim().isNotEmpty()
}fun testString(name: String?) {if (name != null && name.isNotNullOrEmpty()) {println(name.length)  // 1}
}

相比于之前的代码,在 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 编译后的代码
public static final void showShortToast(@NotNull Context $this$showShortToast, @NotNull String message) {......Toast.makeText($this$showShortToast, (CharSequence)message, 0).show();
}// 添加 InlineOnly 编译后的代码
@InlineOnly
private static final void showShortToast(Context $this$showShortToast, String message) {......Toast.makeText($this$showShortToast, (CharSequence)message, 0).show();
}

关于注解完整的使用案例,可前往仓库 KtKit 查看。

一行代码接受 Activity 或者 Fragment 传递的参数

如果想要实现一行代码接受 Activity 或者 Fragment 传递的参数,可以通过 Kotlin 委托属性来实现,在仓库 KtKit 中提供了两个 API,根据实际情况使用即可。案例可前往查看 KtKit/ProfileActivity.kt。

class ProfileActivity : Activity() {// 方式一: 不带默认值private val userPassword by intent<String>(KEY_USER_PASSWORD)// 方式二:带默认值:如果获取失败,返回一个默认值private val userName by intent<String>(KEY_USER_NAME) { "公众号:ByteCode" }
}

一行代码实现 Activity 之间传递参数

这个思路是参考了 anko 的实现,同样是提供了两个 API , 根据实际情况使用即可,可以传递 Android 支持的任意参数,案例可前往查看 KtKit/ProfileActivity.kt。

// API:
activity.startActivity<ProfileActivity> {  arrayOf( KEY_USER_NAME to "ByteCode" ) }
activity.startActivity<ProfileActivity>( KEY_USER_NAME to "ByteCode" )// Example:
class ProfileActivity : Activity() {......companion object {......// 方式一activity.startActivity<ProfileActivity> {arrayOf(KEY_USER_NAME to "ByteCode",KEY_USER_PASSWORD to "1024")}// 方式二activity.startActivity<ProfileActivity>( KEY_USER_NAME to "ByteCode",KEY_USER_PASSWORD to "1024" )}
}

Activity 之间传递参数 和 并回传结果

// 方式一
context.startActivityForResult<ProfileActivity>(KEY_REQUEST_CODE,KEY_USER_NAME to "ByteCode",KEY_USER_PASSWORD to "1024"
)// 方式二
context.startActivityForResult<ProfileActivity>(KEY_REQUEST_CODE) {arrayOf(KEY_USER_NAME to "ByteCode",KEY_USER_PASSWORD to "1024")
}

回传结果

// 方式一
setActivityResult(Activity.RESULT_OK) {arrayOf(KEY_RESULT to "success",KEY_USER_NAME to "ByteCode")
}// 方式二
setActivityResult(Activity.RESULT_OK,KEY_RESULT to "success",KEY_USER_NAME to "ByteCode"
)

一行代码实现 Fragment 之间传递参数

和 Activity 一样提供了两个 API 根据实际情况使用即可,可以传递 Android 支持的任意参数,源码前往查看 KtKit/LoginFragment.kt。

// API:
LoginFragment().makeBundle(  KEY_USER_NAME to "ByteCode" )
LoginFragment().makeBundle { arrayOf( KEY_USER_NAME to "ByteCode" ) }// Example:
class LoginFragment : Fragment(R.layout.fragment_login) {......companion object {......// 方式一fun newInstance1(): Fragment {return LoginFragment().makeBundle(KEY_USER_NAME to "ByteCode",KEY_USER_PASSWORD to "1024")}// 方式二fun newInstance2(): Fragment {return LoginFragment().makeBundle {arrayOf(KEY_USER_NAME to "ByteCode",KEY_USER_PASSWORD to "1024")}}}
}

一行代码实现点击事件,避免内存泄露

KtKit 提供了常用的三个 API:单击事件、延迟第一次点击事件、防止多次点击

单击事件

view.click(lifecycleScope) { showShortToast("公众号:ByteCode" }

延迟第一次点击事件

// 默认延迟时间是 500ms
view.clickDelayed(lifecycleScope){ showShortToast("公众号:ByteCode" }// or
view.clickDelayed(lifecycleScope, 1000){ showShortToast("公众号:ByteCode") }

防止多次点击

// 默认间隔时间是 500ms
view.clickTrigger(lifecycleScope){ showShortToast("公众号:ByteCode") }// or
view.clickTrigger(lifecycleScope, 1000){ showShortToast("公众号:ByteCode") }

但是 View#setOnClickListener 造成的内存泄露,如果做过性能优化的同学应该会见到很多这种 case。

根本原因在于不规范的使用,在做业务开发的时候,根本不会关注这些,那么如何避免这个问题呢,Kotlin Flow 提供了一个非常有用的 API callbackFlow,源码如下所示。

fun View.clickFlow(): Flow<View> {return callbackFlow {setOnClickListener {safeOffer(it)}awaitClose { setOnClickListener(null) }}
}

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) {clickFlow().onEach {onClick(this)}.launchIn(lifecycle)
}

总结

仓库 KtKit 是用 Kotlin 语言编写的工具库,包含了项目中常用的一系列工具,但是目前还不是很完善,正在陆续将一些常用的功能,结合着 Kotlin 的高级函数的特性,不仅让代码可读性更强,使用更加简单,而且还可以帮助我们解决项目中常见的问题。

  • KtKit 仓库地址:https://github.com/hi-dhl/KtKit
  • KtKit 在线阅读:https://ktkit.hi-dhl.com

项目中引用了 spotless 插件,执行 ./gradlew spotlessApply 会将 Java 、Kotlin 、xml 、gradle 、md 、gitignore 等等文件按照官方标准去格式化代码。这也是 Google 提交代码的时候,推荐的方式。

全文到这里就结束了,如果这个仓库对你有帮助,请在仓库右上角帮我 star 一下,非常感谢你的支持,同时也欢迎你提交 PR ❤️❤️❤️

如果有帮助 点个赞 就是对我最大的鼓励

代码不止,文章不停


最后推荐我一直在更新维护的项目和网站:

  • 个人博客,将所有文章进行分类,欢迎前去查看 https://hi-dhl.com

  • 计划建立一个最全、最新的 AndroidX Jetpack 相关组件的实战项目 以及 相关组件原理分析文章,正在逐渐增加 Jetpack 新成员,仓库持续更新,欢迎前去查看:AndroidX-Jetpack-Practice

  • LeetCode / 剑指 offer / 国内外大厂面试题 / 多线程 题解,语言 Java 和 kotlin,包含多种解法、解题思路、时间复杂度、空间复杂度分析

  • 剑指 offer 及国内外大厂面试题解:在线阅读

  • LeetCode 系列题解:在线阅读

  • 最新 Android 10 源码分析系列文章,了解系统源码,不仅有助于分析问题,在面试过程中,对我们也是非常有帮助的,仓库持续更新,欢迎前去查看 Android10-Source-Analysis

  • 整理和翻译一系列精选国外的技术文章,每篇文章都会有译者思考部分,对原文的更加深入的解读,仓库持续更新,欢迎前去查看 Technical-Article-Translation

  • 「为互联网人而设计,国内国外名站导航」涵括新闻、体育、生活、娱乐、设计、产品、运营、前端开发、Android 开发等等网址,欢迎前去查看 为互联网人而设计导航网站

为数不多的人知道的 Kotlin 技巧及解析(三)相关推荐

  1. 为数不多的人知道的 Kotlin 技巧以及原理解析 | 开发者说·DTalk

    本文原作者: HiDhl,原文发布于: 掘金 https://juejin.im/post/6847902224467623950 文章中没有奇淫技巧,都是一些在实际开发中常用,但很容易被我们忽略的一 ...

  2. 为数不多的人知道的AndroidStudio快捷键(二)

    为数不多的人知道的AndroidStudio快捷键(二) 这篇文章将会继续分享一些实用的快捷键,没有看过为数不多的人知道的AndroidStudio快捷键(一),可以点击下方连接前往 为数不多的人知道 ...

  3. androidstudio自动生成变量_为数不多的人知道的AndroidStudio快捷键(一)

    好的idea能提高我们的工作效率,如果掌握一些快捷键,能帮我们从繁琐重复的工作解放出来,写起代码来效率就越高,接下来想分享一些实用的快捷键, 如果已经看完了这篇,点击下方连接前往为数不多的人知道的An ...

  4. 几个超级实用但很少人知道的 VS 技巧[更新]

    大家好,今天分享几个我知道的实用 VS 技巧,而这些技巧我发现很多人都不知道.因为我经常在工作中遇到:我在同事电脑上解决问题,或在会议上演示代码示例时,使用了一些 VS "骚"操作 ...

  5. [css] 举例说明你知道的css技巧有哪些?

    [css] 举例说明你知道的css技巧有哪些? /* 等比例容器 */ .ratio { position: relative; display: block; } .ratio:before { c ...

  6. android电视打开ppt,很少人知道的3种智能电视演示PPT的方法

    原标题:很少人知道的3种智能电视演示PPT的方法 演示PPT,通常是在投影上,但随着大屏智能电视的火热和普及,越来越多的办公室以大屏智能电视取代投影,成为办公室不可或缺的生产力.今天就以60吋酷开K6 ...

  7. kdj值应用口诀_极少人知道的“KDJ”波段指标,学好5个口诀,离股神很近了

    原标题:极少人知道的"KDJ"波段指标,学好5个口诀,离股神很近了 KDJ大家都知道,但是未必都深度了解和熟练应用,但是该指标在实战中成功率却名列前茅,而且更是做波段的强势神器.想 ...

  8. 8个老手都不一定知道的sketch技巧

    Sketch老手都不一定知道的sketch小技巧 001.拖动和删除样式 让我们从一个简单的技巧开始.如果要删除样式属性(如模糊或填充),只需从中删除即可"检查器"把面板拖到画板里 ...

  9. c 语言boll型变量,极少人知道的BOLL指标使用技巧 一旦学会终身受益!

    很多炒股的朋友都喜欢研究各种各样的指标和战法,殊不知研究多了,自己会乱,长远的投资,只要真正的掌握一个指标和战法,用以极致,那么也是能够稳定获利的,我知道很多老股民朋友喜欢用BOLL这个指标来帮助自己 ...

最新文章

  1. 威纶通宏开机后使用初始化宏指令_【操作系统】我们按下电脑开机键的背后发生了什么?...
  2. 了解你所不知道的SMON功能(十二):Shrink UNDO(rollback) SEGMENT
  3. 基于主成分分析与支持向量机的人脸识别
  4. SAP Fiori应用里日期格式的显示奥秘
  5. python3多线程编程_Python 3-多线程编程
  6. CGGeometry.h详解
  7. python3.6安装步骤-Ubuntu16.04安装python3.6详细教程
  8. MacOS:Shell工具-Royal TSX
  9. 怎样固定计算机桌面背景,Win7桌面背景老是被修改如何将其锁定不让他人随意修改...
  10. div 空隙_尖叫到建筑的空隙
  11. ie 打开html文件 慢,win7系统使用ie浏览器访问网页显示缓慢、卡死的解决方法
  12. 【前端实战项目】手把手教你做出小米商城官网(HTML+CSS)
  13. 【开始报名】第二届中国移动“梧桐杯”大数据应用创新大赛邀你夺52w大奖
  14. 如何清除windowsoffice KMS激活
  15. DNS是什么意思有什么作用了
  16. 关于数据挖掘的something
  17. Day03-《Guide to Maritime Informatics》-Part I Maritime Data 1-1.2
  18. 华为手机升级回退_华为荣耀手机系统回退
  19. LAN 局域网通讯软件 中期报告
  20. No resource found that matches the given name ‘android.TextAppearance.Materia...

热门文章

  1. angular5+模块懒加载
  2. html5 游戏 黑屏,《战地5》游戏黑屏无限加载怎么解决 黑屏解决办法
  3. Mac下QQ表情管理
  4. html文件被当毒杀掉了,文件被AVAST误杀怎么办
  5. 网络语言为你打c,2018十大网络流行语横空出世,你知道几个?你out了吗?
  6. 论文阅读:Automatic Cobb Angle Detection using Vertebra Detector and Vertebra Corners Regression
  7. css遇到的坑(一)
  8. EHS — 学习笔记的开篇问题
  9. easy connect电脑版_mysql 5.7解压版安装教程
  10. IOS6 IOS7 Mapkit draw Rout(地图划线)