Android 學習筆記架構篇

架構原則

關注點分離

  • 一個組件應該只關注一個簡單的問題,只負責完成一項簡單的任務,應該盡少依賴其它組件
  • 就算依賴另外一個組件,也不能同時依賴它下下一級的組件,要像網絡協議分層同樣簡單明確
  • ActivityFragment 做爲操做系統和應用之間的粘合類,不該該將全部代碼寫在它們裏面,它們甚至能夠當作是有生命週期的普通 View,大部分狀況下就是 用來簡單 顯示數據的

模型驅動視圖

  • 爲了保證數據 model 和它對應顯示的 UI 始終是一致的,應該用 model 驅動 UI,並且最好是是持久化 model。model 是負責處理應用數據的組件,只關心數據

單一數據源

  • 爲了保證數據的一致性,必須實現相同的數據來自同一個數據源。如: 好友列表頁顯示了好友的備註名,數據來源於服務器的 api/friends 響應,好友詳情頁也顯示了好友的備註名,數據來源於服務器的 api/user 響應,此時在好友詳情頁更改了對這個好友的備註名,那麼好友列表並不知情,它的數據模型並無發生變化,因此仍是顯示原來的備註名,這就產生了數據不一致的問題
  • 要實現單一數據源(Single source of truth),最簡單的方式就是將本地數據庫做爲單一數據源,主鍵和外鍵的存在保證了數據對應實體的一致性

推薦架構

arch
Android Jetpack 組件庫中有一個叫 Architecture Components 的組件集,裏面包含了 Data Binding,Lifecycles,LiveData,Navigation,Paging,Room,ViewModel,WorkManager 等組件的實現

  • ViewModel 用來爲指定的 UI 組件提供數據,它只負責根據業務邏輯獲取合適的數據,他不知道 View 的存在,因此它不受系統銷燬重建的影響,通常它的生命週期比 View 更長久
  • LiveData 是一個數據持有者,它持有的數據能夠是任何 Object 對象。它相似於傳統觀察者模式中的 Observable,當它持有的數據發生變化時會通知它全部的 Observer。同時它還能夠感知 Activity,Fragment 和 Service 的生命週期,只通知它們中 active 的,在生命週期結束時自動取消訂閱
  • Activity/Fragment 持有 ViewModel 進行數據的渲染,ViewModel 持有 LiveData 形式的數據以便尊重應用組件的生命週期,可是獲取 LiveData 的具體實現應該由 Repository 完成
  • Repository 是數據的抽象,它提供簡潔一致的操做數據的 API,內部封裝好對持久化數據、緩存數據、後臺服務器數據等數據源數據的操做。因此 ViewModel 不關心數據具體是怎麼得到的,甚至能夠不關心數據究竟是從哪拿到的

實踐

基礎設施建設

建立項目時要勾選 【Use AndroidX artifacts】 複選框以便自動使用 AndroidX 支持庫,不然須要手動在 gradle.properties 文件中添加php

android.useAndroidX=true
android.enableJetifier=true
複製代碼

而後在項目根目錄建立 versions.gradle 文件,以便統一管理依賴和版本號java

ext.deps = [:]

def build_versions = [:]
build_versions.min_sdk = 14
build_versions.target_sdk = 28
ext.build_versions = build_versions

def versions = [:]
versions.android_gradle_plugin = "3.3.0"
versions.support = "1.1.0-alpha01"
versions.constraint_layout = "1.1.3"
versions.lifecycle = "2.0.0"
versions.room = "2.1.0-alpha04"
versions.retrofit = "2.5.0"
versions.okhttp = "3.12.1"
versions.junit = "4.12"
versions.espresso = "3.1.0-alpha4"
versions.atsl_runner = "1.1.0-alpha4"
versions.atsl_rules = "1.1.0-alpha4"

def deps = [:]

deps.android_gradle_plugin = "com.android.tools.build:gradle:$versions.android_gradle_plugin"

def support = [:]
support.app_compat = "androidx.appcompat:appcompat:$versions.support"
support.v4 = "androidx.legacy:legacy-support-v4:$versions.support"
support.constraint_layout = "androidx.constraintlayout:constraintlayout:$versions.constraint_layout"
support.recyclerview = "androidx.recyclerview:recyclerview:$versions.support"
support.cardview = "androidx.cardview:cardview:$versions.support"
support.design = "com.google.android.material:material:$versions.support"
deps.support = support

