【譯】使用Kotlin從零開始寫一個現代Android 項目-Part1

做者 | Mladen Rakoajc 譯者 | 依然範特稀西 編輯 | 依然範特稀西php

原文地址:proandroiddev.com/modern-andr…html

前言

常常在medium.com上看到一些高質量的技術帖子,可是因爲國內的上網環境或者有的同窗對於看英文比較排斥,錯過了很多好文章。所以,西哥決定弄一個《優質譯文專欄》,花一些時間翻譯一些優質技術文給你們。這篇文章是一個小系列,用Kotlin開發現代Android APP,總共四篇,後面的會陸續翻譯!如下是正文。java

如今,真的很難找到一個涵蓋全部Android新技術的項目,所以我決定本身來寫一個,在本文中,咱們將用到以下技術:android

  • 0 、Android Studio
  • 一、Kotlin 語言
  • 二、構建變體
  • 三、ConstraintLayout
  • 四、DataBinding庫
  • 五、MVVM+repository+Android Manager架構模式
  • 六、RxJava2及其對架構的幫助
  • 七、Dagger 2.11,什麼是依賴注入?爲何要使用它?
  • 八、Retrofit + RxJava2 實現網絡請求
  • 九、RooM + RxJava2 實現儲存
咱們的APP最終是什麼樣子?

咱們的APP是一個很是簡單的應用程序,它涵蓋了上面提到的全部技術。只有一個簡單的功能:從Github 獲取googlesamples用戶下的全部倉庫,將數據儲存到本地數據庫,而後在界面展現它。git

我將嘗試解釋更多的代碼,你也能夠看看你Github上的代碼提交。github

Github:github.com/mladenrakon…面試

讓咱們開始吧。算法

0、Android Studio

首先安卓Android Studio 3 beta 1(注:如今最新版爲Android Studio 4.0),Android Studio 已經支持Kotlin,去到Create Android Project界面,你將在此處看到新的內容:帶有標籤的複選框include Kotlin support。默認狀況下選中。按兩次下一步,而後選擇EmptyActivity,而後完成了。 恭喜!你用Kotlin開發了第一個Android app)數據庫

一、Kotlin

在剛纔新建的項目中,你能夠看到一個MainActivity.kt:api

package me.mladenrakonjac.modernandroidapp

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}
複製代碼

.kt後綴表明了這是一個Kotlin文件

MainActivity : AppCompatActivity()表示咱們的MainActivity繼承自AppCompatActivity

此外,全部的方法都必須有一個關鍵字fun,在Kotlin 中,你不能使用@override註解,若是你要代表方法是複寫父類或者接口的方法的話,直接使用override關鍵字,注意:它和Java不同,不是一個註解了。

而後,savedInstanceState: Bundle? 中的?表明什麼呢?它表明了savedInstanceState這個參數能夠是Bundle或者null。Kotlin是一門null 安全語言,若是你像下面這樣寫:

var a : String
複製代碼

你將會獲得一個編譯錯誤。由於a變量必須被初始化,而且不能爲null,所以你要像這樣寫:

var a : String = "Init value"
複製代碼

而且,若是你執行如下操做,也會報編譯錯誤:

a = null
複製代碼

要想使a變量爲null ,你必須這樣寫:

var a : String?
複製代碼

爲何這是Kotlin語言的一個重要功能呢?由於它幫咱們避免了NPE,Androd開發者已經對NPE感到厭倦了,甚至是null的發明者-Tony Hoare先生,也爲發明它而道歉。假設咱們有一個能夠爲空的nameTextView。若是爲null,如下代碼將會發生NPE:

nameTextView.setEnabled(true)
複製代碼

但實際上,Kotlin作得很好,它甚至不容許咱們作這樣的事情。它會強制咱們使用?或者!!操做符。若是咱們使用?操做符:

nameTextView?.setEnabled(true)
複製代碼

僅當nameTextView不爲null時,這行代碼纔會繼續執行。另外一種狀況下,若是咱們使用!!操做符:

nameTextView!!.setEnabled(true)
複製代碼

若是nameTextView爲null,它將爲咱們提供NPE。它只適合喜歡冒險的傢伙)

