jetpack(一)

158 阅读7分钟

一ViewModel(储存数据)

屏幕旋转,activity默认会销毁重新创建,这个时候页面上的数据就会消失,或系统内存不足,系统会杀死后台运行的页面,再次进入页面数据也会消失。这给用户带来了很不友好的体验。

在ViewModel出现之前,当页面旋转或因内存不足被系统杀死之前,会调用onSaveInstanceState方法,我们可以把我们想要保存的数据通过该方法保存下来,当页面再次创建的时候,我们就可以获取到之前保存的数据。这样的处理方式会让开发者编写很多保存和恢复数据的代码。官方注意到了这一点,提供了ViewModel库来解决这两个问题。

屏幕旋转保留数据的思路:把页面数据放入ViewModel类中,只要保证页面在重新创建的时候尝试获取老的ViewModel,如果没有再去创建新的ViewModel,并且在合适的时机清除保存老的ViewModel。(关键源码如下)

 public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
        ViewModel viewModel = mViewModelStore.get(key);
    
        if (modelClass.isInstance(viewModel)) {
            if (mFactory instanceof OnRequeryFactory) {
                ((OnRequeryFactory) mFactory).onRequery(viewModel);
            }
            return (T) viewModel;
        } else {
            //noinspection StatementWithEmptyBody
            if (viewModel != null) {
                // TODO: log a warning.
            }
        }
        if (mFactory instanceof KeyedFactory) {
            viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
        } else {
            viewModel = (mFactory).create(modelClass);
        }
        mViewModelStore.put(key, viewModel);
        return (T) viewModel;
    }

   getLifecycle().addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    if (!isChangingConfigurations()) {
                        getViewModelStore().clear();
                    }
                }
            }
        });

通过源码可以看出:

1.ViewModel比页面生命周期要长,只有这样,才能获取老的对象

2.ViewModel使用了Lifecycle框架,毕竟要在合适的实际清除数据

2.仅上边源码并未找到处理页面意外被后台关闭数据保存的逻辑,事实也确实这样。

原理知道了,那么如何使用ViewModule呢?

1.继承ViewModel

2.把要保存的数据放入ViewModel中

3.页面上数据全部通过Viewmodel去保存和获取

代码如下:

class MyViewModule() : ViewModel() {
    var  number = 0
}

class MainActivity : AppCompatActivity() {

    lateinit var viewModule: MyViewModule

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModule = ViewModelProvider(this).get(MyViewModule::class.java)
        tv.setText(viewModule.number.toString())
        button.setOnClickListener {
            viewModule.number = 1
            tv.setText(viewModule.number.toString())
        }
    }


}

注意:如果我们自己去设计ViewModule,那么我们就要自己处理获取 储存 清除。google给我们封装关于数据的获取储存清除,这就意味着,我们不能去new,而是用过google的api去获取ViewModel对象

通过上边代码我们可以在旋转屏幕的时候数据不会重新创建。我们继续验证第二个理论:页面被系统意外销毁,数据是否会保存(开发者选项-->开启不保留活动)发现数据并未保存

那么如何能让ViewModel保存页面被系统意外销毁,再次进入,数据仍然被保存呢?

步骤:

1.给自己定义的ViewModel构造函数中添加一个参数:val handle: SavedStateHandle

2.创建ViewModel对象的时候传入创建该对象的工厂类(可以不写)

代码如下:

class MyViewModule(val handle: SavedStateHandle) : ViewModel() {

    val key = "number"
    var  number = 0
    fun getNumber(): Student {
        if (!handle.contains(key)){
            handle.set(key,Student("默认值"))
        }
        return handle.get<Student>(key)!!
    }


    fun  setNumber( stu:Student){
        handle.set(key,stu)
    }
}

class MainActivity : AppCompatActivity() {

    lateinit var viewModule: MyViewModule

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModule = ViewModelProvider(this,SavedStateViewModelFactory(application,this)).get(MyViewModule::class.java)
        tv.setText(viewModule.number.toString())
        button.setText(viewModule.getNumber().name)
        button.setOnClickListener {
            viewModule.number = 1
            tv.setText(viewModule.number.toString())
            viewModule.setNumber(Student("张三"))
            button.setText(viewModule.getNumber().name)
        }
    }


}

在页面被系统关闭,再次进入页面,button上的数据仍然会恢复,tv上的数据不会恢复

tips1:步骤2不实现也可以,是因为系统默认使用的就是这个工厂类,ComponentActivity源码如下:

    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        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 (mDefaultFactory == null) {
            mDefaultFactory = new SavedStateViewModelFactory(
                    getApplication(),
                    this,
                    getIntent() != null ? getIntent().getExtras() : null);
        }
        return mDefaultFactory;
    }

tips2:当我们ViewModel需要一个上下文对象的时候,我们可以继承AndroidViewModel对象。

tips3:如果要保存对象类型,需要对象实现序列化

细心地人已经发现上边示例代码有点冗余:每次改变值需要改变两个地方:Viewmodel中的值和UI上边的值。并没有节省下来代码,google当然也注意到这点,并且为我们提供了贴心的解决方法LiveData。

二MutableLiveData

思路:利用监听者模式,把ViewModel中的数据用MutableLiveData包裹住,这样数据就可以设置监听,当数据改变后,会自动更新ui,类似数据驱动ui展示,我们只管改变数据即可。

代码如下:

class MyViewModule(val handle: SavedStateHandle) : ViewModel() {