def lifecycle = [:]
lifecycle.runtime = "androidx.lifecycle:lifecycle-runtime:$versions.lifecycle"
lifecycle.extensions = "androidx.lifecycle:lifecycle-extensions:$versions.lifecycle"
lifecycle.java8 = "androidx.lifecycle:lifecycle-common-java8:$versions.lifecycle"
lifecycle.compiler = "androidx.lifecycle:lifecycle-compiler:$versions.lifecycle"
deps.lifecycle = lifecycle

def room = [:]
room.runtime = "androidx.room:room-runtime:$versions.room"
room.compiler = "androidx.room:room-compiler:$versions.room"
deps.room = room

def retrofit = [:]
retrofit.runtime = "com.squareup.retrofit2:retrofit:$versions.retrofit"
retrofit.gson = "com.squareup.retrofit2:converter-gson:$versions.retrofit"
deps.retrofit = retrofit

deps.okhttp_logging_interceptor = "com.squareup.okhttp3:logging-interceptor:${versions.okhttp}"

deps.junit = "junit:junit:$versions.junit"

def espresso = [:]
espresso.core = "androidx.test.espresso:espresso-core:$versions.espresso"
deps.espresso = espresso

def atsl = [:]
atsl.runner = "androidx.test:runner:$versions.atsl_runner"
deps.atsl = atsl

ext.deps = deps
複製代碼

以顯示 谷歌的開源倉庫列表api.github.com/users/googl…)爲例,先依賴好 ViewModelLiveDataRetrofit:android

apply plugin: 'com.android.application'

