标准的MVC模式中,各层职责:
- Model层:负责处理数据,包括网络数据和持久化数据的获取、加工等。
- View层:负责处理界面绘制,展示数据,并对用户产生交互反馈等,在Android中表现为如Activity/Fragment等。
-Controller层:负责处理业务逻辑等。
上图没有展示Model层,ViewGroup也就是layout,通常是由各控件view组成的树形结构,并在Controller中被引用,UIData是界面的控制逻辑数据,可以使用ViewModel去管理这些数据:
这样Controller就变得更简洁,更加模块化,而且ViewModel有保存Activity当前状态的功能,无论你是退到后台还是切换横屏,回到原界面数据状态不变,当然只是短时间临时保存,如果程序长时间在后台的话,还是会被杀死。
但是当ViewModel中的UIData改变时,依然要靠Controller中的References驱动ViewGroup改变,而使用LiveData会通过给数据添加一个观察者,监听到数据改变时自动刷新UI,不再需要reference去驱动UI的刷新:
再使用DataBinding彻底将Controller与ViewGroup解耦:
接下来写一个小Demo,涉及到一下几个技能点:
- ViewModel
- LiveData
- DataBinding
- Screen Orientation
- Localization
- Vector Drawable
有几个注意点:
- DataBinding需要在gradle中开启,如果使用矢量图也需要开启向下兼容功能
android.defaultConfig.vectorDrawables.useSupportLibrary = true
:
- Demo中通过DataBinding将数据回绑到界面xml文件中,应该有更好的方式,后面再探索。
正如上面所说,ViewModel中保存着UIData,当Activity发生以下操作时,依然能临时保证其之前的状态:
- 进入后台
- 屏幕翻转
- 切换语言
但是当系统杀死进入后台的进程时,ViewModel也会被释放,当再次打开程序,ViewModel就被重建了,Activity之前的状态就会消失:
我们可以模拟此情况:
- 在设置中打开开发者模式:设置-> 关于模拟设备中,多次点击版本号后,在设置-> 系统中,就看到了开发者选项,打开不保留活动功能,就会在程序进入后台时杀死该程序了。
那如何在系统杀死了我们在后台的程序后,依然保存我们的状态数据呢?
当然我们可以借用Activity的onSaveInstanceState
方法来保存状态值予以解决:
public class MainActivity extends AppCompatActivity {
MyViewModel myViewModel;
ActivityMainBinding binding;
final static String KEY_TeamA_NAME = "a_number";
final static String KEY_TeamB_NAME = "b_number";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
if (savedInstanceState != null) {
myViewModel.getaTeamScore().setValue(savedInstanceState.getInt(KEY_TeamA_NAME));
myViewModel.getbTeamScore().setValue(savedInstanceState.getInt(KEY_TeamB_NAME));
}
binding.setData(myViewModel);
binding.setLifecycleOwner(this);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_TeamA_NAME,myViewModel.getaTeamScore().getValue());
outState.putInt(KEY_TeamB_NAME,myViewModel.getbTeamScore().getValue());
}
}
不过ViewModel在2019年后就增加了自带保存状态的功能:
在gradle依赖中加上:
implementation 'androidx.lifecycle:lifecycle-viewmodel-savedstate:2.3.0-alpha03'
ViewModel就会多了个构造函数, 写法就变成如下(Demo中以前的写法已注释了):
//public class MyViewModel extends ViewModel {
// private MutableLiveData<Integer> aTeamScore;
// private MutableLiveData<Integer> bTeamScore;
// private int aBack = 0;
// private int bBack = 0;
//
//
// public MutableLiveData<Integer> getaTeamScore() {
// if (aTeamScore == null) {
// aTeamScore = new MutableLiveData<>();
// aTeamScore.setValue(0);
// }
// return aTeamScore;
// }
//
// public MutableLiveData<Integer> getbTeamScore() {
// if (bTeamScore == null) {
// bTeamScore = new MutableLiveData<>();
// bTeamScore.setValue(0);
// }
// return bTeamScore;
// }
//
// public void aTeamAdd(int p) {
// aBack = aTeamScore.getValue();
// bBack = bTeamScore.getValue();
// aTeamScore.setValue(aTeamScore.getValue() + p);
// }
//
// public void bTeamAdd(int p) {
// aBack = aTeamScore.getValue();
// bBack = bTeamScore.getValue();
// bTeamScore.setValue(bTeamScore.getValue() + p);
// }
//
// public void reset() {
// aBack = aTeamScore.getValue();
// bBack = bTeamScore.getValue();
// aTeamScore.setValue(0);
// bTeamScore.setValue(0);
// }
//
// public void undo() {
// aTeamScore.setValue(aBack);
// bTeamScore.setValue(bBack);
// }
//}
public class MyViewModel extends ViewModel {
private int aBack = 0;
private int bBack = 0;
private SavedStateHandle handle;
public MyViewModel(SavedStateHandle handle) {
this.handle = handle;
}
public MutableLiveData<Integer> getaTeamScore() {
if (!handle.contains(MainActivity.KEY_TeamA_NAME)) {
handle.set(MainActivity.KEY_TeamA_NAME,0);
}
return handle.getLiveData(MainActivity.KEY_TeamA_NAME);
}
public MutableLiveData<Integer> getbTeamScore() {
if (!handle.contains(MainActivity.KEY_TeamB_NAME)) {
handle.set(MainActivity.KEY_TeamB_NAME,0);
}
return handle.getLiveData(MainActivity.KEY_TeamB_NAME);
}
public void aTeamAdd(int p) {
aBack = getaTeamScore().getValue();
bBack = getbTeamScore().getValue();
getaTeamScore().setValue(getaTeamScore().getValue() + p);
}
public void bTeamAdd(int p) {
aBack = getaTeamScore().getValue();
bBack = getbTeamScore().getValue();
getbTeamScore().setValue(getbTeamScore().getValue() + p);
}
public void reset() {
aBack = getaTeamScore().getValue();
bBack = getbTeamScore().getValue();
getaTeamScore().setValue(0);
getbTeamScore().setValue(0);
}
public void undo() {
getaTeamScore().setValue(aBack);
getbTeamScore().setValue(bBack);
}
}
而MainActivity中就不需要使用onSaveInstanceState
方法去保存状态了:
public class MainActivity extends AppCompatActivity {
MyViewModel myViewModel;
ActivityMainBinding binding;
final static String KEY_TeamA_NAME = "a_number";
final static String KEY_TeamB_NAME = "b_number";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
// myViewModel = ViewModelProviders.of(this).get(MyViewModel.class);
SavedStateViewModelFactory factory = new SavedStateViewModelFactory(getApplication(),this);
myViewModel = ViewModelProviders.of(this,factory).get(MyViewModel.class);
// if (savedInstanceState != null) {
// myViewModel.getaTeamScore().setValue(savedInstanceState.getInt(KEY_TeamA_NAME));
// myViewModel.getbTeamScore().setValue(savedInstanceState.getInt(KEY_TeamB_NAME));
// }
binding.setData(myViewModel);
binding.setLifecycleOwner(this);
}
// @Override
// protected void onSaveInstanceState(@NonNull Bundle outState) {
// super.onSaveInstanceState(outState);
// outState.putInt(KEY_TeamA_NAME,myViewModel.getaTeamScore().getValue());
// outState.putInt(KEY_TeamB_NAME,myViewModel.getbTeamScore().getValue());
// }
}
这样在程序退到后台,即使系统会杀死后台程序,当我们再次回到程序时,之前的状态依然得以保存。
但是如果我们重新启动程序或点击back键退出程序后再启动,以前的状态就会消失,这是我们可能就需要采用永久保存状态数据了。
首先,安卓有四种存储方式:
- Internal file storage. 内部文件存储。
- Extenal file storage. 外部文件存储。
- Shared preference. 也属于内部存储,主要存储简单数据。
- Databases.数据库。
接下来了解一下持久化保存方式之一的Shared preference的用法。
Shared preference
我们可以单独建个类,去实现保存和获取数据的功能:
public class MySharedData {
public int number = 0;
private Context context;
final static String CONTEXT_NAME = "context_name";
final static String NUMBER_KEY = "myNumber";
public MySharedData(Context context) {
this.context = context;
}
public void save() {
SharedPreferences sp = context.getSharedPreferences(CONTEXT_NAME,Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putInt(NUMBER_KEY,number);
editor.apply();
}
public int fetch() {
SharedPreferences sp = context.getSharedPreferences(CONTEXT_NAME,Context.MODE_PRIVATE);
int x = sp.getInt(NUMBER_KEY,0);
number = x;
return x;
}
}
在Activity中的具体使用:
MySharedData sharedData = new MySharedData((getApplicationContext()));
sharedData.number = 100;
sharedData.save();
int number = sharedData.fetch();
Log.d("tag","result: "+number);
上面的写法是将Activity中的context传到了MySharedData
中,context才能调用出SharedPreferences的api,实际上ViewModel中自带有Application,需要的是继承于AndroidViewModel:
public class MyAndroidViewModel extends AndroidViewModel {
final static String MY_KEY = "myKey";
final static String SHARED_NAME = "mySharedName";
private SavedStateHandle handle;
public MyAndroidViewModel(@NonNull Application application, SavedStateHandle handle) {
super(application);
this.handle = handle;
if (handle.contains(MY_KEY)) {
load();
}
}
public LiveData<Integer> getNumber() {
return handle.getLiveData(MY_KEY);
}
void load() {
SharedPreferences sp = getApplication().getSharedPreferences(SHARED_NAME, Context.MODE_PRIVATE);
int x = sp.getInt(MY_KEY,0);
handle.set(MY_KEY,x);
}
public void add(int x) {
handle.set(MY_KEY,getNumber().getValue() + x);
}
//持久化保存
public void save() {
SharedPreferences sp = getApplication().getSharedPreferences(SHARED_NAME,Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putInt(MY_KEY,getNumber().getValue());
editor.apply();
}
}
调用save时建议放到Activity中的onPause
方法节点中做统一保存,而不是在每次add操作时save,这样利于性能优化:
@Override
protected void onPause() {
super.onPause();
myAndroidViewModel.save();
}
网友评论