    val key = "number"

    fun getNumber(): MutableLiveData<Student> {
        if (!handle.contains(key)){
            handle.set(key,Student("默认值"))
        }
        return handle.getLiveData(key)
    }

    fun  setNumber(stu :Student){
        getNumber().value = stu
    }

}

class MainActivity : AppCompatActivity() {

    lateinit var viewModule: MyViewModule

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        viewModule = ViewModelProvider(this,SavedStateViewModelFactory(application,this)).get(MyViewModule::class.java)
        viewModule.getNumber().observe(this,object :Observer<Student>{
            override fun onChanged(t: Student?) {
                tv.setText(t?.name)
            }
        })
        button.setOnClickListener {
            viewModule.setNumber(Student("张三"))
        }
    }
}

tips1:MutableLiveData并没有能力发现name改变后进行数据的驱动,而是当对象整体改变后才会驱动,那么有没有更加细节的,当观察到对象中某个字段改变,就去驱动UI的更新呢?那就是使用MutableLiveData的父类,LiveData类,具体实现

tips2:ViewModel是Acitivy局部单例(因为ViewModel的储存位置决定了他局部单例的特性),我们可以通过ViewModel配合LiveData去实现Fragment之间的通讯,(注意Fragment写法:viewModule =ViewModelProvider(mActivity).get(HomeViewModule::class.java))

源码分析:

     @Override
        public void onStateChanged(@NonNull LifecycleOwner source,
                @NonNull Lifecycle.Event event) {
            if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
                removeObserver(mObserver);
                return;
            }
            activeStateChanged(shouldBeActive());
        }

上线源码依赖Lifecycle来移除监听,防止内存泄漏。

    @MainThread
    protected void setValue(T value) {
        assertMainThread("setValue");
        mVersion++;
        mData = value;
        dispatchingValue(null);
    } 

setValue方法会触发被观察者回调,这也解释了为什么tips1某个字段改变无法触发回调的原因。

    private void considerNotify(ObserverWrapper observer) {
        if (!observer.mActive) {
            return;
        }
        // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet.
        //
        // we still first check observer.active to keep it as the entrance for events. So even if
        // the observer moved to an active state, if we've not received that event, we better not
        // notify for a more predictable notification order.
        if (!observer.shouldBeActive()) {
            observer.activeStateChanged(false);
            return;
        }
        if (observer.mLastVersion >= mVersion) {
            return;
        }
        observer.mLastVersion = mVersion;
        observer.mObserver.onChanged((T) mData);
    }

上边源码表达的:只有当页面是激活状态的时候,才触发回调,如果想要非激活状态也触发回调就需要使用observerForever方法。

三DataBinding

通过上边两个类的配合,我们代码执行逻辑变成如下图:

关于UI的更新还是通过持有references对象向View中更新,于是google提供了DataBinding来让ViewModel直接和View去绑定,但是通过很多前辈的实践证明DataBinding如果用于生产环境带来的痛苦比收益更大,故不打算用这个功能,这里不再阐述。Databinding吐槽

四Navigation

google提出了单页面应用(一个应用只有一个Activity,剩下的全部是Fragment),如果按照这个思路进行下去,程序员就会面临一个问题,Fragment回退栈,当然google为我们提供了自己控制栈的回退,又是一堆繁琐的代码。于是google帮我们封装了fragment需要使用回退栈的情况----Navigation。

封装细节一:假如加载三个Fragment,那么第一个Fragment永远不用进入栈。从第二个Fragment才进入栈管理。

封装细节二:每个Fragment进入,上一个Fragment都会触发生命周期方法onDestroyView执行,包括第一个Fragment。猜测内部源码如下:

加载第一个Fragment,不进行栈管理:

supportFragmentManager.beginTransaction().add(R.id.con,onefragment).commit()

从第二个Fragment开始就执行:

supportFragmentManager.beginTransaction().replace(R.id.con,TwoFragment()).addToBackStack("two").commit()

注意:View重新渲染,就会带来UI显示和数据的丢失。数据丢失,我们可以使用ViewModel来解决,UI显示数据丢失,我们可以通过android:saveEnabled属性来冻结(必须设置id)。毕竟View重新渲染了。

五Lifecycle

我们在使用Activity或者Fragment的时候,往往需要在生命周期中处理一些资源的获取和释放,如果写到声明周期方法中,会让代码变得臃肿,如果抽离出来,就需要自己再搞一个回调(MVP架构),google就从源码层做了统一的回调,让我们可以在自己创建的类获取想要观察到的声明周期。(本质还是向LifecycleRegistry类中注册监听,并且在生命周期回调的时候,触发LifecycleRegistry内部回调自己创建的监听)

使用方法:

public class MyLife  implements LifecycleObserver {

    @OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
    private  void   MainOnCreate(){
        Log.e("rrrrrrrrrrre","MainonCreate");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    private  void   MainOnResume(){
        Log.e("rrrrrrrrrrre","MainOnResume");
    }

    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    private  void   MainOnPause(){
        Log.e("rrrrrrrrrrre","MainOnPause");
    }


    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
    private  void   MainOnDestroy(){
        Log.e("rrrrrrrrrrre","MainOnDestroy");
    }
} 

   lifecycle.addObserver(MyLife())

或者

lifecycle.addObserver(object:LifecycleEventObserver{
            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
             Log.e("rrrrrrrrrrre",event.toString())
            }
        })