Android JetPack應(yīng)用架構(gòu)指南

標(biāo)簽 :JetPack

Android開發(fā)中經(jīng)常面臨的問題

  • 在界面控制器中編寫大量代碼,造成界面類非常臃腫,難以維護(hù);
  • 在橫豎屏切換時,界面控制器中存儲的數(shù)據(jù)丟失,需要重新初始化
  • Activity、Fragment中經(jīng)常需要開啟異步線程去獲取數(shù)據(jù),界面控制器需要管理這些調(diào)用,并確保系統(tǒng)在其銷毀后清理這些調(diào)用以避免潛在的內(nèi)存泄漏;
  • 當(dāng)多個地方使用相同數(shù)據(jù)的時候,需要主動去獲取同步數(shù)據(jù),增加邏輯復(fù)雜度;

MVP模式中的解決方案

  • 將網(wǎng)絡(luò)請求和邏輯處理放到對應(yīng)的Presenter
  • 在界面控制器的生命周期中處理維護(hù)Presenter
  • 使用RxJava處理異步操作,并在生命周期中解綁

常見的架構(gòu)原則

1、分離關(guān)注點(diǎn)

任何不處理界面或操作系統(tǒng)交互的代碼都不應(yīng)該寫在Activity、Fragment中,這樣可以避免許多與生命周期相關(guān)的問題。

2、通過模型驅(qū)動界面

模型是負(fù)責(zé)為應(yīng)用處理數(shù)據(jù)的組件。它們獨(dú)立于應(yīng)用中的視圖和應(yīng)用組件,因此不受這些組件的生命周期問題的影響。 同時模型類應(yīng)明確定義數(shù)據(jù)管理職責(zé),這樣將使這些模型類可測試,并且使應(yīng)用保持一致。

Google推薦的MVVM模式

定義Repository管理數(shù)據(jù)來源(Model),使用LiveData驅(qū)動界面(View)更新,使用ViewModel代替Presenter管理數(shù)據(jù)(VM)

看一下google推薦的架構(gòu)圖:


final-architecture.png

ViewModel

ViewModel是什么

架構(gòu)組件為界面控制器提供了 ViewModel輔助程序類,該類負(fù)責(zé)為界面準(zhǔn)備數(shù)據(jù)。在配置更改期間會自動保留 ViewModel 對象,以便它們存儲的數(shù)據(jù)立即可供下一個 ActivityFragment實(shí)例使用

  • 旨在以注重生命周期的方式存儲和管理界面相關(guān)的數(shù)據(jù)。
  • 允許數(shù)據(jù)在屏幕旋轉(zhuǎn)等配置更改后繼續(xù)存在。

ViewModel的使用

class WeatherViewModel : ViewModel() {
    val weatherResult: LiveData<WeatherResult>? = null
    fun getWeather(cityCode: String): WeatherResult {
        if (weatherResult != null) {
            weatherResult = LiveData<WeatherResult>()
        }
        loadWeather(cityCode)
        return weatherResult
    }
    fun loadWeather(cityCode: String) {
        //...獲取天氣數(shù)據(jù)
    }
}

然后在你的Activity中使用:

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val weatherModel = ViewModelProviders.of(this).get(WeatherViewModel::class.java)
    val weather = weatherModel.getWeather("101010100")
    updateWeather(weather)
}

ViewMode的實(shí)現(xiàn)
ViewModel存儲在ActivityFragment
從源碼中分析ViewModel的生命周期

@NonNull
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity,
        @Nullable Factory factory) {
    Application application = checkApplication(activity);
    if (factory == null) {
        factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
    }
    return new ViewModelProvider(activity.getViewModelStore(), factory);
}

該靜態(tài)方法是實(shí)際上是從界面控制器中獲取到ViewModelStore來創(chuàng)建一個ViewModelProvider,此處的factory約定了ViewModel的實(shí)例化方式

Activity中源碼

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    if (mViewModelStore == null) {
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

ViewModelProvider中獲取ViewModel

@NonNull
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {
        //noinspection unchecked
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }

    viewModel = mFactory.create(modelClass);
    mViewModelStore.put(key, viewModel);
    //noinspection unchecked
    return (T) viewModel;
}