這是對Kotlin的一些介紹。咱們繼續進行,我將中止描述其餘Kotlin特定代碼。

二、構建變體

一般,在開發中,若是你有兩套環境,最多見的是測試環境和生產環境。這些環境在服務器URL圖標名稱目標api等方面可能有所不一樣。一般,在開始的每一個項目中我都有如下內容:

  • finalProduction: 上傳Google Play 使用
  • demoProduction:該版本使用生產環境服務器Url,而且它有着GP上的版本沒有的新功能,用戶能夠在Google play 旁邊安裝,而後能夠進行新功能測試和提供反饋。
  • demoTesting:和demoProduction同樣,只不過它用的是測試地址
  • mock: 對於我來講,做爲開發人員和設計師而言都是頗有用的。有時咱們已經準備好設計,而咱們的API仍未準備好。等待API準備就緒後再開始開發可不是好的解決方案。此構建變體爲提供有mock數據,所以設計團隊能夠對其進行測試並提供反饋。對於保證項目進度真的頗有幫助,一旦API準備就緒,咱們便將開發轉移到demoTesting環境。

在此應用程序中,咱們將擁有全部這些變體。它們的applicationId和名稱不一樣。 gradle 3.0.0 flavourDimension中有一個新的api,可以讓您混合不一樣的產品風味,所以您能夠混合demominApi23風味。在咱們的應用程序中,咱們將僅使用「默認」 的flavorDimension。早app的build.gradle中,將此代碼插入android {}下:

flavorDimensions "default"
    
productFlavors {

    finalProduction {
        dimension "default"
        applicationId "me.mladenrakonjac.modernandroidapp"
        resValue "string", "app_name", "Modern App"
    }

    demoProduction {
        dimension "default"
        applicationId "me.mladenrakonjac.modernandroidapp.demoproduction"
        resValue "string", "app_name", "Modern App Demo P"
    }

    demoTesting {
        dimension "default"
        applicationId "me.mladenrakonjac.modernandroidapp.demotesting"
        resValue "string", "app_name", "Modern App Demo T"
    }


    mock {
        dimension "default"
        applicationId "me.mladenrakonjac.modernandroidapp.mock"
        resValue "string", "app_name", "Modern App Mock"
    }
}
複製代碼

打開string.xml文件,刪掉app_namestring資源,所以,咱們纔不會發生資源衝突,而後點擊Sync Now,若是轉到屏幕左側的「構建變體」,則能夠看到4個不一樣的構建變體,其中每一個都有兩種構建類型:「Debug」和「Release」,切換到demoProduction構建變體並運行它。而後切換到另外一個並運行它。您就能夠看到兩個名稱不一樣的應用程序。

三、ConstraintLayout

若是你打開activity_main.xml,你能夠看到跟佈局是ConstraintLayout,若是你開發過iOS應用程序,你可能知道AutoLayoutConstraintLayout和它很是的類似,他們甚至用了相同的Cassowary算法。

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="me.mladenrakonjac.modernandroidapp.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
複製代碼

Constraints能夠幫咱們描述View之間的關係。對於每個View來講,應該有4個約束,每一邊一個約束,在這種狀況下,咱們的View就被約束在了父視圖的每一邊了。

在Design Tab中,若是你將Hello World文本稍微向上移動,則在TextTab中將增長下面這行代碼:

app:layout_constraintVertical_bias="0.28"
複製代碼

Design tab 和 Text tab是同步的,咱們在Design中移動視圖,則會影響Text中的xml,反之亦然。垂直誤差描述了視圖對其約束的垂直趨勢。若是要使視圖垂直居中,則應使用:

app:layout_constraintVertical_bias="0.28"
複製代碼

咱們讓Activity只顯示一個倉庫,它有倉庫的名字,star的數量,做者,而且還會顯示是否有issue

