遇到的问题
有的时候程序中需要全局皆可访问的变量,比如:用户是否登录,用户个人信息(用户名,地区,生日),或者一些其他信息如:是否是首次登录,是否需要显示新手引导等等。
其中有些数据需要持久化到本地硬盘中,比如:
大多数应用,当用户第一次启动应用的时候,需要显示应用介绍和新手引导的页面。
而应用介绍只在第一次启动时显示。所以我们需要记录一个值表示当前是否已经显示过了应用介绍。并且每次在应用开启的时候检查这个值是否存在,如果存在则直接跳入主页面,否则就显示应用介绍。
另外新手引导这个东西是分步骤的,当用户第一次进入主页面的时候,可能会提示用户去做什么,比如提示用户注册,登录之类的。可能会有很多步的新手引导。
这时候问题就来了,如果新手引导一共有5步,而用户只看了新手引导前2步之后就退出程序。当他重新打开应用的时候。前两步就不应该显示了。
所以此时需要记录下当前新手引导已经走到了第几步。当需要显示新手引导的时候要检查是否用户已经看过了这个新手引导。如果看过了则不显示引导。
另外,有些全局数据则不需要持久化到硬盘,比如:
用户是否登录了,用户上次网络请求的时间,服务器当前时间,等等。
解决方案
从需求上看,这些数据都是简单数据,并且无需担心安全问题,因此可以使用系统自带的键值存储系统来存储这些值。
- android 使用 SharedPreferece。
- iOS 使用 NSUserDefaults。
许多人可能会认为,系统调用谁不会,是个人都知道,哪有必要写一个单章出来,还推到框架的高度。
系统调用直接使用确实很简单,也能得到正确结果。但是问题也是显而易见的:
- 规范:虽然是简单的系统调用代码,但是不同的人使用仍然会写出不同的代码,为了底层代码的一致,所以把系统调用封装起来。
- 多态:封装系统调用的另一个目的是,如果哪天不能用键值存储系统,改成数据库存取,则只需修改一处。
- 防止滥用:因为是键值存储,所以,对于键的取名,可能就比较随意了。封装之后,程序员需要把键写在同一个或几个文件中,防止命名重复,并且便于review代码。
对于android来说,持久化数据有着更深的意义,因为android系统的原因,导致应用进入后台后,重新回到前台,静态变量会变为null,所以对于这种数据也可以使用数据持久化来解决问题。
由此可知,对于任何系统调用的封装都是有意义的。
实现代码
在这里我们需要定义一个父类,把系统调用全部封装在父类中,子类直接调用save/get方法即可。
//android: BaseModel.java
public class BaseModel extends BaseRecord {
private static final String TAG = "BaseModel";
private static final String OBJ_PREFIX = "__object__";//防止重名,因此添加的特殊前缀
private static final String SHAREDPREFERENCE_FILE = "__sharedpreference_file__";//防止重名,因此添加的特殊前缀
public void save(String key, float value){
SharedPreferences sharedPreferences = Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sharedPreferences.edit();
edit.putFloat(key, value);
edit.apply();
edit.commit();
}
public void save(String key, int value){
SharedPreferences sharedPreferences = Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sharedPreferences.edit();
edit.putInt(key, value);
edit.apply();
edit.commit();
}
public void save(String key, String value){
SharedPreferences sharedPreferences = Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE);
SharedPreferences.Editor edit = sharedPreferences.edit();
edit.putString(key, value);
edit.apply();
edit.commit();
}
public void save(String key, Serializable value){
FileOutputStream fos = null;
try {
fos = Util.context.openFileOutput(OBJ_PREFIX + TAG + key + OBJ_PREFIX, Context.MODE_PRIVATE);
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(value);
} catch (FileNotFoundException e) {
LogUtil.e(TAG, e);
} catch (IOException e) {
LogUtil.e(TAG, e);
} finally {
if(fos != null){
try {
fos.close();
} catch (IOException e) {
LogUtil.e(TAG, e);
}
}
}
}
public String getString(String key){
return Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE).getString(key, "null");
}
public int getInt(String key){
return Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE).getInt(key, Integer.MAX_VALUE);
}
public float getFloat(String key){
return Util.context.getSharedPreferences(SHAREDPREFERENCE_FILE, Context.MODE_PRIVATE).getFloat(key, Integer.MAX_VALUE);
}
public Serializable getSerializable(String key){
FileInputStream fis = null;
try {
fis = Util.context.openFileInput(OBJ_PREFIX + TAG + key + OBJ_PREFIX);
ObjectInputStream ois = new ObjectInputStream(fis);
return (Serializable) ois.readObject();
} catch (FileNotFoundException e) {
LogUtil.e(TAG, e);
} catch (StreamCorruptedException e) {
LogUtil.e(TAG, e);
} catch (IOException e) {
LogUtil.e(TAG, e);
} catch (ClassNotFoundException e) {
LogUtil.e(TAG, e);
} finally {
if (fis != null){
try {
fis.close();
} catch (IOException e) {
LogUtil.e(TAG, e);
}
}
}
return null;
}
}
//iOS: BaseModel.h
//单例声明的宏,可在子类.h文件中直接使用
#define CREATE_SINGLE_INSTANCE_IN_MODEL_H +(instancetype)getInstance
//单例声明的宏,可在子类.m文件中直接使用
#define CREATE_SINGLE_INSTANCE_IN_MODEL_M(modelType) \
+(instancetype)getInstance{ \
static modelType *sModel; \
if (![modelType isSubclassOfClass: [BaseModel class]]) { \
return nil; \
} \
if (!sModel) { \
sModel = [[modelType alloc] init]; \
} \
return sModel; \
}
//下面是setget方法的宏,可在子类直接使用,自动调用父类save/get。
//需要注意的是:这种写法可能有些问题。这样变量都成了强引用。写了copy也无用。不过没有影响。
//具体要看编译器如何实现。它可以避免此种问题,也可以不避免。
#define CREATE_SETGET_IN_MODEL_H(type, copyOrStrong, param) \
@property (nonatomic, copyOrStrong) type param
#define CREATE_SETGET_IN_MODEL_M(type, param, upperFirstParam) \
\
@synthesize param; \
-(void)set##upperFirstParam:(type)p##param{ \
param = p##param; \
[self save:@#param data:param]; \
} \
\
-(type)param{ \
if(!param){ \
type saved = [self get:@#param]; \
if (saved) { \
param = saved; \
} \
} \
return param; \
}
@interface BaseModel : NSObject
-(void) save: (NSString *)key data: (id)data;
-(id) get: (NSString *)key;
@end
//iOS: BaseModel.m
#import "BaseModel.h"
@implementation BaseModel
//iOS只能存储简单数据,NSString, NSNumber, NSArray, NSDictionary。否则会报错。
-(void) save:(NSString *)key data:(id)data{
[[NSUserDefaults standardUserDefaults] setObject:data forKey:key];
}
-(id) get:(NSString *)key{
return [[NSUserDefaults standardUserDefaults] objectForKey:key];
}
@end
代码清单:
android:
BaseModel.java
iOS:
BaseModel.h
BaseModel.m