可以看出來界面Activity持有一個ViewModelStore來存儲ViewMode,ViewModelStore中使用HashMap來存儲ViewMode

ViewModel的銷毀

@Override
protected void onDestroy() {
    super.onDestroy();

    if (mViewModelStore != null && !isChangingConfigurations()) {
        mViewModelStore.clear();
    }

    mFragments.dispatchDestroy();
}

ViewModel的存活范圍

viewmodel-lifecycle.png

ViewModel更重要的是提供一種Android開發(fā)的規(guī)范

需要注意的地方

  • 單一責(zé)任原則,維護(hù)對外提供所需的數(shù)據(jù),獲取數(shù)據(jù)最好使用另外單獨(dú)的Repository
  • 默認(rèn)構(gòu)造函數(shù)沒有參數(shù),有參數(shù)的構(gòu)造函數(shù)需要自定義ViewModelProvider.Factory,重寫create方法
  • context不能傳入ViewModel(不應(yīng)該關(guān)心界面),AndroidViewModel可以直接使用Application
  • ViewModel不能代替onSaveInstanceState

LifeCycle

Lifecycle

Lifecycle是一個類,它包含有ActivityFragment生命周期狀態(tài)的信息,并允許其他對象觀察此狀態(tài)。

Lifecycle使用兩個主要枚舉來跟蹤其關(guān)聯(lián)組件的生命周期狀態(tài):

  1. EventLifecycle所跟蹤組件(ActivityFragment)回調(diào)的生命周期事件。
  2. StateLifecycle所跟蹤組件(ActivityFragment)的當(dāng)前狀態(tài)。

LifecycleOwner
LifecycleOwner是一個單方法接口,表示該類具有生命周期。它僅有一個方法getLifecycle(),通過該方法提供一個Lifecycle實(shí)例用來接收和存儲當(dāng)前UI控制器的生命周期狀態(tài)。

實(shí)現(xiàn)LifecycleObserver的組件與實(shí)現(xiàn)LifecycleOwner的組件可以無縫協(xié)作,因?yàn)樗姓?ActivityFragment)可以提供生命周期,而觀察者可以監(jiān)聽生命周期回調(diào)。

LiveData

LiveData 是一種可觀察的封裝容器,可以用于任何數(shù)據(jù),包括實(shí)現(xiàn) Collections 的對象,如 List。具有生命周期感知能力,感知應(yīng)用組件(Activity、Fragment 或 Service)的生命周期確保 LiveData 僅更新處于活躍狀態(tài)的應(yīng)用組件

優(yōu)勢

  • 響應(yīng)式更新界面:數(shù)據(jù)發(fā)生變化時自動更新相關(guān)聯(lián)的UI
  • 不會發(fā)生內(nèi)存泄漏:觀察者綁定到 Lifecycle對象,并且在其關(guān)聯(lián)的生命周期被銷毀后,會自我清理
  • 不會因 Activity停止而導(dǎo)致崩潰:如果觀察者的生命周期處于非活躍狀態(tài)(如返回棧中的 Activity),則它不會接收任何 LiveData 事件
  • 數(shù)據(jù)始終保持最新狀態(tài):如果生命周期變?yōu)榉腔钴S狀態(tài),它會在再次變?yōu)榛钴S狀態(tài)時接收最新的數(shù)據(jù)。例如,曾經(jīng)在后臺的 Activity 會在返回前臺后立即接收最新的數(shù)據(jù)。
  • 適當(dāng)?shù)呐渲酶模喝绻捎谂渲酶模ㄈ缭O(shè)備旋轉(zhuǎn))而重新創(chuàng)建了 ActivityFragment,它會立即接收最新的可用數(shù)據(jù)。

LiveData的相關(guān)操作

  1. 更新數(shù)據(jù)
    setValue(value) 主線程中使用
    postValue(value) 子線程中使用

  2. 轉(zhuǎn)換數(shù)據(jù)
    Transformations.map()

    val weather = LiveData<Weather>()
    val result = Transformations.map(weather) { it: Weather ->
        it.date + ":" + weather.pm25
    }