要獲得上面的佈局設計,代碼以下所示:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="me.mladenrakonjac.modernandroidapp.MainActivity">

    <TextView android:id="@+id/repository_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.083" tools:text="Modern Android app" />

    <TextView android:id="@+id/repository_has_issues" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:text="@string/has_issues" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="@+id/repository_name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toEndOf="@+id/repository_name" app:layout_constraintTop_toTopOf="@+id/repository_name" app:layout_constraintVertical_bias="1.0" />

    <TextView android:id="@+id/repository_owner" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/repository_name" app:layout_constraintVertical_bias="0.0" tools:text="Mladen Rakonjac" />

    <TextView android:id="@+id/number_of_starts" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/repository_owner" app:layout_constraintVertical_bias="0.0" tools:text="0 stars" />

</android.support.constraint.ConstraintLayout>

複製代碼

不要被tools:text搞迷惑了,它的做用僅僅是讓咱們能夠預覽咱們的佈局。

咱們能夠注意到,咱們的佈局是扁平的,沒有任何嵌套,你應該儘可能少的使用佈局嵌套,由於它會影響咱們的性能。ConstraintLayout也能夠在不一樣的屏幕尺寸下正常工做。

我有種預感,很快就能達到咱們想要的佈局效果了。

上面只是一些關於ConstraintLayout的少部分介紹,你也能夠看一下關於ConstraintLayout使用的google code lab: codelabs.developers.google.com/codelabs/co…

4. Data binding library

當我聽到Data binding 庫的時候,個人第一反應是:Butterknife已經很好了,再加上,我如今使用一個插件來從xml中獲取View,我爲啥要改變,來使用Data binding呢?但當我對Data binding有了更多的瞭解以後,個人它的感受就像我第一次見到Butterknife同樣,沒法自拔。

Butterknife能幫咱們作啥?

ButterKnife幫助咱們擺脫無聊的findViewById。所以,若是您有5個視圖,而沒有Butterknife,則你有5 + 5行代碼來綁定您的視圖。使用ButterKnife,您只有我行代碼就搞定。就是這樣。

Butterknife的缺點是什麼?

Butterknife仍然沒有解決代碼可維護問題,使用ButterKnife時,我常常發現本身遇到運行時異常,這是由於我刪除了xml中的視圖,而沒有刪除Activity/Fragment類中的綁定代碼。另外,若是要在xml中添加視圖,則必須再次進行綁定。真的很很差維護。你將浪費大量時間來維護View綁定。

那與之相比,Data Binding 怎麼樣呢?

有不少好處,使用Data Binding,你能夠只用一行代碼就搞定View的綁定,讓咱們看看它是如何工做的,首先,先將Data Binding 添加到項目:

// at the top of file 
apply plugin: 'kotlin-kapt'


android {
    //other things that we already used
    dataBinding.enabled = true
}
dependencies {
    //other dependencies that we used
    kapt "com.android.databinding:compiler:3.0.0-beta1"
}
複製代碼

請注意,數據綁定編譯器的版本與項目build.gradle文件中的gradle版本相同:

classpath 'com.android.tools.build:gradle:3.0.0-beta1'
複製代碼

而後,點擊Sync Now,打開activity_main.xml,將Constraint Layout用layout標籤包裹

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="me.mladenrakonjac.modernandroidapp.MainActivity">

        <TextView
            android:id="@+id/repository_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:layout_marginStart="16dp"
            android:textSize="20sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.083"
            tools:text="Modern Android app" />

        <TextView
            android:id="@+id/repository_has_issues"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="16dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            android:text="@string/has_issues"
            android:textStyle="bold"
            app:layout_constraintBottom_toBottomOf="@+id/repository_name"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintStart_toEndOf="@+id/repository_name"
            app:layout_constraintTop_toTopOf="@+id/repository_name"
            app:layout_constraintVertical_bias="1.0" />

        <TextView
            android:id="@+id/repository_owner"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="16dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/repository_name"
            app:layout_constraintVertical_bias="0.0"
            tools:text="Mladen Rakonjac" />

        <TextView
            android:id="@+id/number_of_starts"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="16dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/repository_owner"
            app:layout_constraintVertical_bias="0.0"
            tools:text="0 stars" />

    </android.support.constraint.ConstraintLayout>

</layout>

複製代碼

注意,你須要將全部的xml移動到layout 標籤下面,而後點擊Build圖標或者使用快捷鍵Cmd + F9,咱們須要構建項目來使Data Binding庫爲咱們生成ActivityMainBinding類,後面在MainActivity中將用到它。

