美文网首页Android笔记本Android
Android JetPack应用架构指南

Android JetPack应用架构指南

作者: 大地零一 | 来源:发表于2019-01-02 15:23 被阅读0次

    标签 :JetPack

    Android开发中经常面临的问题

    • 在界面控制器中编写大量代码,造成界面类非常臃肿,难以维护;
    • 在横竖屏切换时,界面控制器中存储的数据丢失,需要重新初始化
    • ActivityFragment中经常需要开启异步线程去获取数据,界面控制器需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄漏;
    • 当多个地方使用相同数据的时候,需要主动去获取同步数据,增加逻辑复杂度;

    MVP模式中的解决方案

    • 将网络请求和逻辑处理放到对应的Presenter
    • 在界面控制器的生命周期中处理维护Presenter
    • 使用RxJava处理异步操作,并在生命周期中解绑

    常见的架构原则

    1、分离关注点

    任何不处理界面或操作系统交互的代码都不应该写在ActivityFragment中,这样可以避免许多与生命周期相关的问题。

    2、通过模型驱动界面

    模型是负责为应用处理数据的组件。它们独立于应用中的视图和应用组件,因此不受这些组件的生命周期问题的影响。 同时模型类应明确定义数据管理职责,这样将使这些模型类可测试,并且使应用保持一致。

    Google推荐的MVVM模式

    定义Repository管理数据来源(Model),使用LiveData驱动界面(View)更新,使用ViewModel代替Presenter管理数据(VM)

    看一下google推荐的架构图:


    final-architecture.png

    ViewModel

    ViewModel是什么

    架构组件为界面控制器提供了 ViewModel辅助程序类,该类负责为界面准备数据。在配置更改期间会自动保留 ViewModel 对象,以便它们存储的数据立即可供下一个 ActivityFragment实例使用

    • 旨在以注重生命周期的方式存储和管理界面相关的数据。
    • 允许数据在屏幕旋转等配置更改后继续存在。

    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存储在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);
    }
    

    该静态方法是实际上是从界面控制器中获取到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来存储ViewModeViewModelStore中使用HashMap来存储ViewMode

    ViewModel的销毁

    @Override
    protected void onDestroy() {
        super.onDestroy();
    
        if (mViewModelStore != null && !isChangingConfigurations()) {
            mViewModelStore.clear();
        }
    
        mFragments.dispatchDestroy();
    }
    

    ViewModel的存活范围

    viewmodel-lifecycle.png

    ViewModel更重要的是提供一种Android开发的规范

    需要注意的地方

    • 单一责任原则,维护对外提供所需的数据,获取数据最好使用另外单独的Repository
    • 默认构造函数没有参数,有参数的构造函数需要自定义ViewModelProvider.Factory,重写create方法
    • context不能传入ViewModel(不应该关心界面),AndroidViewModel可以直接使用Application
    • ViewModel不能代替onSaveInstanceState

    LifeCycle

    Lifecycle

    Lifecycle是一个类,它包含有ActivityFragment生命周期状态的信息,并允许其他对象观察此状态。

    Lifecycle使用两个主要枚举来跟踪其关联组件的生命周期状态:

    1. EventLifecycle所跟踪组件(ActivityFragment)回调的生命周期事件。
    2. StateLifecycle所跟踪组件(ActivityFragment)的当前状态。

    LifecycleOwner
    LifecycleOwner是一个单方法接口,表示该类具有生命周期。它仅有一个方法getLifecycle(),通过该方法提供一个Lifecycle实例用来接收和存储当前UI控制器的生命周期状态。

    实现LifecycleObserver的组件与实现LifecycleOwner的组件可以无缝协作,因为所有者(ActivityFragment)可以提供生命周期,而观察者可以监听生命周期回调。

    LiveData

    LiveData 是一种可观察的封装容器,可以用于任何数据,包括实现 Collections 的对象,如 List。具有生命周期感知能力,感知应用组件(Activity、Fragment 或 Service)的生命周期确保 LiveData 仅更新处于活跃状态的应用组件

    优势

    • 响应式更新界面:数据发生变化时自动更新相关联的UI
    • 不会发生内存泄漏:观察者绑定到 Lifecycle对象,并且在其关联的生命周期被销毁后,会自我清理
    • 不会因 Activity停止而导致崩溃:如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件
    • 数据始终保持最新状态:如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
    • 适当的配置更改:如果由于配置更改(如设备旋转)而重新创建了 ActivityFragment,它会立即接收最新的可用数据。

    LiveData的相关操作

    1. 更新数据
      setValue(value) 主线程中使用
      postValue(value) 子线程中使用

    2. 转换数据
      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. 创建LiveData对象,并让其持有一个具体类型的数据。通常在ViewModel中使用。
    2. 创建Observer对象,并重写onChange()方法,当LiveData持有的数据发生改变时会回调此方法。Observer对象通常在UI控制器(ActivityFragment)中使用。
    3. 使用LiveDataobserver()方法来订阅一个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) 标记字段名
    @IgnoreRoom默认会在表中创建所有字段,如果不需要可以使用Ignore
    @Embedded(prefix = String) 用来注解Object类型字段(非基础类型数据)如果有多个Object字段,且包含相同的字段名,需要指定prefix

    常规操作
    插入 @Insert(onConflict = OnConflictStrategy.REPLACE)

    更新 @Update

    删除 @Delete

    查询,需要自定义查询语句,其中cityCode为参数
    @Query("SELECT * FROM weather WHERE city_code = :cityCode")

    相关文章

      网友评论

        本文标题:Android JetPack应用架构指南

        本文链接:https://www.haomeiwen.com/subject/fkmzlqtx.html