android {
    compileSdkVersion build_versions.target_sdk
    defaultConfig {
        applicationId "cn.frank.sample"
        minSdkVersion build_versions.min_sdk
        targetSdkVersion build_versions.target_sdk
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    lintOptions {
        abortOnError false
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation deps.support.app_compat
    implementation deps.support.constraint_layout

    implementation deps.lifecycle.runtime
    implementation deps.lifecycle.extensions
    annotationProcessor deps.lifecycle.compiler
    implementation deps.room.runtime
    annotationProcessor deps.room.compiler
    implementation deps.retrofit.runtime
    implementation deps.retrofit.gson
    implementation deps.okhttp_logging_interceptor

    testImplementation deps.junit
    androidTestImplementation deps.atsl.runner
    androidTestImplementation deps.espresso.core
}
複製代碼

而後根據習慣合理地設計源碼的目錄結構,如 git

public class RepoRepository {

    private static RepoRepository sInstance;

    public RepoRepository() {
    }

    public static RepoRepository getInstance() {
        if (sInstance == null) {
            synchronized (RepoRepository.class) {
                if (sInstance == null) {
                    sInstance = new RepoRepository();
                }
            }
        }
        return sInstance;
    }

    public LiveData<List<Repo>> getRepo(String userId) {
        final MutableLiveData<List<Repo>> data = new MutableLiveData<>();
        ServiceGenerator.createService(GithubService.class)
                .listRepos(userId)
                .enqueue(new Callback<List<Repo>>() {
                    @Override
                    public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) {
                        data.setValue(response.body());
                    }

                    @Override
                    public void onFailure(Call<List<Repo>> call, Throwable t) {

                    }
                });
        return data;
    }

}
複製代碼
public class RepoViewModel extends AndroidViewModel {

    private LiveData<List<Repo>> repo;
    private RepoRepository repoRepository;

    public RepoViewModel(@NonNull Application application) {
        super(application);
        this.repoRepository = ((SampleApp) application).getRepoRepository();
    }

    public void init(String userId) {
        if (this.repo != null) {
            return;
        }
        this.repo = repoRepository.getRepo(userId);
    }

    public LiveData<List<Repo>> getRepo() {
        return repo;
    }
}
複製代碼
public class RepoFragment extends Fragment {

    private static final String ARG_USER_ID = "user_id";

    private RepoViewModel viewModel;
    private TextView repoTextView;

    public RepoFragment() {

    }

    public static RepoFragment newInstance(String userId) {
        RepoFragment fragment = new RepoFragment();
        Bundle args = new Bundle();
        args.putString(ARG_USER_ID, userId);
        fragment.setArguments(args);
        return fragment;
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View rootView =  inflater.inflate(R.layout.fragment_repo, container, false);
        repoTextView = (TextView) rootView.findViewById(R.id.repo);
        return rootView;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        Bundle args = getArguments();
        if (args != null) {
            String userId = args.getString(ARG_USER_ID);
            viewModel = ViewModelProviders.of(this).get(RepoViewModel.class);
            viewModel.init(userId);
            viewModel.getRepo().observe(this, new Observer<List<Repo>>() {
                @Override
                public void onChanged(List<Repo> repos) {
                    StringBuilder builder = new StringBuilder();
                    if (repos != null) {
                        for (Repo repo : repos) {
                            builder.append(repo.getFull_name()).append("\n");
                        }
                    }
                    repoTextView.setText(builder);
                }
            });
        }
    }

}
複製代碼

這是最簡單直接的實現,但仍是存下不少模板代碼,還有不少地方能夠優化github

  1. 既然 View 是和 ViewModel 綁定在一塊兒的,那爲何每次都要先 findViewById()setText() 呢?在聲明或者建立 View 的時候就給它指定好對應的 ViewModel 不是更簡單直接麼
  2. ViewModel 的實現真的優雅嗎?init() 方法和 getRepo() 方法耦合的嚴重麼?ViewModel 應該在什麼時刻開始加載數據?
  3. 網絡請求的結果最好都緩存到內存和數據庫中,既保證了單一數據源原則又能提高用戶體驗

Data Binding

對於第一個問題,Data Binding 組件是一個還算不錯的實現,能夠在佈局文件中使用 表達式語言 直接給 View 綁定數據,綁定能夠是單向的也能夠是雙向的。Data Binding 這樣綁定能夠避免內存泄漏,由於它會自動取消綁定。能夠避免空指針,由於它會寬容評估表達式。能夠避免同步問題,能夠在後臺線程更改非集合數據模型,由於它會在評估時本地化數據
爲了使用 Data Binding,須要在 app module 的 build.gradle 文件中添加數據庫

dataBinding {
    enabled = true
}
複製代碼

利用 @{} 語法能夠給 View 的屬性綁定數據變量,可是該表達式語法應該儘量簡單直接,複雜的邏輯應該藉助於自定義 BindingAdapterapi

<layout xmlns:android="http://schemas.android.com/apk/res/android">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
       <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{user.firstName}"/>
   </LinearLayout>
</layout>
複製代碼

不須要從新編譯代碼,構建工具就會爲每一個這樣的佈局文件自動生成一個對應的綁定類,繼承自 ViewDataBinding,路徑爲 app/build/generated/data_binding_base_class_source_out/debug/dataBindingGenBaseClassesDebug/out/cn/frank/sample/databinding/FragmentRepoBinding.java,默認的類名是佈局文件名的大駝峯命名加上 Binding 後綴,如 fragment_repo.xml 對應 FragmentRepoBinding,能夠經過 <data class=".ContactItem"> 自定義類名和所在包名。能夠經過 DataBindingUtilinflate() 等靜態方法或自動生成的綁定類的 inflate() 等靜態方法獲取綁定類的實例,而後就能夠操做這個實例了數組

操做符和關鍵字

這個表達式語言的 操做符和關鍵字 包括: 數學運算 + - / * %,字符串拼接 +,邏輯 && ||,二進制運算 & | ^,一元操做符 + - ! ~,移位 >> >>> <<,比較 == > < >= <=,判斷實例 instanceof,分組 (),字符/字符串/數字/null 的字面量,強制轉化,方法調用,字段訪問,數組訪問 [],三目運算符 ?:,二目空缺省運算符 ??緩存

android:text="@{String.valueOf(index + 1)}"
android:visibility="@{age > 13 ? View.GONE : View.VISIBLE}"
android:transitionName='@{"image_" + id}'
android:text="@{user.displayName ?? user.lastName}"
android:text="@{user.lastName}"
android:padding="@{large? @dimen/largePadding : @dimen/smallPadding}"
android:text="@{@string/nameFormat(firstName, lastName)}"
android:text="@{@plurals/banana(bananaCount)}"
複製代碼

小於比較符 < 須要轉義爲 &lt;,爲了不字符串轉義單引號和雙引號能夠隨便切換使用
<import> 的類衝突時能夠取別名加以區分服務器

<import type="android.view.View"/>
<import type="com.example.real.estate.View" alias="Vista"/>
複製代碼

<include> 佈局中能夠傳遞變量

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:bind="http://schemas.android.com/apk/res-auto">
   <data>
       <variable name="user" type="com.example.User"/>
   </data>
   <LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent">
       <include layout="@layout/name" bind:user="@{user}"/>
       <include layout="@layout/contact" bind:user="@{user}"/>
   </LinearLayout>
</layout>
複製代碼

不支持 <merge> 結合 <include> 的使用

事件處理

View 事件的分發處理有兩種機制,一種是 Method references,在表達式中直接經過監聽器方法的簽名來引用,Data Binding 會在編譯時評估這個表達式,若是方法不存在或者簽名錯誤那麼編譯就會報錯,若是表達式評估的結果是 null 那麼 Data Binding 就不會建立監聽器而是直接設置 null 監聽器,Data Binding 在 綁定數據的時候 就會建立監聽器的實例: android:onClick="@{handlers::onClickFriend}"。一種是 Listener bindings,Data Binding 在 事件發生的時候 纔會建立監聽器的實例並設置給 view而後評估 lambda 表達式,android:onClick="@{(theView) -> presenter.onSaveClick(theView, task)}"

綁定 Observable 數據

雖然 View 能夠綁定任何 PO 對象,可是所綁定對象的更改並不能自動引發 View 的更新,因此 Data Binding 內置了 Observable 接口和它的 BaseObservableObservableBoolean 等子類能夠方便地將對象、字段和集合變成 observable

private static class User {
    public final ObservableField<String> firstName = new ObservableField<>();
    public final ObservableInt age = new ObservableInt();
}
複製代碼
private static class User extends BaseObservable {
    private String firstName;
    private String lastName;

    @Bindable
    public String getFirstName() {
        return this.firstName;
    }

    @Bindable
    public String getLastName() {
        return this.lastName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
        notifyPropertyChanged(BR.firstName);
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
        notifyPropertyChanged(BR.lastName);
    }
}
複製代碼

執行綁定

有時候綁定須要當即執行,如在 onBindViewHolder() 方法中:

public void onBindViewHolder(BindingHolder holder, int position) {
    final T item = mItems.get(position);
    holder.getBinding().setVariable(BR.item, item);
    holder.getBinding().executePendingBindings();
}
複製代碼

Data Binding 在爲 View 設置表達式的值的時候會自動選擇對應 View 屬性的 setter 方法,如 android:text="@{user.name}" 會選擇 setText() 方法,可是像 android:tint 屬性沒有 setter 方法,可使用 BindingMethods 註解自定義方法名

@BindingMethods({
       @BindingMethod(type = "android.widget.ImageView",
                      attribute = "android:tint",
                      method = "setImageTintList"),
})
複製代碼

若是要自定義 setter 方法的綁定邏輯,可使用 BindingAdapter 註解

@BindingAdapter("android:paddingLeft")
public static void setPaddingLeft(View view, int padding) {
  view.setPadding(padding,
                  view.getPaddingTop(),
                  view.getPaddingRight(),
                  view.getPaddingBottom());
}
複製代碼
<ImageView app:imageUrl="@{venue.imageUrl}" app:error="@{@drawable/venueError}" />
複製代碼
@BindingAdapter({"imageUrl", "error"})
public static void loadImage(ImageView view, String url, Drawable error) {
  Picasso.get().load(url).error(error).into(view);
}
複製代碼

若是要自定義表達式值的自動類型轉換,可使用 BindingConversion 註解

<View android:background="@{isError ? @color/red : @color/white}" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
複製代碼
@BindingConversion
public static ColorDrawable convertColorToDrawable(int color) {
    return new ColorDrawable(color);
}
複製代碼

ViewModel 能夠實現 Observable 接口並結合 PropertyChangeRegistry 能夠更方便地控制數據更改後的行爲

雙向綁定

使用 @={} 符號能夠實現 View 和數據的雙向綁定

<CheckBox android:id="@+id/rememberMeCheckBox" android:layout_width="wrap_content" android:layout_height="wrap_content" android:checked="@={viewmodel.rememberMe}" />
複製代碼
public class LoginViewModel extends BaseObservable {
    // private Model data = ...

    @Bindable
    public Boolean getRememberMe() {
        return data.rememberMe;
    }

    public void setRememberMe(Boolean value) {
        // 爲了防止無限循環,必需要先檢查再更新
        if (data.rememberMe != value) {
            data.rememberMe = value;
            saveData();
            notifyPropertyChanged(BR.remember_me);
        }
    }
}
複製代碼

自定義屬性的雙向綁定還須要藉助 @InverseBindingAdapter@InverseBindingMethod

@BindingAdapter("time")
public static void setTime(MyView view, Time newValue) {
    // Important to break potential infinite loops.
    if (view.time != newValue) {
        view.time = newValue;
    }
}

@InverseBindingAdapter("time")
public static Time getTime(MyView view) {
    return view.getTime();
}
複製代碼

監聽屬性的更改,事件屬性以 AttrChanged 做爲後綴

@BindingAdapter("app:timeAttrChanged")
public static void setListeners( MyView view, final InverseBindingListener attrChange) {
    // Set a listener for click, focus, touch, etc.
}
複製代碼

能夠藉助轉換器類定製 View 的顯示規則

<EditText android:id="@+id/birth_date" android:text="@={Converter.dateToString(viewmodel.birthDate)}" />
複製代碼
public class Converter {
    @InverseMethod("stringToDate")
    public static String dateToString(EditText view, long oldValue, long value) {
        // Converts long to String.
    }

    public static long stringToDate(EditText view, String oldValue, String value) {
        // Converts String to long.
    }
}
複製代碼

Data Binding 內置了 android:textandroid:checked 等的雙向綁定

生命週期敏感組件

在 Activity 或 Fragment 的生命週期方法中進行其它組件的配置並不老是合理的,如在 onStart() 方法中註冊廣播接收器 A、開啓定位服務 A、啓用組件 A 的監聽、啓用組件 B 的監聽等等,在 onStop() 方法中註銷廣播接收器 A、關閉定位服務 A、停用組件 A 的監聽、停用組件 B 的監聽等等,隨着業務邏輯的增長這些生命週期方法變得愈來愈臃腫、愈來愈亂、愈來愈難以維護,若是這些組件在多個 Activity 或 Fragment 上使用那麼還得重複相同的邏輯,就更難以維護了。 並且若是涉及到異步甚至 沒辦法保證 onStart() 方法中的代碼 必定onStop() 方法執行前執行
關注點分離,這些組件的行爲受生命週期的影響,因此它們本身應該意識到本身是生命週期敏感的組件,當生命週期變化時它們應該 本身決定 本身的行爲,而不是交給生命週期的擁有者去處理
生命週期有兩個要素: 事件和狀態,生命週期事件的發生通常會致使生命週期狀態的改變
生命週期敏感組件應該實現 LifecycleObserver 以觀察 LifecycleOwner 的生命週期,支持庫中的 Activity 和 Fragment 都實現了 LifecycleOwner,能夠直接經過它的 getLifecycle() 方法獲取 Lifecycle 實例

MainActivity.this.getLifecycle().addObserver(new MyLocationListener());
複製代碼
class MyLocationListener implements LifecycleObserver {
    private boolean enabled = false;
    
    @OnLifecycleEvent(Lifecycle.Event.ON_START)
    void start() {
        if (enabled) {
           // connect
        }
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
            // connect if not connected
        }
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_STOP)
    void stop() {
        // disconnect if connected
    }
}
複製代碼

