Android笔记本Android

Android JetPack应用架构指南

2019-01-02  本文已影响0人  大地零一

标签 :JetPack

Android开发中经常面临的问题

MVP模式中的解决方案


常见的架构原则

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开发的规范

需要注意的地方

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 仅更新处于活跃状态的应用组件

优势

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")

上一篇下一篇

猜你喜欢

热点阅读