您可以用class关键字在Kotlin中声明一个类。在下面的示例中,LoginFragment是Fragment的子类。您可以通过在子类与其父类之间使用 运算符来指示继承:

class LoginFragment : Fragment()
  • 在这个类声明中,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)
    

    在Kotlin中,您必须在声明对象时初始化对象的属性。这意味着当您获取类的实例时,您可以立即引用其任何可访问的属性。但是,Fragment中的view对象并不能在调用Fragment#OnCreateView之前被膨胀(inflate),因此需要一种方法来延迟视图的属性初始化。

    lateinit允许您推迟属性初始化。使用lateinit时,应尽快初始化属性。以下示例演示如何使用Lateinit在OnViewCreate中指定View对象

    class LoginFragment : Fragment() {
        private lateinit var usernameEditText: EditText
        private lateinit var passwordEditText: EditText
        private lateinit var loginButton: Button
        private lateinit var statusTextView: TextView
        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
            super.onViewCreated(view, savedInstanceState)
            usernameEditText = view.findViewById(R.id.username_edit_text)
            passwordEditText = view.findViewById(R.id.password_edit_text)
            loginButton = view.findViewById(R.id.login_button)
            statusTextView = view.findViewById(R.id.status_text_view)
    
  • 注意:如果在属性初始化之前访问它,Kotlin将抛出UninitializedPropertyAccessException。
  • SAM转换

  • 通过实现OnClickListener接口,您可以监听Android中的事件。Button对象包含实现OnClickListener的setOnClickListener()函数。
  • OnClickListener有一个单一的抽象方法,onclick(),您必须实现它。因为setOnClickListener()始终以OnClickListener为参数,并且因为OnClickListener总是具有相同的单个抽象方法onClick,所以可以使用Kotlin中的匿名函数来表示此实现。此过程称为单抽象方法转换(Single Abstract Method Conversion)
  • SAM转换可以使您的代码变得更干净。以下示例说明如何使用SAM转换为按钮实现OnClickListener:

    loginButton.setOnClickListener {
        val authSuccessful: Boolean = viewModel.authenticate(
                usernameEditText.text.toString(),
                passwordEditText.text.toString()
        if (authSuccessful) {
            // Navigate to next screen
        } else {
            statusTextView.text = requireContext().getString(R.string.auth_failed)
    
  • 传递到setOnClickListener()的匿名函数中的代码在用户单击LoginButton时执行
  • Companion objects

  • 伴随对象提供了一种机制,用于定义在概念上链接到类型但不与特定对象相关联的变量或函数。伴随对象类似于对变量和方法使用Java的static关键字。
  • 在下面的示例中,TAG是字符串常量。您不需要针对LoginFragment的每个实例都需要一个字符串的唯一实例,因此您应该在一个伴生对象中定义它:

    class LoginFragment : Fragment() {
        companion object {
            private const val TAG = "LoginFragment"
    

    您可以在文件的顶层定义标记,但文件也可能有大量的变量、函数和类,这些变量、函数和类也在顶层定义。伴随对象有助于连接变量、函数和类定义,而无需引用该类的任何特定实例。

    属性委托(Property delegation)

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

    private val viewModel: LoginViewModel by viewModels()
    

    属性委派提供了一个常见的实现方式,您可以在整个应用程序中重复使用。AndroidKTX为您提供了一些性能代表。例如,ViewModel检索范围为当前片段的ViewModel。

  • 属性委派使用反射,这增加了一些性能开销。折衷是节省开发时间的简明语法。
  • Nullability

  • Kotlin提供了严格的可空性规则,可以在整个应用程序中维护类型安全。在Kotlin中,对象的引用默认情况下不能包含空值。若要将空值赋值给变量,必须通过添加?到基类型的末尾。
  • 例如,以下表达式在Kotlin中是非法的。名称的类型为字符串,不可为空:

    val name: String = null
    

    若要允许空值,必须使用可空字符串类型String?,如以下示例所示:

    val name: String? = null
    

    Interoperability(互用性,协同工作的能力)

  • Kotlin的严格规则使您的代码更安全、更简洁。这些规则降低了导致应用程序崩溃的NullPointerException的可能性。此外,它们减少了您需要在代码中进行的空检查的数量。
  • 通常,在编写Android应用程序时,还必须调用非kotlin代码,因为大多数AndroidAPI都是用Java编程语言编写的。Nullability是Java和Kotlin行为不同的关键领域。Java对空性语法不那么严格。例如,Account类有几个属性,包括一个名为name的字符串属性。Java没有Kotlin关于空性的规则,而是依赖于可选的空注解来显式声明是否可以分配空值。因为Android框架主要是用Java编写的,所以在调用API而没有空注解时,您可能会遇到这种情况。
  • Platform types

  • 如果使用Kotlin引用在Java Account类中定义的未注解标注的名称成员,则编译器不知道字符串是否映射到String或字符串String?在Kotlin。此模糊是通过平台类型String!字符串来表示的。
  • String!对Kotlin编译器没有特殊意义。String!可以表示String或String?,编译器允许您指定任意类型的值。注意,如果将类型表示为String并指定一个空值,则可能引发NullPointerException。
  • 要解决此问题,无论何时在Java中编写代码,都应使用Nullability注释。这些注释帮助Java和Kotlin开发人员。
  • 例如,下面示例是用Java写的Account类

    public class Account implements Parcelable {
        public final String name;
        public final String type;
        private final @Nullable String accessId;
    
  • 成员变量(AccessId)中的一个用@Nullable来注释,表示它可以是空值。然后,Kotlin会把AccessId当作一个String?来处理
  • 若要指示变量永远不能为空,请使用@NonNulll注解:

    public class Account implements Parcelable {
        public final @NonNull String name;
    
  • 在这种情况下,name在kotlin中被认为是不可空的String。
  • 所有新的Android API和许多现有的Android API都包含Nullability注解。许多Java库都添加了空值注解,以更好地支持Kotlin和Java开发人员。

    处理空值(Handling nullability)

  • 如果您不确定Java类型,您应该认为它是可空的。例如,Account类的Name成员没有注解,因此您应该假设它是一个可空的String?。
  • 如果要trim name,以便其值不包括前导空格或尾随空格,则可以使用Kotlin的trim功能。您可以安全地trim String?以几种不同的方式。这些方法之一是使用NOT-NULL断言运算符,!!如下例所示:

    val account = Account("name", "type")
    val accountName = account.name!!.trim() 
    
  • !!运算符将其左侧的所有内容视为非空字符串,因此在本例中,您将名称视为非空字符串。如果左边的表达式的结果为NULL,那么应用程序将抛出一个NullPointerException。这个操作符简单快捷,但应该谨慎使用,因为它可以在代码中重新引入NullPointerException实例。
  • 一个更加安全的选择是使用 ?.运算符, as shown in the following example:

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

    使用安全调用运算符(如果name非空),则name?.trim()的结果是没有前导或尾随空格的名称值。如果name为空,则name?.trim()的结果为null。这意味着在执行此语句时,您的应用程序永远无法引发NullPointerException。

    在安全调用运算符将您从潜在的NullPointerException中保存出来时,它确实会将空值传递给下一条语句。您可以使用Elvis运算符(?:),如下例所示:

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

    如果猫王运算符左侧的表达式的结果为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 Changes

  • 安卓(Android)API正变得越来越复杂。许多Android的最常见的API,包括AppCompatActivity和Fragment都包含Nullability注释,并且一些类似Fragment#GetContext的调用有更多的Kotlin友好的替代方案。
  • 例如,访问Fragment的Context几乎总是是非null的,因为在Fragment attach到Activity(Context的子类)时,您在Fragment中进行的大多数调用都会发生。
  • 也就是说,Fragment#GetContext不总是返回非零值,因为存在Fragment未附着到Activity的情形。因此,Fragment#GetContext的返回类型为Nullable。
  • 由于从Fragment#getContext返回的上下文是Nullable(并且被注释为@Nullable),因此您必须将其视为Cotnext?在你的Kotlin代码中。这意味着在访问其属性和功能之前应用前面提到的运算符之一来解决Nullability。
  • 对于这些场景中的一些场景,Android包含提供此方便的替代API。例如,Fragment#requireContext返回非NULL context,并在context将为NULL时抛出IllegalStateException。这样,您可以将生成的context视为非null,而无需使用安全调用运算符或解决方法
  • Property initialization

    默认情况下,Kotlin中的属性不会初始化。它们必须在初始化其封闭类时初始化。您可以用几种不同的方式初始化属性。下面的示例演示如何通过在类声明中为索引变量赋值来初始化索引变量:

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

    还可以在初始化程序块中定义此初始化:

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

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

  • 但是,您可能有一些在对象构造过程中无法初始化的属性。例如,您可能希望从Fragment中引用View,这意味着布局必须首先加载(inflate)。当一个Fragment被构造时,通货inflate不会发生。相反,它是在调用片段#onCreateView时inflate的。
  • 解决此情况的一种方法是将View声明为可空并尽快初始化它,如以下示例所示:

    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进行View初始化,如以下示例所示:

    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)
    
  • lateinit关键字允许您在构造对象时避免初始化属性。如果在初始化之前引用了属性,Kotlin将抛出UninitializedPropertyAccessException,因此请确保尽快初始化属性。
  •