若是沒有從新編譯項目,你是看不到ActivityMainBinding的,由於它在編譯時生成。

咱們尚未完成綁定,咱們只是定義了一個非空的 ActivityMainBinding 類型的變量。你會注意到我沒有把? 放在 ActivityMainBinding 的後面,並且也沒有初始化它。這怎麼可能呢?lateinit 關鍵字容許咱們使用非空的延遲被初始化的變量。和 ButterKnife 相似,在咱們的佈局準備完成後,初始化綁定須要在 onCreate 方法中進行。此外,你不該該在 onCreate 方法中聲明綁定,由於你頗有可能在 onCreate 方法外使用它。咱們的 binding 不能爲空,因此這就是咱們使用 lateinit 的緣由。使用 lateinit 修飾,咱們不須要在每次訪問它的時候檢查 binding 變量是否爲空。

咱們初始化binding變量,你須要替換:

setContentView(R.layout.activity_main)
複製代碼

爲:

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
複製代碼

就是這樣,你成功的綁定了全部View,如今你能夠訪問它而且作一些更改,例如,咱們將倉庫名字改成Modern Android Medium Article:

binding.repositoryName.text = "Modern Android Medium Article"
複製代碼

如你所見,如今咱們能夠經過bingding變量來訪問main_activity.xml的全部View了(前提是它們有id),這就是Data Binding 比ButterKnife 好用的緣由。

kotlin的 Getters 和 setters

大概,你已經注意到了,咱們沒有像Java那樣使用.setText(),我想在這裏暫停一下,以說明與Java相比,Kotlin中的getter和setter方法如何工做的。

首先,你須要知道,咱們爲何要使用getters和setters,咱們用它來隱藏類中的變量,僅容許使用方法來訪問這些變量,這樣咱們就能夠向用戶隱藏類中的細節,並禁止用戶直接修改咱們的類。假設咱們用 Java 寫了一個 Square 類:

public class Square {
  private int a;
  
  Square(){
    a = 1;
  }

  public void setA(int a){
    this.a = Math.abs(a);
  }
  
  public int getA(){
    return this.a;
  }
  
}
複製代碼

使用setA()方法,咱們禁止了用戶向Square類的a變量設置一個負數,由於正方形的邊長必定是正數,要使用這種方法,咱們必須將其設爲私有,所以不能直接設置它。這也意味着咱們不能直接得到a,須要給它定一個get方法來返回a,若是有10個變量,那麼咱們就得定義10個類似的get方法,寫這樣無聊的樣板代碼,一般會影響咱們的心情。

Kotling使咱們的開發人員更輕鬆了。若是你調用下面的代碼:

var side: Int = square.a
複製代碼

這並不意味着你是在直接訪問a變量,它和Java中調用getA()是相同的

int side = square.getA();
複製代碼

由於Kotlin自動生成默認的getter和setter。在Kotlin中,只有當您有特殊的setter或getter時,才應指定它。不然,Kotlin會爲您自動生成:

var a = 1
   set(value) { field = Math.abs(value) }
複製代碼

field ? 這又是個什麼東西?爲了更清楚明白,請看下面代碼:

var a = 1
   set(value) { a = Math.abs(value) }
複製代碼

這代表你在調用set方法中的set(value){},由於Kotlin的世界中,沒有直接訪問屬性,這就會形成無限遞歸,當你調用a = something,會自動調用set方法。使用filed就能避免無限遞歸,我但願這能讓你明白爲何要用filed關鍵字,而且瞭解getters和setters是如何工做的。

回到代碼中繼續,我將向你介紹Kotlin語言的另外一個重要功能:apply函數:

class MainActivity : AppCompatActivity() {

    lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
        binding.apply {
            repositoryName.text = "Medium Android Repository Article"
            repositoryOwner.text = "Mladen Rakonjac"
            numberOfStarts.text = "1000 stars"
            
        }
    }
}
複製代碼

apply 容許你在一個實例上調用多個方法,咱們仍然尚未完成數據綁定,還有更棒的事兒,讓咱們爲倉庫定義一個UI模型(這個是github倉庫的數據模型Repository,它持有要展現的數據,請不要和Repository模式的中的Repository搞混淆了哈),要建立一個Kotlin class,點擊New -> Kotlin File/Class :