GenericLifecycleObserver 接口繼承了 LifecycleObserver,有一個接口方法 onStateChanged(LifecycleOwner, Lifecycle.Event) 代表它能夠接收全部的生命週期過渡事件

LiveData

它的 observe() 方法源碼

@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
    assertMainThread("observe");
    if (owner.getLifecycle().getCurrentState() == DESTROYED) {
        // ignore
        return;
    }
    LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
    if (existing != null && !existing.isAttachedTo(owner)) {
        throw new IllegalArgumentException("Cannot add the same observer"
                + " with different lifecycles");
    }
    if (existing != null) {
        return;
    }
    owner.getLifecycle().addObserver(wrapper);
}
複製代碼

說明 LiveData 只能在主線程中訂閱,訂閱的觀察者被包裝成生命週期組件的觀察者 LifecycleBoundObserver

class LifecycleBoundObserver extends ObserverWrapper implements GenericLifecycleObserve @NonNull final LifecycleOwner mOwner;
    LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer)
        super(observer);
        mOwner = owner;
    }
    @Override
    boolean shouldBeActive() {
        return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
    }
    @Override
    public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
        if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
            removeObserver(mObserver);
            return;
        }
        activeStateChanged(shouldBeActive());
    }
    @Override
    boolean isAttachedTo(LifecycleOwner owner) {
        return mOwner == owner;
    }
    @Override
    void detachObserver() {
        mOwner.getLifecycle().removeObserver(this);
    }
}
複製代碼

