Android JetPack应用架构指南
标签 :JetPack
Android开发中经常面临的问题
- 在界面控制器中编写大量代码,造成界面类非常臃肿,难以维护;
- 在横竖屏切换时,界面控制器中存储的数据丢失,需要重新初始化
- 在
Activity
、Fragment
中经常需要开启异步线程去获取数据,界面控制器需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄漏; - 当多个地方使用相同数据的时候,需要主动去获取同步数据,增加逻辑复杂度;
MVP模式中的解决方案
- 将网络请求和逻辑处理放到对应的
Presenter
中 - 在界面控制器的生命周期中处理维护
Presenter
- 使用
RxJava
处理异步操作,并在生命周期中解绑
常见的架构原则
1、分离关注点
任何不处理界面或操作系统交互的代码都不应该写在
Activity
、Fragment
中,这样可以避免许多与生命周期相关的问题。
2、通过模型驱动界面
模型是负责为应用处理数据的组件。它们独立于应用中的视图和应用组件,因此不受这些组件的生命周期问题的影响。 同时模型类应明确定义数据管理职责,这样将使这些模型类可测试,并且使应用保持一致。
Google推荐的MVVM模式
定义
Repository
管理数据来源(Model),使用LiveData
驱动界面(View)更新,使用ViewModel
代替Presenter
管理数据(VM)
看一下google推荐的架构图:
final-architecture.png
ViewModel
ViewModel是什么
架构组件为界面控制器提供了 ViewModel
辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel
对象,以便它们存储的数据立即可供下一个 Activity
或 Fragment
实例使用
- 旨在以注重生命周期的方式存储和管理界面相关的数据。
- 允许数据在屏幕旋转等配置更改后继续存在。
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) {
//...获取天气数据
}
}
然后在你的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的实现
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);
}
该静态方法是实际上是从界面控制器中获取到ViewModelStore
来创建一个ViewModelProvider
,此处的factory
约定了ViewModel
的实例化方式
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.pngViewModel更重要的是提供一种Android开发的规范
需要注意的地方
- 单一责任原则,维护对外提供所需的数据,获取数据最好使用另外单独的
Repository
- 默认构造函数没有参数,有参数的构造函数需要自定义
ViewModelProvider.Factory
,重写create
方法 -
context
不能传入ViewModel
(不应该关心界面),AndroidViewModel
可以直接使用Application
-
ViewModel
不能代替onSaveInstanceState
LifeCycle
Lifecycle
Lifecycle
是一个类,它包含有Activity
或Fragment
生命周期状态的信息,并允许其他对象观察此状态。
Lifecycle
使用两个主要枚举来跟踪其关联组件的生命周期状态:
-
Event
:Lifecycle
所跟踪组件(Activity
或Fragment
)回调的生命周期事件。 -
State
:Lifecycle
所跟踪组件(Activity
或Fragment
)的当前状态。
LifecycleOwner
LifecycleOwner
是一个单方法接口,表示该类具有生命周期。它仅有一个方法getLifecycle()
,通过该方法提供一个Lifecycle
实例用来接收和存储当前UI控制器的生命周期状态。
实现LifecycleObserver
的组件与实现LifecycleOwner
的组件可以无缝协作,因为所有者(Activity
或Fragment
)可以提供生命周期,而观察者可以监听生命周期回调。
LiveData
LiveData 是一种可观察的封装容器,可以用于任何数据,包括实现 Collections 的对象,如 List。具有生命周期感知能力,感知应用组件(Activity、Fragment 或 Service)的生命周期确保 LiveData 仅更新处于活跃状态的应用组件
优势
- 响应式更新界面:数据发生变化时自动更新相关联的UI
- 不会发生内存泄漏:观察者绑定到
Lifecycle
对象,并且在其关联的生命周期被销毁后,会自我清理 - 不会因
Activity
停止而导致崩溃:如果观察者的生命周期处于非活跃状态(如返回栈中的Activity
),则它不会接收任何LiveData
事件 - 数据始终保持最新状态:如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的
Activity
会在返回前台后立即接收最新的数据。 - 适当的配置更改:如果由于配置更改(如设备旋转)而重新创建了
Activity
或Fragment
,它会立即接收最新的可用数据。
LiveData的相关操作
-
更新数据
setValue(value)
主线程中使用
postValue(value)
子线程中使用 -
转换数据
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的使用
按照如下步骤使用:
- 创建
LiveData
对象,并让其持有一个具体类型的数据。通常在ViewModel中使用。 - 创建
Observer
对象,并重写onChange()
方法,当LiveData
持有的数据发生改变时会回调此方法。Observer
对象通常在UI控制器(Activity
或Fragment
)中使用。 - 使用
LiveData
的observer()
方法来订阅一个Observer
对象,这样LiveData
持有的数据发生变化时观察者就能够收到通知。通常在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", "获取数据code=$cityCode")
val result = MutableLiveData<WeatherResult>()
//...获取数据源
//...可以来自缓存、数据库、网络
//网络获取
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
中调用:
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已经实现了LifecycleOwner
接口,所以可以直接使用
如果在Activity
中使用,需要实现LifecycleOwner
接口,并提供一个LifecycleRegistry
即可
class ThirdActivity : Activity(), LifecycleOwner {
private var lifecycleRegistry = LifecycleRegistry(this)
override fun getLifecycle(): Lifecycle {
return lifecycleRegistry
}
}
LiveData
还提供另一种添加观察者的方法observeForever(Observer)
,通过该方法添加观察者后,要手动调用removeObserver()
方法来停止观察者接收回调通知
扩展LiveData
当 LiveData
对象具有活跃观察者时,会调用 onActive()
方法
当 LiveData
对象没有任何活跃观察者时,会调用 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时接受数据变化,在onInactive时停止接受新数据
Room数据库
上述 WeatherRepository
实现的问题是,获取数据后,不会将数据保留在任何位置。如果用户在离开后再回到该类,应用将重新获取数据,这样做很体验不太好,同时也浪费流量
Room
是一个对象映射库,可利用最少的样板代码实现本地数据持久性。在编译时,它会根据架构验证每个查询,使损坏的SQL
查询导致编译时错误而不是运行时失败。Room
可以抽象化处理原始SQL
表格和查询的一些底层实现细节。它还允许观察对数据库数据(包括集合和连接查询)的更改,并通过LiveData
对象公开此类更改。此外,它还明确定义了解决一些常见问题(如访问主线程上的存储空间)的线程约束。
Room的使用
要使用 Room
,我们需要定义本地model
类。使用 @Entity(tableName = String)
进行注解,以将其标记为数据库中的表格
@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
)
创建一个数据访问对象 (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
知道何时修改了数据库,并且在数据发生更改时会自动通知所有活跃的观察者。
为我们的App创建数据库:
@Database(entities = [WeatherResult::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun getWeatherDao(): WeatherDao
}
WeatherDatabase
是抽象类。Room
将自动提供它的实现。
获取AppDatabase
实例
val db = Room.databaseBuilder(applicationContext,AppDatabase::class.java,"weather.db").build();
注意:
Room
不允许在主线程中访问数据库,除了创建实例的时候
表中常用注解
@Entity
创建一张表
@PrimaryKey
主键
@ColumnInfo(name = String)
标记字段名
@Ignore
Room默认会在表中创建所有字段,如果不需要可以使用Ignore
@Embedded(prefix = String)
用来注解Object类型字段(非基础类型数据)如果有多个Object字段,且包含相同的字段名,需要指定prefix
常规操作
插入 @Insert(onConflict = OnConflictStrategy.REPLACE)
更新 @Update
删除 @Delete
查询,需要自定义查询语句,其中cityCode为参数
@Query("SELECT * FROM weather WHERE city_code = :cityCode")