开发者经常面临的问题
Android应用由四大组件构成,各组件可以被独立且无序的调起,用户会在各个App之间来回切换。组件启动后,生命周期会受用户的操作和系统影响,不完全受开发者控制。而由于设备内存问题,进程随时可能被系统强杀,所以不要将数据和状态直接存储在组件中,也不要让组件互相依赖。
问题实例
-
内存泄漏:在Activity中发起网络请求,在网络请求返回之前退出Activity, 那么Activity就被泄漏了。
-
崩溃:Activity destroy后,还被其他类操作,从而引发崩溃,例如Glide图片库;
-
Activity类臃肿:数据,逻辑,控件代码都堆积在Activity中,导致Activity臃肿,不易维护和测试。
-
Fragment通信困难:开发中经常会遇到Activity中含有多个Fragment的情况, 并且Fragment之间需要通信。通常会利用Activity转发Fragment之间的通信,而且Fragment之间还需要依赖对方的生命周期和通信细节。
-
数据易销毁:如果将内存中的数据保存在Activity中,由于Activity很容易被销毁重建(配置改变,内存不够),那么数据也就很容易被销毁。
通用的框架原则
-
关注点分离:不要在Activity/Fragment中添加非UI控制、非系统接口调用的代码。尽量让他们保持精炼,以免引起生命周期相关的问题。这些类是系统创建和管理的,并不完全受开发者控制。
-
模型驱动UI:应该用数据模型驱动UI展示,最好是持久模型,因为当系统强杀进程或者网络不稳定时,持久模型能让程序继续工作。模型独立于组件之外,不会受到生命周期的影响。
应用框架组件
Android官方在17年IO大会上发布了一套框架组件,帮助开发者开发优质的App.
LifeCycle
将Activity/Fragment的生命周期剥离到其他类中,减少组件类中的代码。
LifeCycle用两个枚举类追踪组件的状态。
- Event:从Framework层分发的,匹配Activity和Fragment中的生命周期方法回调。
- State:当前组件的状态。
使用方式
public class LearnLifeCycleObserver implements LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void onCreate() {
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop() {
}
}
public class LearnLifeCycleActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getLifecycle().addObserver(new LearnLifeCycleObserver());
}
}
实践
-
将UI和数据分离,UI层不需要持有数据。
-
构建数据驱动UI的应用,UI控制器负责根据数据更新UI以及将用户的操作反馈给数据层。
-
将数据逻辑放在ViewModel中,将数据拉取逻辑放在数据仓库(Data Repository)中.
-
使用MVP模式,引入Presenter, 让程序更加容易被测试。
LiveData
一个自带观察者模式的数据封装类,不同的是LiveData有生命周期,会利用LifeCycle自动监听与它绑定的组件的生命周期。当数据发生改变时,Activity或者Fragment要处于活动状态,观察者才会接收到数据改变的回调,此时界面就可以安全的进行渲染,而不会出现Activity销毁后还更改界面的情况。
优点
- 不会造成泄漏内存。
- 不会由于操作销毁的Activity而发生崩溃。
- 不需要手动处理生命周期。
- 数据总能实时更新。
- 数据不会受configuration更改的影响。
- 易于共享数据。
使用
final MutableLiveData<String> userNameLiveData = new MutableLiveData<>();
userNameLiveData.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
//Activity的lifecycle state处于STARTED或RESUMED时才会回调
mUserNameTV.setText(s);
}
});
mUserNameSetBtn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
//更新LiveData的数据,以尝试触发观察者的通知
userNameLiveData.setValue("jayden");
}
});
两个扩展类
MutableLiveData
开放了setValue和postValue方法,用于主动改变LiveData的值,并通知观察者。
MediatorLiveData
可以同时观察多个LiveData, 当被观察的LiveData发生改变时,可以对LiveData的数据进行加工后再通知MediatorLiveData.
转换LiveData
Transformations#map
利用MediatorLiveData将LiveData的数据加工后再通知给观察者。
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
Transformations#switchMap
和map方法类似,而且也是用MediatorLiveData实现。但是switchMap方法的第二个形参接口的返回值是LiveData. 如下代码块中的例子,当userId
发生改变后,getUser(id)
返回另一个LiveData: liveData1
, user
的观察者开始观察liveData1
的变化。
举个例子:id为1的用户切换成id为2的用户后,getUser(id)
方法返回的LiveData就是id为2的用户的用户信息,以后如果id为2的用户信息发生改变,user
的观察者就会接收到通知。
private LiveData<User> getUser(String id) {
...;
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
ViewModel
用来存储和管理和UI相关的有生命周期的数据。当Configuration改变,例如屏幕旋转,语言切换时,ViewModel中的数据不会被销毁,是一个不会被滥用的单例。
创建ViewModel
利用LiveData持有数据
public class MyViewModel extends ViewModel {
private MutableLiveData<List<User>> mObservableUsers;
public MyViewModel(){
mObservableUsers = new MutableLiveData<List<Users>>();
}
public LiveData<List<User>> getUsers() {
return mObservableUsers;
}
private void loadUsers() {
// 异步加载数据后通知到users中。
...
mObservableUsers.setValue(users)
}
}
在Activity中监听数据改变
public class MyActivity extends AppCompatActivity {
public void onCreate(Bundle savedInstanceState) {
MyViewModel model = ViewModelProviders.of(this).get(MyViewModel.class);
//监听LiveData
model.getUsers().observe(this, users -> {
// update UI
});
}
}
如果由于屏幕旋转或语言切换导致Activity重建,ViewModelProviders.of(this).get(MyViewModel.class);
获取的ViewModel还是Activity首次创建时所构建的,只有当Activity销毁后,ViewModel才会被清除。
ViewModel的生命周期
在多个Fragment中共享数据
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
两个Fragment拿到的ViewModel对象是同一个。
这种方式的优点:
- Activity不需要了解Fragment之间的通信,完全松耦合。
- 两个Fragment都不需要依赖对方的生命周期和通信细节,即使一个Fragment被销毁,也不会影响另一个Fragment.
最终的框架
推荐的框架原则
-
在Manifest文件中定义的程序入口:Activities, Services, BroadcastReceiver等,都不能作为数据的来源。
-
明确定义各模块的职责。
-
尽可能少地暴露每个模块的信息。
-
定义模块间的交互时,考虑如何让他们易于测试。
-
将数据持久化,让程序在离线时更加可用。
-
数据存储库应该指定一个数据源作为单一的数据来源。