Transformations.switchMap()

    val cityData = LiveData<String>()
    val result = Transformations.switchMap(cityData) { it: String ->
        getWeather(it)
    }
    private fun getWeather(cityCode: String): LiveData<WeatherResult> {
        //...
    }

LiveData的使用
按照如下步驟使用:

  1. 創(chuàng)建LiveData對象,并讓其持有一個具體類型的數(shù)據(jù)。通常在ViewModel中使用。
  2. 創(chuàng)建Observer對象,并重寫onChange()方法,當(dāng)LiveData持有的數(shù)據(jù)發(fā)生改變時會回調(diào)此方法。Observer對象通常在UI控制器(ActivityFragment)中使用。
  3. 使用LiveDataobserver()方法來訂閱一個Observer對象,這樣LiveData持有的數(shù)據(jù)發(fā)生變化時觀察者就能夠收到通知。通常在UI控制器中訂閱觀察者對象。

我們之前的ViewModel可以改成如下:

class WeatherViewModel : ViewModel() {

    @Inject
    lateinit var weatherRepository: WeatherRepository
    private var cityData = MutableLiveData<String>()
    val weatherResult: LiveData<WeatherResult> by lazy {
        Transformations.switchMap(cityLiveData) {
            weatherRepository.getWeatherResult(it)
        }
    }
    
    init {
        DaggerRepositoryComponent.builder().build().inject(this)
        Log.i("ViewModel", "created")
    }
    
    fun setCity(cityCode: String) {
         cityData.setValue(cityCode)
    }
}

Repository類:

class WeatherRepository {
    fun getWeatherResult(cityCode: String): LiveData<WeatherResult> {
        Log.i("Weather update", "獲取數(shù)據(jù)code=$cityCode")
        val result = MutableLiveData<WeatherResult>()
        //...獲取數(shù)據(jù)源
        //...可以來自緩存、數(shù)據(jù)庫、網(wǎng)絡(luò)
        //網(wǎng)絡(luò)獲取
        DDHttp.get(Api.GET_WEATHER + cityCode)
            .build()
            .enqueue(object : ResponseCallback<WeatherResult> {
                override fun onSuccess(model: WeatherResult) {
                    result.value = model
                }
                override fun onFailure(throwable: Throwable) {
                }
            })

        return result
    }
}

FragmentActivity中調(diào)用:

override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(R.layout.activity_main)
       weatherModel.setCity("101010100")
       val weatherModel = ViewModelProviders.of(this).get(WeatherViewModel::class.java)
       weatherModel.weatherResult.observe(this, Observer<WeatherResult> { updateWeather(it) })
       
       btn_bj.setOnClickListener { weatherModel.setCity("101010100") }
   }

   private fun updateWeather(result: WeatherResult) {
       Log.i("Weather Update", result.time)
    //...
   }

由于FragmentActivity已經(jīng)實(shí)現(xiàn)了LifecycleOwner接口,所以可以直接使用
如果在Activity中使用,需要實(shí)現(xiàn)LifecycleOwner接口,并提供一個LifecycleRegistry即可

class ThirdActivity : Activity(), LifecycleOwner {

    private var lifecycleRegistry = LifecycleRegistry(this)

    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

LiveData還提供另一種添加觀察者的方法observeForever(Observer),通過該方法添加觀察者后,要手動調(diào)用removeObserver()方法來停止觀察者接收回調(diào)通知

擴(kuò)展LiveData
當(dāng) LiveData 對象具有活躍觀察者時,會調(diào)用 onActive() 方法
當(dāng) LiveData 對象沒有任何活躍觀察者時,會調(diào)用 onInactive() 方法

public class StockLiveData extends LiveData<BigDecimal> {
    private StockManager mStockManager;

    private SimplePriceListener mListener = new SimplePriceListener() {
        @Override
        public void onPriceChanged(BigDecimal price) {
            setValue(price);
        }
    };

