仅需6步,教你轻易撕掉app开发框架的神秘面纱(5):数据持久化
遇到的问题
有的时候程序中需要全局皆可访问的变量,比如:用户是否登录,用户个人信息(用户名,地区,生日),或者一些其他信息如:是否是首次登录,是否需要显示新手引导等等。
其中有些数据需要持久化到本地硬盘中,比如:
大多数应用,当用户第一次启动应用的时候,需要显示应用介绍和新手引导的页面。
而应用介绍只在第一次启动时显示。所以我们需要记录一个值表示当前是否已经显示过了应用介绍。并且每次在应用开启的时候检查这个值是否存在,如果存在则直接跳入主页面,否则就显示应用介绍。
另外新手引导这个东西是分步骤的,当用户第一次进入主页面的时候,可能会提示用户去做什么,比如提示用户注册,登录之类的。可能会有很多步的新手引导。
这时候问题就来了,如果新手引导一共有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.hBaseModel.m
仅需6步,教你轻易撕掉app开发框架的神秘面纱(5):数据持久化相关推荐
- 仅需6步,教你轻易撕掉app开发框架的神秘面纱(1):确定框架方案
遇到的问题 做游戏的时候用的是cocos2dx+lua,游戏开发自有它的一套框架机制.而现在公司主要项目要做android和iOS应用.本文主要介绍如何搭建简单易用的App框架. 如何解决 对 ...
- 仅需6步,教你轻易撕掉app开发框架的神秘面纱(4):网络模块的封装
程序框架确定了,还需要封装网络模块. 一个丰富多彩的APP少不了网络资源的支持,毕竟用户数据要存储,用户之间也要交互,用户行为要统计等等. 使用开源框架 俗话说得好,轮子多了路好走,我们不需要自己造轮 ...
- 仅需6步,教你轻易撕掉app开发框架的神秘面纱(3):构造具有个人特色的MVP模式
1. MVP的问题 之前我们说过MVP模式最大的问题在于:每写一个Activity/Fragment需要写4个对应的文件,对于一个简易的app框架来说太麻烦了.所以我们需要对MVP进行一定的简化. 关 ...
- 仅需6步,教你轻易撕掉app开发框架的神秘面纱(6):各种公共方法及工具类的封装
为什么要封装公共方法 封装公共方法有2方面的原因: 一是功能方面的原因:有些方法很多地方都会用,而且它输入输出明确,并且跟业务逻辑无关.比如检查用户是否登录,检查某串数字是否为合法的手机号.像这种方法 ...
- 仅需6步,教你轻易撕掉app开发框架的神秘面纱(2):MVP比MVC更好吗
对于程序框架的选择,由于android天然的MVC,本来不需要另外设计直接使用即可.但是我更加钟情于MVP模式,对于其将ui完全与业务逻辑分离的思路很赞同. 那么什么是业务逻辑?个人认为,对数据(即M ...
- 怎么快速修改gif尺寸?仅需三步教你改gif大小
很多时候我们从网上下载的gif动图或者是从电影.电视剧中截取的高清gif动图尺寸过大不方便传输,想要对gif图片尺寸修改的时候应该如何调整gif尺寸呢?很简单,使用[GIF中文网]的gif改大小(ht ...
- python爬虫excel数据_最简单的爬数据方法:Excel爬取数据,仅需6步
原标题:最简单的爬数据方法:Excel爬取数据,仅需6步 在看到这篇文章的时候,大家是不是都还停留在对python爬虫的迷恋中,今天就来教大家怎样使用微软的Excel爬取一个网页的后台数据,注:此方法 ...
- 隐藏esp_仅需一分钟教你看懂汽车内的隐藏功能,哪些功能是你不知道的?
车内的按键多种多样,而且越高档的车,按键就越多.除了少数国产车,绝大部分车辆的按键标识都是用英文字母表示,从而导致不少车主只能通过查看说明书才知道是什么意思. 今天小编整理了车内各种按键标识,不是很清 ...
- php 商城套餐搭配功能,速卖通商品搭配套餐功能已上线!设置速卖通搭配套餐仅需三步...
据雨果网获悉,速卖通商品搭配套餐功能已于 10 月 19 日上线.商品搭配套餐的主要功能及作用,主要是帮助速卖通的卖家,通过自行选择商品,设置不同商品间搭配优惠促销价格,提高商品推广内容的丰富性及专业 ...
最新文章
- 【SAP-PM模块】服务采购业务流程
- Android 启动模式简介
- 漫步数学分析二十九——幂级数
- 清除sqlserver日志方法(不适合always on)
- hdu 2873 Bomb Game 博弈论
- “球鞋一面墙,堪比一套房” 央视评炒鞋乱象:呼吁“鞋穿不炒”
- Android设备新型恶意软件,融合银行木马、键盘记录器和移动勒索软件等功能
- Android7.1 Audio的FW和HAL层dump PCM数据(三十七)
- 第四季-专题4-嵌入式文件系统
- 此次边路调整系统推荐射手走哪路_王者荣耀:射手调整前瞻,阿离回归边路!新英雄/皮肤下周上架...
- ue4缓存位置怎么改_[UE4]动态液体材质浅谈
- Unity写lua代码的vs插件:BabeLua
- 数字图像处理-直方图均衡化,直方图规定化
- lora网关软件设计_LoRa网关芯片SX1301IMLTRT网关设计资料
- 【知识管理】知识管理系统功能构件简介
- AcrGIS 做成本距离分析时提示ERROR 999999:无法启动配置 RasterCommander.ImageServer
- 源码 源代码下载 - www.pudn.com 程序员联合开发网
- [FreeBSD] 安全加固
- 亚马逊pii权限开通,公共开发者申请材料;sp api开通
- FileSystemWatcher 基础用法