kotlin---使用注释处理的 Android 框架

2023-05-16

在日常 Android 开发中,流行着数以千计的框架帮助我们提升开发效率。 使用 Kotlin 开发时仍然可以沿用这些框架,而且和使用 Java 同样简单。 本章教程将提供相关示例并重点介绍配置的差异。

教程以 Dagger、 Butterknife、 Data Binding、 Auto-parcel 以及 DBFlow 为例(其它框架配置基本类似)。 以上框架均基于注解处理方式工作:通过对代码注解自动生成模板代码。 注解有助于减少冗余代码,让代码清晰可读,想要了解运行时的代码,可以直接阅读自动生成的源代码。 但所有生成的代码均为 Java 代码而非 Kotlin。

在 Kotlin 中添加依赖与 Java 中类似,仅需要使用 Kotlin 注解处理工具(Kotlin Annotation processing tool)(kapt)替代 annotationProcessor 即可。

Dagger

Dagger 是著名的依赖注入框架。 如果对它还不了解,可以查阅用户手册。 我们已经将整个咖啡示例 使用 Kotlin 重写,详细代码在这里。 Kotlin 代码与 Java 非常相似;所有示例代码可在同一个文件内查看。

与 Java 一样,Dagger 通过 @Inject 对构造函数注解,进而创建类的实例。 而 Kotlin 使用更简洁的语法同时声明属性和构造函数参数。 在 Kotlin 中对构造函数进行注解,必须显式使用 constructor 关键字,并在关键字前声明 @Inject

class Thermosiphon 
@Inject constructor(
        private val heater: Heater
) : Pump {
    // ……
}    

注解方法看上去完全相同。 在下面的示例中,@Binds 决定了无论何时需要 Pump,使用都是 Thermosiphon对象,@Provides 指定了 Heater 的构造方式,@Singleton 则表示 Heater 是全局单例:

@Module
abstract class PumpModule {
    @Binds
    abstract fun providePump(pump: Thermosiphon): Pump
}

@Module(includes = arrayOf(PumpModule::class))
class DripCoffeeModule {
    @Provides @Singleton
    fun provideHeater(): Heater = ElectricHeater()
}

@Module-注解的类定义如何提供不同对象。 需要注意的是,作为多参数传递注解参数时,需要显示的使用 arrayOf 进行包装,比如上文示例中的 @Module(includes = arrayOf(PumpModule::class))

使用 @Component 为类型生成依赖注入的实现。 自动生成类文件的类名带有 Dagger 前缀,比如下文示例 DaggerCoffeeShop

@Singleton
@Component(modules = arrayOf(DripCoffeeModule::class))
interface CoffeeShop {
    fun maker(): CoffeeMaker
}

fun main(args: Array<String>) {
    val coffee = DaggerCoffeeShop.builder().build()
    coffee.maker().brew()
}

Dagger 为 CoffeeShop 所生成的实现,允许你获得一个完全注入的 CoffeeMaker。 DaggerCoffeeShop的具体代码实现可在 IDE 中查看。

我们注意到转换到 Kotlin 时注解代码几乎没有发生改变。 接下来将介绍构建脚本(build script)中需要修改的部分。

在 Java 中需要指定 Dagger 作为 annotationProcessor(或 apt)依赖:

dependencies {
  ...
  annotationProcessor "com.google.dagger:dagger-compiler:$dagger-version"
}

在 Kotlin 中则需要添加 kotlin-kapt 插件激活 kapt,并使用 kapt 替换 annotationProcessor

apply plugin: 'kotlin-kapt'
dependencies {
    ...
    kapt "com.google.dagger:dagger-compiler:$dagger-version"
}

就是这样。 特别提示:kapt 也能够处理 Java 文件,所以不需要再保留 annotationProcessor 的依赖。

查看示例项目的完整构建脚本, 以及转换后的 Android 示例代码。

ButterKnife

ButterKnife可以直接将view和变量进行绑定从而免去调用findViewById

另外,Kotlin Android 扩展插件(Android Studio 内置)具有同样的效果:使用简洁明了的代码替换findViewByid。 除非现在你正在使用 ButterKnife 而且没有迁移计划,那么前者非常值得尝试。

在 Kotlin 中使用 ButterKnife 与 Java 中完全一致。 在 Gradle 构建脚本的修改如下,后面将重点介绍代码部分的差异。

在 Gradle 依赖中添加 kotlin-kapt 插件,并使用 kapt 替代 annotationProcessor

