在 Android 开发中使用常见的 Kotlin 模式

在 Android 开发中使用常见的 Kotlin 模式

介绍 Kotlin 语言在 Android 开发过程中最有用的一些方面

使用 Fragment
继承

LoginFragmentFragment 的子类,在子类与其父类之间使用 : 运算符指示继承:

class LoginFragment : Fragment()

LoginFragment 中,您可以替换许多生命周期回调以响应 Fragment 中的状态变化。如需替换函数,请使用 override 关键字,如以下示例所示:

override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {
    return inflater.inflate(R.layout.login_fragment, container, false)
}

如需引用父类中的函数,请使用 super 关键字,如以下示例所示:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
}
可为 null 性和初始化

在前面的示例中,被替换的方法中某些参数的类型以问号 ? 为后缀。这表示为这些参数传递的实际参数可以为 null。请务必安全地处理其可为 null 性

在 Kotlin 中,您必须在声明对象时初始化对象的属性。这意味着,当您获取类的实例时,可以立即引用它的任何可访问属性。不过,在调用 Fragment#onCreateView 之前,Fragment 中的 View 对象尚未准备好进行扩充,所以您需要一种方法来推迟 View 的属性初始化

可以使用 lateinit 推迟属性初始化。使用 lateinit 时,您应尽快初始化属性

如何使用 lateinitonViewCreated 中分配 View 对象:

private lateinit var usernameEditText: EditText
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        usernameEditText = view.findViewById(R.id.username_edit_text)       
    }

注意:如果您在初始化属性之前对其进行访问,Kotlin 会抛出 UninitializedPropertyAccessException

SAM转换(Single Abstract Method)

Button 对象包含一个 setOnClickListener() 函数,该函数接受 OnClickListener 的实现

OnClickListener 具有单一抽象方法 onClick(),您必须实现该方法。因为 setOnClickListener() 始终将 OnClickListener 当作参数,又因为 OnClickListener 始终都有相同的单一抽象方法,所以此实现在 Kotlin 中可以使用匿名函数来表示。此过程称为单一抽象方法转换或 SAM 转换,SAM 转换可使代码明显变得更简洁。以下示例展示了如何使用 SAM 转换来为 Button 实现 OnClickListener

binding.loginButton.setOnClickListener {
    Toast.makeText(activity, "click me", Toast.LENGTH_LONG).show()
}

当用户点击 loginButton 时,系统会执行传递给 setOnClickListener() 的匿名函数中的代码。

伴生对象

伴生对象提供了一种机制,用于定义在概念上与某个类型相关但不与某个特定对象关联的变量或函数。伴生对象类似于对变量和方法使用 Java 的 static 关键字

在以下示例中,TAG 是一个 String 常量。您不需要为每个 LoginFragment 实例定义一个唯一的 String 实例,因此您应在伴生对象中定义它:

class LoginFragment : Fragment() {

    ...

    companion object {
        private const val TAG = "LoginFragment"
    }
}

您可以在文件的顶级定义 TAG,但文件中可能有大量的变量、函数和类也是在顶级定义的。伴生对象有助于连接变量、函数和类定义,而无需引用该类的任何特定实例

每个类仅允许存在一个伴生对象

属性委托

初始化属性时,您可能会重复 Android 的一些比较常见的模式,例如在 Fragment 中访问 ViewModel。为避免过多的重复代码,您可以使用 Kotlin 的属性委托语法。

private val viewModel: LoginViewModel by viewModels()

属性委托提供了一种可在您的整个应用中重复使用的通用实现。Android KTX 为您提供了一些属性委托。例如,viewModels 可检索范围限定为当前 FragmentViewModel

属性委托使用反射,这样会增加一些性能开销。这种代价换来的是简洁的语法,可让您节省开发时间

可为 null 性

Kotlin 提供了严格的可为 null 性规则,可在您的整个应用中维护类型安全。在 Kotlin 中,默认情况下,对对象的引用不能包含 null 值。如需为变量赋 null 值,必须通过将 ? 添加到基本类型的末尾以声明可为 null 变量类型

name 的类型为 String,不可为 null:

val name: String = null

如需允许 null 值,必须使用可为 null String 类型 String?,如以下示例所示:

val name: String? = null
互操作性
平台类型

如果您使用 Kotlin 引用在 Java Account 类中定义的不带注解的 name 成员,编译器将不知道 String 映射到 Kotlin 中的 String 还是 String?。这种不明确性通过平台类型 String! 表示。

String! 对 Kotlin 编译器而言没有特殊的含义。String! 可以表示 StringString?,编译器可让您赋予任一类型的值。请注意,如果您将类型表示为 String 并赋予 null 值,则系统可能会抛出 NullPointerException

