仅需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
相关文章:

响应因特网端口ping命令_如何使用Ping命令识别基本的Internet问题
响应因特网端口ping命令Next time you call your help desk, do you want to wow them with your networking knowledge? Using a command called “ping”, built right into your existing Mac, Windows, or Linux computer, will help identify basic connection problems.…

Android 常见工具类封装
1,MD5工具类: public class MD5Util {public final static String MD5(String s) {char hexDigits[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,a, b, c, d, e, f };try {byte[] btInput s.getBytes();// 获得MD5摘要算法的 MessageDigest 对象MessageDigest md…
keras系列︱图像多分类训练与利用bottleneck features进行微调(三)
引自:http://blog.csdn.net/sinat_26917383/article/details/72861152 中文文档:http://keras-cn.readthedocs.io/en/latest/ 官方文档:https://keras.io/ 文档主要是以keras2.0。 训练、训练主要就”练“嘛,所以堆几个案例就知…

LIKE 操作符
LIKE 操作符LIKE 操作符用于在 WHERE 子句中搜索列中的指定模式。SQL LIKE 操作符语法SELECT column_name(s)FROM table_nameWHERE column_name LIKE pattern原始的表 (用在例子中的):Persons 表:IdLastNameFirstNameAddressCity1AdamsJohnOxford StreetLondon2Bush…

服务器云ide_语言服务器协议如何影响IDE的未来
服务器云ideThe release of Visual Studio Code single-handedly impacted the developer ecosystem in such a way that theres no going back now. Its open source, free, and most importantly, a super powerful tool. Visual Studio Code的发布以一种无可匹敌的方式对开发…

仅需6步,教你轻易撕掉app开发框架的神秘面纱(6):各种公共方法及工具类的封装
为什么要封装公共方法 封装公共方法有2方面的原因: 一是功能方面的原因:有些方法很多地方都会用,而且它输入输出明确,并且跟业务逻辑无关。比如检查用户是否登录,检查某串数字是否为合法的手机号。像这种方法就应该封…
MySQL优化配置之query_cache_size
原理MySQL查询缓存保存查询返回的完整结果。当查询命中该缓存,会立刻返回结果,跳过了解析,优化和执行阶段。 查询缓存会跟踪查询中涉及的每个表,如果这写表发生变化,那么和这个表相关的所有缓存都将失效。 但是随着服…

request.getSession()
request.getSession(); 与request.getSession(false);区别 服务器把session信息发送给浏览器 浏览器会将session信息存入本地cookie中 服务器本地内存中也会留一个此session信息 以后用户发送请求时 浏览器都会把session信息发送给服务器 服务器会依照浏览器发送过来的se…

alpine 交互sh_在这个免费的交互式教程中学习Alpine JS
alpine 交互shAlpine.js is a rugged, minimal framework for composing Javascript behavior in your markup. Thats right, in your markup! Alpine.js是一个坚固的最小框架,用于在标记中构成Javascript行为。 是的,在您的标记中! It allo…
浅谈 MVP in Android
一、概述 对于MVP(Model View Presenter),大多数人都能说出一二:“MVC的演化版本”,“让Model和View完全解耦”等等。本篇博文仅是为了做下记录,提出一些自己的看法,和帮助大家如何针对一个Acti…

test markdown
test test public void main(String[] args){System.out.println("test"); } 转载于:https://www.cnblogs.com/cozybz/p/5427053.html

java开发工具对比eclipse·myeclipse·idea
eclipse:不说了,习惯了 myeclipse:MyEclipse更适合企业开发者,更团队开发 idea:idea更适合个人开发者,细节优化更好转载于:https://www.cnblogs.com/gjack/p/8136964.html

软件测试质量过程检测文档_如何编写实际上有效的质量检查文档
软件测试质量过程检测文档A software product is like an airplane: it must undergo a technical check before launch.软件产品就像飞机:必须在发射前经过技术检查。 Quality Assurance is a necessary step towards launching a successful software product. I…

Android深度探索--HAL与驱动开发----第一章读书笔记
1.1 Android拥有非常完善的系统构架可以分为四层: 第一层:Linux内核。主要包括驱动程序以及管理内存、进程、电源等资源的程序 第二层:C/C代码库。主要包括Linux的.so文件以及嵌入到APK程序中的NDK代码 第三层:android SDK API …

[NOI2011]Noi嘉年华
题解:我们设计状态方程如下: num[i][j]表示从时间i到j中有多少个 pre[i][j]表示时间1~i中,A选了j个时的B能选的数量的最大值. nex[i][j]表示时间i~cnt中,A选了j个时的B能选的数量的最大值. mus[i][j]表示从时间i到j的保证选时,A和B选的数量中的较小值的最大值. ①对于num数组直…
只有20%的iOS程序员能看懂:详解intrinsicContentSize 及 约束优先级/content Hugging/content Compression Resistance
在了解intrinsicContentSize之前,我们需要先了解2个概念: AutoLayout在做什么约束优先级是什么意思。 如果不了解这两个概念,看intinsic content size没有任何意义。 注:由于上面这几个概念都是针对UIView或其子类(UILabel&…

redux rxjs_可观察的RxJS和Redux入门指南
redux rxjsRedux-Observable is an RxJS-based middleware for Redux that allows developers to work with async actions. Its an alternative to redux-thunk and redux-saga.Redux-Observable是Redux的基于RxJS的中间件,允许开发人员使用异步操作。 它是redux-t…

javascript数组排序和prototype详解
原型的概念::原型对象里的所有属性和方法 被所有构造函数实例化出来的对象所共享,类似于java中的 static 正因为共享所以单一的操作 就会影响了全局,因此使用时需注意 基于prototype:为数组扩展方法 //获取数组最大值function get…
Qt 在Label上面绘制罗盘
自己写的一个小小的电子罗盘的一个小程序,不过是项目的一部分,只可以贴绘制部分代码 效果如下图 首先开始自己写的时候,虽然知道Qt 的坐标系是从左上角开始的,所以,使用了算法,在绘制后,在移动回…

终极方案!解决正确设置LaunchImage后仍然不显示的问题
对于如何设置LaunchImage,网络上有各种各样的教程。 主要分2点: 1. 正确设置图片尺寸 2. 取消LaunchScreen.xib 但是经过上述步骤之后,你觉得完全没有问题了,但是仍然无法显示LaunchImage。 或者,你在多个模拟器上…

c# 持续集成 单元测试_如何在不进行单元测试的情况下设置持续集成
c# 持续集成 单元测试Do you think continuous integration is not for you because you have no automated tests? Or no unit tests at all? Not true. Tests are important. But there are many more aspects to continuous integration than just testing. Lets see what…

Handlebars模板引擎
介绍 Handlebars 是 JavaScript 一个语义模板库,通过对view和data的分离来快速构建Web模板。它采用"Logic-less template"(无逻辑模版)的思路,在加载时被预编译,而不是到了客户端执行到代码时再去编译&#…

字符集图标制作
字符集图标: 将网页上常见的icon做成font(字符集),以字体的方式插入到网页上,作用是减轻服务器负担,减少宽带。 我最常在这两个网站上下载字体图标: https://icomoon.io/app/#/select https://w…

Adobe源码泄漏?3行代码搞定,Flash动画无缝导入Android/iOS/cocos2dx(一)
[注] iOS代码已重构,效率提升90%,200层动画不卡。[2016.10.27] 项目介绍 项目名称:FlashAnimationToMobile 源码。 使用方法点这里。 这是一个把flash中的关键帧动画(不是序列帧)导出,然后在iOS/Android原生应用中解…

背景图像位置css_CSS背景图像大小教程–如何对整页背景图像进行编码
背景图像位置cssThis tutorial will show you a simple way to code a full page background image using CSS. And youll also learn how to make that image responsive to your users screen size.本教程将向您展示一种使用CSS编写整页背景图像的简单方法。 您还将学习如何使…

复习es6-解构赋值+字符串的扩展
1. 数组的解构赋值 从数组中获得变量的值,给对应的声明变量赋值,,有次序和对应位置赋值 解构赋值的时候右边必须可以遍历 解构赋值可以使用默认值 惰性求值,当赋值时候为undefined时候,默认是个函数就会执行函数 2.对象解构赋值 与…

Adobe源码泄漏?3行代码搞定,Flash动画无缝导入Android/iOS/cocos2dx(二)
[注] iOS代码已重构,效率提升90%,200层动画不卡。[2016.10.27] 上一篇 点此阅读 简要介绍了FlashToAnimation的功能,也就是将flash动画无缝导入到Android/iOS及cocos2dx中运行, 这一篇介绍这个库的使用方法。点此查看源码。 准备工作 首先…

the user operation is waiting for building workspace to complete解决办法
如果你在开发android应用程序中总是出现一个提示,显示“the user operation is waiting for "building workspace" to complete”,解决办法如下: 1.选择菜单栏的“Project”,然后把菜单栏中“Build Automatically”前面的对钩去掉。…

ios开发趋势_2020年将成为iOS应用开发的主要趋势
ios开发趋势Technology has always brought something new with time. And with these ever-changing technologies, you need to stay updated to get all the benefits from whats new. 随着时间的流逝,技术总是带来新的东西。 借助这些不断变化的技术,…

http 权威指南 目录
第一部分 HTTP:Web的基础 第1章 HTTP概述 1.1 HTTP——因特网的多媒体信使 1.2 Web客户端和服务器 1.3 资源 1.3.1 媒体类型 1.3.2 URI 1.3.3 URL 1.3.4 URN 1.4 事务 1.4.1 方法 1.4.2 状态码 1.4.3 Web页面中可以包含多个对象 1.5 报文 1.6 连接 1.6.1 TCP/IP 1.6…