apply plugin: 'kotlin-kapt'

dependencies {
    ...
    compile "com.jakewharton:butterknife:$butterknife-version"
    kapt "com.jakewharton:butterknife-compiler:$butterknife-version"
}

我们已经将整个 ButterKnife 示例代码转换为 Kotlin, 参见详细代码。

让我门看看发生了什么变化。 在 Java 中使用注解对将变量与之对应的 view 进行绑定:

@BindView(R2.id.title) TextView title;

在 Kotlin 中使用属性而不是直接使用变量。 对属性使用注解:

@BindView(R2.id.title)
lateinit var title: TextView

@BindView 被定义为仅应用于变量字段,而将注解应用于整个属性时,Kotlin 编译器能够理解并且覆盖相应注解的字段。

lateinit 修饰符允许声明非空类型,并在对象创建后(构造函数调用后)初始化。 不使用 lateinit 则需要声明可空类型并且有额外的空安全检测操作。

使用 ButterKnife 注解可以将方法设置为监听器:

@OnClick(R2.id.hello)
internal fun sayHello() {
    Toast.makeText(this, "Hello, views!", LENGTH_SHORT).show()
}

以上代码表示点击“hello”按钮后的事件响应。 然而在 Kotlin 中使用 lambda 表达式会让代码更加简洁清晰:

hello.setOnClickListener {
    toast("Hello, views!")
}

Anko 库默认提供 toast 函数。

Data Binding

使用 Data Binding 开源库能够让开发者以更简洁的方式将应用程序数据与布局界面进行绑定。

和使用 Java 一样,开发者需要在 gradle 文件中添加并激活配置。

android {
    ...
    dataBinding {
        enabled = true
    }
}

添加 kapt 的依赖后即可与 Kotlin 代码交互:

apply plugin: 'kotlin-kapt'
dependencies {
    kapt "com.android.databinding:compiler:$android_plugin_version"
}  

使用 Kotlin 并不需要修改任何的 xml 文件。 例如,在 data 中使用 variable 来描述可能在布局中使用的变量, 可以使用Kotlin类型声明变量:

<data>
    <variable name="data" type="org.example.kotlin.databinding.WeatherData"/>
</data>

现在,可以使用 @{} 语法引用 Kotlin 的属性:

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@{data.imageUrl}"
    android:contentDescription="@string/image" />

值得一提的是,数据绑定表达式语言使用和 Kotlin 相同的语法对属性进行引用:data.imageUrl。 在 Kotlin 中可以使用 v.prop 来替代 v.getProp(),尽管 getProp() 是Java中的方法。 类似的,也可以直接向属性赋值,而不再需要调用setter。

class MainActivity : AppCompatActivity() {
    // ……
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding: ActivityMainBinding =
                DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.data = weather
        // 等同于
        // binding.setData(weather)
    }
}

在 xml 中绑定监听器,并在运行事对相应操作进行响应:

<Button
    android:text="@string/next"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:onClick="startOtherActivity" />

例如在 MainActivity 中定义的 startOtherActivity 方法:

class MainActivity : AppCompatActivity() {
    // ……
    fun startOtherActivity(view: View) = startActivity<OtherActivity>()
}

本例中使用的效用函数 startActivity 创建一个不带任何数据参数的 intent,并启动一个新的 activity,这些方法都来自于 Anko 库。 若需要添加参数,则调用 startActivity<OtherActivity>("KEY" to "VALUE").

请注意,与其在 xml 中声明 lambda 表达式,不如直接使用代码绑定相关动作:

<Button 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content"
    android:onClick="@{() -> presenter.onSaveClick(task)}" />
// 用 Kotlin 代码写的相同逻辑
button.setOnClickListener { presenter.onSaveClick(task) }

最后一行中 button 由 id 使用 Kotlin Android 扩展插件所引用。 使用该插件作为替代方案,既允许在代码中保持绑定逻辑,同时又具有简洁的语法。

查看完整示例。

DBFlow

DBFlow 是一个用于简化数据库交互的SQLite开源库。 它非常之依赖于注解处理。

使用 kapt 配置 Kotlin 依赖:

apply plugin: 'kotlin-kapt'

dependencies {
    kapt "com.github.raizlabs.dbflow:dbflow-processor:$dbflow_version"
    compile "com.github.raizlabs.dbflow:dbflow-core:$dbflow_version"
    compile "com.github.raizlabs.dbflow:dbflow:$dbflow_version"
}