    public StockLiveData(String symbol) {
        mStockManager = new StockManager(symbol);
    }

    @Override
    protected void onActive() {
        mStockManager.requestPriceUpdates(mListener);
    }

    @Override
    protected void onInactive() {
        mStockManager.removeUpdates(mListener);
    }
}

自定義LiveData主要是在onActive時接受數(shù)據(jù)變化,在onInactive時停止接受新數(shù)據(jù)

Room數(shù)據(jù)庫

上述 WeatherRepository 實(shí)現(xiàn)的問題是,獲取數(shù)據(jù)后,不會將數(shù)據(jù)保留在任何位置。如果用戶在離開后再回到該類,應(yīng)用將重新獲取數(shù)據(jù),這樣做很體驗(yàn)不太好,同時也浪費(fèi)流量

Room 是一個對象映射庫,可利用最少的樣板代碼實(shí)現(xiàn)本地數(shù)據(jù)持久性。在編譯時,它會根據(jù)架構(gòu)驗(yàn)證每個查詢,使損壞的 SQL 查詢導(dǎo)致編譯時錯誤而不是運(yùn)行時失敗。Room 可以抽象化處理原始 SQL 表格和查詢的一些底層實(shí)現(xiàn)細(xì)節(jié)。它還允許觀察對數(shù)據(jù)庫數(shù)據(jù)(包括集合和連接查詢)的更改,并通過 LiveData 對象公開此類更改。此外,它還明確定義了解決一些常見問題(如訪問主線程上的存儲空間)的線程約束。

Room的使用

要使用 Room,我們需要定義本地model類。使用 @Entity(tableName = String)進(jìn)行注解,以將其標(biāo)記為數(shù)據(jù)庫中的表格

@Entity(tableName = "weather")
data class WeatherResult(
    @PrimaryKey
    @ColumnInfo(name = "city_code")
    var cityCode: String,
    @ColumnInfo(name = "weather_date")
    var date: String,
    var sunrise: String,
    var high: String,
    var low: String,
    var sunset: String,
    var aqi: Float,
    var ymd: String,
    var week: String,
    var fx: String,
    var fl: String,
    var type: String,
    var notice: String
)

創(chuàng)建一個數(shù)據(jù)訪問對象 (DAO)。

@Dao
public interface WeatherDao {
    @Insert(onConflict = REPLACE)
    void save(weather WeatherResult);
    @Query("SELECT * FROM weather WHERE city_code = :cityCode")
    LiveData<WeatherResult> load(String cityCode);
    @Delete
    fun delete(weather: WeatherResult)
}

請注意,load方法將返回 LiveData<WeatherResult>。Room知道何時修改了數(shù)據(jù)庫,并且在數(shù)據(jù)發(fā)生更改時會自動通知所有活躍的觀察者。

為我們的App創(chuàng)建數(shù)據(jù)庫:

@Database(entities = [WeatherResult::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun getWeatherDao(): WeatherDao
}

WeatherDatabase是抽象類。Room將自動提供它的實(shí)現(xiàn)。

獲取AppDatabase實(shí)例

val db = Room.databaseBuilder(applicationContext,AppDatabase::class.java,"weather.db").build();

注意:Room不允許在主線程中訪問數(shù)據(jù)庫,除了創(chuàng)建實(shí)例的時候

表中常用注解
@Entity 創(chuàng)建一張表
@PrimaryKey主鍵
@ColumnInfo(name = String) 標(biāo)記字段名
@IgnoreRoom默認(rèn)會在表中創(chuàng)建所有字段,如果不需要可以使用Ignore
@Embedded(prefix = String) 用來注解Object類型字段(非基礎(chǔ)類型數(shù)據(jù))如果有多個Object字段,且包含相同的字段名,需要指定prefix

常規(guī)操作
插入 @Insert(onConflict = OnConflictStrategy.REPLACE)

更新 @Update

刪除 @Delete

查詢,需要自定義查詢語句,其中cityCode為參數(shù)
@Query("SELECT * FROM weather WHERE city_code = :cityCode")

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容