为了解决此问题,每当您用 Java 编写代码时,都应使用可为 null 性注解。这些注解对 Java 和 Kotlin 开发者都有帮助。

下面是在 Java 中定义的 Account 类:

public class Account implements Parcelable {
    public final String name;
    public final String type;
    private final @Nullable String accessId;

    ...
}

其中一个成员变量 accessId 带有 @Nullable 注解,这表示它可以持有 null 值。于是,Kotlin 会将 accessId 视为 String?

如需指明变量绝不能为 null,请使用 @NonNull 注解:

public class Account implements Parcelable {  public final @NonNull String name;  ...}

在这种情况下,name 在 Kotlin 中被视为不可为 null String

可为 null 性注解包含在所有新增的 Android API 以及许多现有的 Android API 中。许多 Java 库已添加可为 null 性注解,以便为 Kotlin 和 Java 开发者提供更好的支持

处理可为 null 性

如果您不确定 Java 类型,则应将其视为可为 null。例如,Account 类的 name 成员不带注解,因此您应假定它是一个可为 null String

如果 name 以使其值不包含尾随空格,则可以使用 Kotlin 的 trim 函数

如何安全的处理String?

使用非 null 断言运算符 !!
val account = Account("name", "type")
val accountName = account.name!!.trim()

!! 运算符将其左侧的所有内容视为非 null,因此,在本例中,应将 name 视为非 null String如果它左侧表达式的结果为 null,则您的应用会抛出 NullPointerException。此运算符简单快捷,但应谨慎使用,因为它会将 NullPointerException 的实例重新引入您的代码

有没有更安全的呢?

使用安全调用运算符 ?.
val account = Account("name", "type")
val accountName = account.name?.trim()

使用安全调用运算符时,如果 name 不为 null,则 name?.trim() 的结果是一个不带前导或尾随空格的名称值。如果 name 为 null,则 name?.trim() 的结果为 null。这意味着,在执行此语句时,您的应用永远不会抛出 NullPointerException

虽然安全调用运算符可使您避免潜在的 NullPointerException,但它会将 null 值传递给下一个语句。您可以使用 Elvis 运算符 (?:) 紧接着处理 null 值的情况,如以下示例所示:

val account = Account("name", "type")
val accountName = account.name?.trim() ?: "Default name"

如果 Elvis 运算符左侧表达式的结果为 null,则会将右侧的值赋予 accountName。此方法对于提供本来为 null 的默认值很有用

还可以使用 Elvis 运算符提前从函数返回结果,如以下示例所示:

fun validateAccount(account: Account?) {
    val accountName = account?.name?.trim() ?: "Default name"

    // account cannot be null beyond this point
    account ?: return

    ...
}
Android API 变更

由于从 Fragment#getContext 返回的 Context 可为 null(并且带有 @Nullable 注释),因此您必须在 Kotlin 代码中将其视为 Context?。这意味着,在访问其属性和函数之前,需要应用前面提到的某个运算符来处理可为 null 性问题。对于一些这样的情况,Android 包含可提供这种便利的替代 API。例如,Fragment#requireContext 会返回非 null Context,如果在 Context 将为 null 时调用它,则会抛出 IllegalStateException。这样,您就可以将生成的 Context 视为非 null 值,而无需使用安全调用运算符或其他解决方法

属性初始化

默认情况下,Kotlin 中的属性并未初始化。当初始化属性的封闭类时,必须初始化属性。

如何通过在类声明中为 index 变量赋值初始化该变量:

class LoginFragment : Fragment() {
    val index: Int = 12
}
class LoginFragment : Fragment() {
    val index: Int
    init {
        index = 12
    }
}

上面的示例中,在构建 LoginFragment 时初始化 index

不过,某些属性可能无法在对象构建期间进行初始化。例如,您可能要从 Fragment 中引用 View,这意味着,必须先扩充布局。构建 Fragment 时不会发生扩充,而是在调用 Fragment#onCreateView 时进行扩充。

应对这种情况的一种方法是将视图声明为可为 null 并尽快对其进行初始化,如以下示例所示:

class LoginFragment : Fragment() {
    private var statusTextView: TextView? = null

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView?.setText(R.string.auth_failed)
    }
}

当然可以使用lateinit关键字,可以避免在构建对象时初始化属性。

class LoginFragment : Fragment() {
    private lateinit var statusTextView: TextView
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            statusTextView = view.findViewById(R.id.status_text_view)
            statusTextView.setText(R.string.auth_failed)
    }
}

如果在属性进行初始化之前对其进行了引用,Kotlin 会抛出 UninitializedPropertyAccessException,因此请务必尽快初始化属性

注意:诸如数据绑定之类的视图绑定解决方案可以消除对 findViewById 的手动调用,这些解决方案有助于减少您需要考虑的 null 安全问题数量

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值