class Repository(var repositoryName: String?,var repositoryOwner: String?,var numberOfStars: Int? ,var hasIssues: Boolean = false)
複製代碼

在Kotlin中,主構造函數是類頭的一部分,若是你不想定義次構造函數,那就是這樣了,數據類到此就完成了,構造函數沒有參數分配給字段,沒有setters和getters,整個類就一行代碼。

回到MainActivity.kt,爲Repository建立一個實例。

var repository = Repository("Medium Android Repository Article",
        "Mladen Rakonjac", 1000, true)
複製代碼

你應該注意到了,建立類實例,沒有用new

如今,咱們在activity_main.xml中添加data標籤。

<data>
      <variable
        name="repository"
        type="me.mladenrakonjac.modernandroidapp.uimodels.Repository"
        />
</data>
複製代碼

咱們能夠在佈局中訪問存儲的變量repository,例如,咱們能夠以下使用id是repository_name的TextView,以下:

android:text="@{repository.repositoryName}"
複製代碼

repository_name文本視圖將顯示從repository變量的屬性repositoryName獲取的文本。剩下的惟一事情就是將repository變量從xml綁定到MainActivity.kt中的repository。

點擊Build使DataBinding 爲咱們生成類,而後在MainActivity中添加兩行代碼:

binding.repository = repository
binding.executePendingBindings()
複製代碼

若是你運行APP,你會看到TextView上顯示的是:「Medium Android Repository Article」,很是棒的功能,是吧?

可是,若是咱們像下面這樣改一下呢?

Handler().postDelayed({repository.repositoryName="New Name"}, 2000)
複製代碼

新的文本將會在2000ms後顯示嗎?不會的,你必須從新設置一次repository,像這樣:

Handler().postDelayed({repository.repositoryName="New Name"
    binding.repository = repository
    binding.executePendingBindings()}, 2000)
複製代碼

可是,若是咱們每次更改一個屬性都要這麼寫的話,那就很是蛋疼了,這裏有一個更好的方案叫作Property Observer

讓咱們首先解釋一下什麼是觀察者模式,由於在rxJava部分中咱們也將須要它:

可能你已經據說過http://androidweekly.net/,這是一個關於Android開發的週刊。若是您想接收它,則必須訂閱它並提供您的電子郵件地址。過了一段時間,若是你不想看了,你能夠去網站上取消訂閱。

這就是一個觀察者/被觀察者的模式,在這個例子中, Android 週刊是被觀察者,它每週都會發布新聞通信。讀者是觀察者,由於他們訂閱了它,一旦訂閱就會收到數據,若是不想讀了,則能夠中止訂閱。

Property Observer在這個例子中就是 xml layout,它將會監聽Repository實例的變化。所以,Repository被觀察者,例如,一旦在Repository類的實例中更改了repository nane 屬性後,xml不調用下面的代碼也會更新:

binding.repository = repository
binding.executePendingBindings()
複製代碼

如何讓它使用Data Binding 庫呢?,Data Binding庫提供了一個BaseObservable類,咱們的Repostory類必須繼承它。

class Repository(repositoryName : String, var repositoryOwner: String?, var numberOfStars: Int?
                 , var hasIssues: Boolean = false) : BaseObservable(){

    @get:Bindable
    var repositoryName : String = ""
    set(value) {
        field = value
        notifyPropertyChanged(BR.repositoryName)
    }

}
複製代碼

當咱們使用了 Bindable 註解時,就會自動生成 BR 類。你會看到,一旦設置新值,就會通知它更新。如今運行 app 你將看到倉庫的名字在 2 秒後改變而沒必要再次調用 executePendingBindings()

以上就是這一節的全部內容,下一節將會講MVVM+Repository 模式的使用。敬請期待!感謝閱讀。

文章首發於公衆號:「 技術最TOP 」,天天都有乾貨文章持續更新,能夠微信搜索「 技術最TOP 」第一時間閱讀,回覆【思惟導圖】【面試】【簡歷】有我準備一些Android進階路線、面試指導和簡歷模板送給你

相關文章
相關標籤/搜索