查看 DBFlow 配置向导。

若您的项目中已在使用 DBFlow,可以安全地将在项目中引入 Kotlin。 并且逐步地将代码转换为 Kotlin(确保每次编译通过)。 转换后的代码与 Java 并无明显差异。 例如,对表的声明和在 Java 中仅有小小的区别,属性声明时必须显示的指定默认值:

@Table(name="users", database = AppDatabase::class)
class User: BaseModel() {

    @PrimaryKey(autoincrement = true)
    @Column(name = "id")
    var id: Long = 0

    @Column
    var name: String? = null
}

对于 DBFlow 而言,除了将已经有功能代码转换为 Kotlin,还能享受到 Kotlin 的特别支持。 例如,将表声明为数据类:

@Table(database = KotlinDatabase::class)
data class User(@PrimaryKey var id: Long = 0, @Column var name: String? = null)

DBFlow 定义了一系列符合 Kotlin 语言习惯的扩展功能,这些都可以通过依赖添加:

dependencies {
    compile "com.github.raizlabs.dbflow:dbflow-kotlinextensions:$dbflow_version"
}

该扩展可以通过类似 C# 中的 LINQ 语法方式编写查询语句,使用 lambda 表达式可以编写更简单的异步计算代码。 详见此处。

查看完整示例程序。

Auto-Parcel

Auto-Parcel 使用 @AutoValue 的注解为类文件自动生成 Parcelable 对应方法和值。

同样的,gradle 文件中也需要使用 kapt 作为注解处理器来处理 Kotlin 文件:

apply plugin: 'kotlin-kapt'

dependencies {
    ...
    kapt "frankiesardo:auto-parcel:$latest-version"
}

点击这里查看完整示例代码。

对 Kotlin 类文件添加 @AutoValue 注解。 下方的示例展示转换后的 Address 类以及自动生成相应的 Parceable 实现:

@AutoValue
abstract class Address : Parcelable {
    abstract fun coordinates(): DoubleArray
    abstract fun cityName(): String

    companion object {
        fun create(coordinates: DoubleArray, cityName: String): Address {
            return builder().coordinates(coordinates).cityName(cityName).build()
        }
        
        fun builder(): Builder = `$AutoValue_Address`.Builder()
    }
    
    @AutoValue.Builder
    interface Builder {
        fun coordinates(x: DoubleArray): Builder
        fun cityName(x: String): Builder
        fun build(): Address
    }
}

由于 Kotlin 中没有 static 方法,因此相应的方法会在 companion object中生成。 如果仍然需要从 Java 中调用这些方法,需要添加@JvmStatic注解。