當觀察到生命週期狀態變化時會調用 onStateChanged() 方法,因此當狀態爲 DESTROYED 的時候會移除數據觀察者和生命週期觀察者,shouldBeActive() 方法的返回值代表只有生命週期狀態是 STARTEDRESUMEDLifecycleOwner 對應的數據觀察者纔是 active 的,只有 active 的數據觀察者纔會被通知到,當數據觀察者 第一次 從 inactive 變成 active 時,也會 收到通知
observeForever() 方法也能夠訂閱,可是 LiveData 不會自動移除數據觀察者,須要主動調用 removeObserver() 方法移除
LiveDataMutableLiveData 子類提供了 setValue() 方法能夠在主線程中更改所持有的數據,還提供了 postValue() 方法能夠在後臺線程中更改所持有的數據
能夠繼承 LiveData 實現本身的 observable 數據,onActive() 方法代表有 active 的觀察者了,能夠進行數據更新通知了,onInactive() 方法代表沒有任何 active 的觀察者了,能夠清理資源了
單例的 LiveData 能夠實現多個 Activity 或 Fragment 的數據共享
能夠對 LiveData 持有的數據進行變換,須要藉助 Transformations 工具類

private final PostalCodeRepository repository;
private final MutableLiveData<String> addressInput = new MutableLiveData();
public final LiveData<String> postalCode =
    Transformations.switchMap(addressInput, (address) -> {
        return repository.getPostCode(address);
    });
