標(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)圖:

ViewModel
ViewModel是什么
架構(gòu)組件為界面控制器提供了 ViewModel輔助程序類,該類負(fù)責(zé)為界面準(zhǔn)備數(shù)據(jù)。在配置更改期間會自動保留 ViewModel 對象,以便它們存儲的數(shù)據(jù)立即可供下一個 Activity 或 Fragment實(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存儲在Activity或Fragment中
從源碼中分析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更重要的是提供一種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是一個類,它包含有Activity或Fragment生命周期狀態(tài)的信息,并允許其他對象觀察此狀態(tài)。
Lifecycle使用兩個主要枚舉來跟蹤其關(guān)聯(lián)組件的生命周期狀態(tài):
-
Event:Lifecycle所跟蹤組件(Activity或Fragment)回調(diào)的生命周期事件。 -
State:Lifecycle所跟蹤組件(Activity或Fragment)的當(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)樗姓?Activity或Fragment)可以提供生命周期,而觀察者可以監(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)建了
Activity或Fragment,它會立即接收最新的可用數(shù)據(jù)。
LiveData的相關(guān)操作
更新數(shù)據(jù)
setValue(value)主線程中使用
postValue(value)子線程中使用轉(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的使用
按照如下步驟使用:
- 創(chuàng)建
LiveData對象,并讓其持有一個具體類型的數(shù)據(jù)。通常在ViewModel中使用。 - 創(chuàng)建
Observer對象,并重寫onChange()方法,當(dāng)LiveData持有的數(shù)據(jù)發(fā)生改變時會回調(diào)此方法。Observer對象通常在UI控制器(Activity或Fragment)中使用。 - 使用
LiveData的observer()方法來訂閱一個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")