如果调用 Java 的类或方法恰好在 Kotlin 中是保留字,可以使用反引号(`)作为转义字符,比如调用上例中生成类的`$AutoValue_Address`。

以上所有经过转换的代码与原生 Java 代码非常相似。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

kotlin---使用注释处理的 Android 框架 的相关文章

  • 文件递归搜索

    我试图在根目录及其子目录中查找文件 步骤1 在指定路径中查找目录 步骤2 如果找到上述目录 则在其子目录之一中查找文件 为此 我使用下面的代码片段进行递归搜索 现在 这里的问题是 当它满足我的上述要求时 如何突破递归 boolean bFi
  • 未找到 Google 地图 api v2 类

    我正在使用谷歌地图 api v2 一切正常 今天早上我更新了 sdk 现在地图无法工作 尝试了很多事情 例如再次导入 lib 项目 但似乎没有任何效果 请帮忙 这是 logcat 输出 05 16 08 53 34 327 E dalvik
  • 删除SD卡上的文件夹

    I tried File delete 但它不起作用 如何删除SD卡上的目录 我正在开发 Android 2 1 在删除目录本身之前 您必须将所有目录清空 请参阅here http www rgagnon com javadetails j
  • 针对 Android 开发优化 Eclipse

    我使用 Eclipse 和 ADT 插件开发 Android 而且速度 很慢 我必须经常重新启动 当我打开各种 Android 项目 当我使用库项目时需要 时 情况会变得更糟 使用 ADT 插件时 是否可以进行任何具体优化来提高 Eclip
  • 您如何在 Android 上处理超高 MP 相机(和图像)? - “画布:尝试绘制太大的位图”

    我有一个活动 用户可以像这样打开相机 getPictureUri createImageFromFile true let photoUri it openCameraActivity REQUEST IMAGE CAPTURE it ph
  • Android - 如何设置所有屏幕的背景颜色?

    维护字体和颜色样式的最佳实践是什么 我制作了一个 color xml 文件 用于更改按钮等单独元素的颜色 但我不确定 Android 希望开发人员如何组织他们的样式 例如 我希望所有屏幕都具有相同的背景颜色 我怎么做 我需要为每个 Acti
  • Android Fragment 中的 SharedPreferences

    我正在尝试读取 Fragment 内的 SharedPreferences 我的代码用于获取任何其他活动中的首选项 SharedPreferences preferences getSharedPreferences pref 0 我收到错
  • 如何实现可运行队列

    我正在尝试实现一个可运行队列 在异步任务期间依次执行 意味着队列中的下一个将在另一个完成后执行 我编写了一个管理器来管理这些可运行对象和本身就是可运行对象的任务 然后 我获取异步任务中的第一个任务并运行它 希望它能够在队列中运行 但是它最终
  • Android 上 WebRTC 的自定义视频源

    Overview 我想使用自定义视频源通过 WebRTC Android 实现来直播视频 如果我理解正确的话 现有的实现仅支持 Android 手机上的前置和后置摄像头 以下类与此场景相关 Camera1Enumerator java ht
  • Android“权限拒绝:无法使用相机”

    我正在学习有关在 Android 应用程序中使用相机的教程 我收到错误 权限被拒绝 无法使用相机 在模拟器和物理设备上运行调试时 我在清单文件中尝试了各种权限 似乎大多数遇到此错误的人都遇到了拼写错误 缺少权限或权限不在清单中的正确位置 这
  • 如何以编程方式设置 ConstraintLayout 的 XML 属性“layout_constrainedWidth”?

    ConstraintLayout中 如何转换xml属性 app layout constrainedWidth true false in code 如果你想设置constrainedWidth Height以编程方式 那么你必须采取Con
  • 即时应用程序上的文本转语音崩溃

    我正在实现一个即时应用程序 该应用程序利用 Android 设备上提供的文本转语音功能 我已经设法得到了TextToSpeech按照 Android 开发博客文章中详细说明的说明 实例已初始化并可在非即时应用程序中正常工作Android 中
  • Android中从一个应用程序向另一个应用程序发送数据时的加密

    我想将敏感数据从一个应用程序发送到另一个应用程序 我使用 Intent 并通过 Bundle 发送数据 现在 我应该使用加密算法来加密要发送的数据 同时接收器应用程序将解密数据 哪种算法最适合移动平台 我浏览过RSA文档 建议不建议用于长文
  • 从我自己的网站而不是市场安装(和更新)Android应用程序[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我可以在自己的网站上发布 Android 应用程序 而不使用 Android 市场 该应用程序与我们的互联网软件服务一起使用 因此仅符
  • 为什么我的字体大小在 android webview 对象中看起来比在 android 浏览器中查看时大得多?

    我正在尝试制作一个小型 Android 应用程序 它除了在 webview 对象而不是浏览中显示网站之外什么也不做 到目前为止它加载了目标网页 但文本和图像大小都比查看页面时大得多在实际设备浏览器中 在浏览器中 页面看起来正确 但在我的应用
  • Android Studio:src/androidTest 和 src/main 文件夹之间的区别?

    我是 Android Studio 的新手 我的问题是 src androidTest 和 src main 文件夹有什么区别 我所有的课程应该放在哪里 Refer Android Studio 概述 http developer andr
  • Visual Studio代码无法检测到模拟器设备或连接的电话

    I was running my app with vscode using Android emulator or my phone however all of a sudden vscode could not identify an
  • 在片段视图之间切换

    在 xml 布局文件中声明片段的标准方法是
  • Flutter 中 Android RecyclerView.SCROLL STATE IDLE 的等价物是什么

    Android 给出的滚动状态如下RecyclerView SCROLL STATE IDLE它告诉用户何时停止滚动 我找不到任何选择在颤动中Pageview or ListView滚动监听器 我的问题 我需要检测 PageView 中的向
  • PinnedHeaderListView 滚动和标题问题

    背景 我正在尝试模仿 Lollipop 的联系人应用程序显示联系人首字母的固定标题的方式 正如我所写的here https stackoverflow com q 27621425 878126 问题 由于原始代码 发现here http

随机推荐