複製代碼
private LiveData<User> getUser(String id) {
  ...;
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
複製代碼

LiveDataMediatorLiveData 子類能夠 merge 多個 LiveData 源,能夠像 ReactiveX 的操做符同樣進行各類變換

幾點感悟

  • 避免手動寫任何模板代碼,尤爲是寫業務代碼時
  • 不要用回調的思想去寫 Rx 的代碼,用 Stream 思想去寫,思想要完全轉變過來
  • 把異步的思想調整回同步:
void businessLogic() {
    showLoadingView();
    request(uri, params, new Callbacks() {
    
        @Override
        void onSuccess(Result result) {
            showDataView(result);
        }
        
        @Override
        void onFailure(Error error) {
            showErrorView(error);
        }
        
    });
}
複製代碼
void businessLogic() async {
    showLoadingView()
    Result result = await request(uri, params)
    result.ok() ? showDataView(result) : showErrorView(result)
}
複製代碼
  • 約定優於配置 ,適當的約定能夠減小至關可觀的勞動量
  • 能自動的就不要手動,好比取消網絡請求等操做要自動進行,不要出現手動操做的代碼
  • 避免源碼中模板代碼的重複,包括用工具自動生成的
  • 藉助編譯器在編譯時生成的代碼就像一把雙刃劍,它獨立於源碼庫,又與源碼緊密聯繫,要謹慎使用

Sample

相關文章
相關標籤/搜索