到目前为止,煎蛋的Android项目算是告一段落了,功能基本都已完成,那么今天,我就介绍一下在煎蛋这个项目里,是怎么完成数据缓存功能的。想看代码的请戳煎蛋项目的GITHUB地址

转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992

  • 缓存功能的解决方案
  • 配置GreenDao
  • 实现缓存功能
  • 其他资料

缓存功能的解决方案

因为算是一个阅读类的应用,所以说如果在无网络情况下,用户打开App还能看到内容的话,属于比较好的用户体验。那么,这就涉及到本地缓存了。

本地缓存有好多解决方案,比如说存数据库里面,或者是存文件里面,甚至可以存在SharedPrefrence里面。我个人更倾向于保存在数据库里面,因为这样进行一些基本操作比较简单。

在煎蛋项目中,缓存数据分成两部分,一部分是请求接口的数据,包括图片url、发布者、段子等等文本类型数据,另外一部分则是图片缓存了。因为UIL已经完成了图片本地缓存功能,所以说,我们只需要缓存请求接口返回的数据就可以了。

既然是缓存在数据库,我们就可以使用Sqlite了,但是直接用Sqlite吧,比较麻烦,那么有木有好用的ORM框架呢?当然有,GreenDao就是比较好的一个ORM框架。因为之前没试过怎么用,就趁着这次机会用用吧,但是真用起来,才发现配置起来确实麻烦,所以下面就介绍下如何使用GreenDao来完成数据库缓存,这应该是最新的GreenDao的使用介绍了。

配置GreenDao

GreenDao使用的时候,需要添加一个辅助项目,来生成数据库的实体类和Dao类。
流程如下:
1. 选中项目
2. 右键
3. new Module
4. 选择类型为Java Library
5. 然后按照下面自己填写,左边是完成的,右边是你要填写的

这样写了之后,我们就有了一个辅助项目了。下面,我们就需要为我们的附注项目添加依赖,所以呢,打开build.gradle文件,然后像下面一样,把我们的依赖库 greendao-generator:1.3.1 添加进去

apply plugin: 'java'repositories {mavenLocal()mavenCentral()
}dependencies {compile 'de.greenrobot:greendao-generator:1.3.1'
}sourceSets {main {java {srcDir 'src/main/java'}}
}
artifacts {archives jar
}

然后就可以在创建的类文件里面,写上下面的代码。当然了,这是煎蛋项目里面的,其他用法你需要自己google:

/*** 用来为GreenDao框架生成Dao文件*/
public class MyDaoGenerator {//辅助文件生成的相对路径public static final String DAO_PATH = "../app/src/main/java-gen";//辅助文件的包名public static final String PACKAGE_NAME = "com.socks.greendao";//数据库的版本号public static final int DATA_VERSION_CODE = 1;public static void main(String[] args) throws Exception {Schema schema = new Schema(DATA_VERSION_CODE, PACKAGE_NAME);addCache(schema, "JokeCache");addCache(schema, "FreshNewsCache");addCache(schema, "PictureCache");addCache(schema, "SisterCache");addCache(schema, "VideoCache");//生成Dao文件路径new DaoGenerator().generateAll(schema, DAO_PATH);}/*** 添加不同的缓存表* @param schema* @param tableName*/private static void addCache(Schema schema, String tableName) {Entity joke = schema.addEntity(tableName);//主键id自增长joke.addIdProperty().primaryKey().autoincrement();//请求结果joke.addStringProperty("result");//页数joke.addIntProperty("page");//插入时间,暂时无用joke.addLongProperty("time");}
}

因为我们需要缓存所有的功能模块,所以呢,调用addCache方法,然后传进去表名就ok啦。

注意在addCache方法里面,我们就四个字段,主键id,接口请求数据result,页码page,添加时间time。因为这几个功能模块的数据很相似,所以这个方法可以复用,如果你需要其他字段,自己使用addXXXProperty()即可。

好了,现在我们的辅助项目就完成了,下面,就需要运行起来,生成辅助文件了。

打开工具栏的这个窗口

点击Edit Configurations,在打开的界面里面,点击左上角的+号,然后选择Application,然后按照下面的提示,把对应位置属性设置好

设置好了,点击OK,然后这时候在工具栏里面,就可以选中我们新创建的项目,然后点击运行,出现下面的提示,就说明我们的辅助实体类和Dao类创建好了。

不信?你打开我们的项目看看,是不是都创建好了~

Ok,到了这一步,我们的任务已经完成50%了。因为找了很多资料,中文英文的都有,要不就是版本太老,要不就是说的不对,就是没有一个成功的,花了好长时间才完成GreenDao的环境搭配,希望这一步对你有所帮助。

实现缓存功能

其实配置好GreenDao之后,后面的工作就是小意思了。

我先说下煎蛋项目里面缓存的思路,当然了,这种实现比较简单,你完全可以扩展。

首先,当有网络连接的时候,我们每次获取新数据的时候,都需要把获取的数据和对应的页码保存进数据库。如果用户执行刷新操作,之前的数据就没用了,直接清除,然后再次把新数据保存起来。当手机是无网络状况的时候,根据页码直接从缓存数据库拿出数据,然后展现出来。对于段子这种纯文本文件,可以让用户查看缓存的文本内容,而对于无聊图这种图片文件,用于有UIL的文件缓存,所以根据我们缓存的url地址,也可以查看图片。

好了,整理了一下思路,下面说一下具体代码实现。

首先,使用GreenDao,我们需要重点关注DaoSession和DaoMaster这两个类,因为我们如果想获取到我们的Dao类,就需要用DaoSession获取。

为了节省资源,官方推荐我们只需要保留一个DaoSession和DaoMaster的实例即可,所以,我们可以直接在Application里面声明称静态常量,来保证一个实例的存在,代码如下:

public class AppAplication extends Application {private static Context mContext;private static DaoMaster daoMaster;private static DaoSession daoSession;@Overridepublic void onCreate() {super.onCreate();mContext = this;initImageLoader();Logger.init().hideThreadInfo();}public static Context getContext() {return mContext;}/*** 初始化ImageLoader*/private void initImageLoader() {ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(this).tasksProcessingOrder(QueueProcessingType.LIFO)
//              .writeDebugLogs().build();ImageLoader.getInstance().init(config);}public static DaoMaster getDaoMaster(Context context) {if (daoMaster == null) {DaoMaster.OpenHelper helper = new DaoMaster.DevOpenHelper(context, DateBaseInfo.DB_NAME, null);daoMaster = new DaoMaster(helper.getWritableDatabase());}return daoMaster;}public static DaoSession getDaoSession(Context context) {if (daoSession == null) {if (daoMaster == null) {daoMaster = getDaoMaster(context);}daoSession = daoMaster.newSession();}return daoSession;}}

请无视其他无关代码。当然了,如果你需要线程安全,你可以再加个同步锁什么的。

因为所有的缓存逻辑基本相同,所以我抽取了一个Cache的基类BaseCacheUtil,代码如下:

public abstract class BaseCacheUtil<T> {protected static DaoSession mDaoSession;public abstract void clearAllCache();public abstract ArrayList<T> getCacheByPage(int page);public abstract void addResultCache(String result, int page);}

所有子类都必须实现这三个方法,完成数据的清空、添加和获取操作。比如,我们以JokeCacheUtil为例:

public class JokeCacheUtil extends BaseCacheUtil {private static JokeCacheUtil instance;private static JokeCacheDao mJokeCacheDao;private JokeCacheUtil() {}public static JokeCacheUtil getInstance(Context context) {if (instance == null) {synchronized (JokeCacheUtil.class) {if (instance == null) {instance = new JokeCacheUtil();}}mDaoSession = AppAplication.getDaoSession(context);mJokeCacheDao = mDaoSession.getJokeCacheDao();}return instance;}/*** 清楚全部缓存*/public void clearAllCache() {mJokeCacheDao.deleteAll();}/*** 根据页码获取缓存数据** @param page* @return*/@Overridepublic ArrayList<Joke> getCacheByPage(int page) {QueryBuilder<JokeCache> query = mJokeCacheDao.queryBuilder().where(JokeCacheDao.Properties.Page.eq("" + page));if (query.list().size() > 0) {return (ArrayList<Joke>) JSONParser.toObject(query.list().get(0).getResult(),new TypeToken<ArrayList<Joke>>() {}.getType());} else {return new ArrayList<Joke>();}}/*** 添加Jokes缓存** @param result* @param page*/@Overridepublic void addResultCache(String result, int page) {JokeCache jokeCache = new JokeCache();jokeCache.setResult(result);jokeCache.setPage(page);jokeCache.setTime(System.currentTimeMillis());mJokeCacheDao.insert(jokeCache);}}

在这里使用了线程安全的单例模式。那么我们在代码里面怎么用呢?很简单,首先看我们改造之后的获取方法

public void loadFirst() {page = 1;loadDataByNetworkType();}public void loadNextPage() {page++;loadDataByNetworkType();}/*** 根据不同的网络状态选择不同的加载策略*/private void loadDataByNetworkType() {if (NetWorkUtil.isNetWorkConnected(getActivity())) {loadData();} else {loadCache();}}

之前的获取数据方法,都换成了loadDataByNetworkType(),然后根据网络情况选择不同的加载策略,loadDate()和之前完全一样,loadCache()代码如下

private void loadCache() {google_progress.setVisibility(View.GONE);mLoadFinisCallBack.loadFinish(null);if (mSwipeRefreshLayout.isRefreshing()) {mSwipeRefreshLayout.setRefreshing(false);}JokeCacheUtil jokeCacheUtil = JokeCacheUtil.getInstance(getActivity());if (page == 1) {mJokes.clear();ShowToast.Short(ToastMsg.LOAD_NO_NETWORK);}mJokes.addAll(jokeCacheUtil.getCacheByPage(page));notifyDataSetChanged();}

我们从缓存中获取数据,然后添加给适配器,然后刷新即可。

有的同学可能注意到了,那么我们什么时候添加的缓存呀?

因为段子这个功能,需要请求两次接口,第一次是数据,第二次是评论数量,所以我们只能在获取到评论数量之后,再缓存我们的数据,就像下面这样:

private void getCommentCounts(final ArrayList<Joke> jokes) {StringBuilder sb = new StringBuilder();for (Joke joke : jokes) {sb.append("comment-" + joke.getComment_ID() + ",");}executeRequest(new Request4CommentCounts(CommentNumber.getCommentCountsURL(sb.toString()), new Response.Listener<ArrayList<CommentNumber>>() {@Overridepublic void onResponse(ArrayList<CommentNumber> response) {google_progress.setVisibility(View.GONE);if (mSwipeRefreshLayout.isRefreshing()) {mSwipeRefreshLayout.setRefreshing(false);}mLoadFinisCallBack.loadFinish(null);for (int i = 0; i < jokes.size(); i++) {jokes.get(i).setComment_counts(response.get(i).getComments() + "");}if (page == 1) {mJokes.clear();//首次正常加载之后,清空之前的缓存JokeCacheUtil.getInstance(getActivity()).clearAllCache();}mJokes.addAll(jokes);notifyDataSetChanged();//加载完毕后缓存JokeCacheUtil.getInstance(getActivity()).addResultCache(JSONParser.toString(jokes),page);}}, new Response.ErrorListener() {@Overridepublic void onErrorResponse(VolleyError error) {ShowToast.Short(ToastMsg.LOAD_FAILED);mLoadFinisCallBack.loadFinish(null);google_progress.setVisibility(View.GONE);if (mSwipeRefreshLayout.isRefreshing()) {mSwipeRefreshLayout.setRefreshing(false);}}}));}

还有个问题,就是从数据库解析缓存数据的时候,因为请求的数据,在经过我们解析之后全都转换成了对象,然后我们直接将对象转换成json数据存入的数据库,那么我们再从数据库中把数据拿出来之后,就不能简单的按照之前的解析方法去解析了,有可能需要单独写一个解析方法,比如说新鲜事模块就需要两套解析,而段子模块,因为请求之后的数据直接就是json形式,所以只需要一套即可。

新鲜事的两套解析代码如下

public static ArrayList<FreshNews> parse(JSONArray postsArray) {ArrayList<FreshNews> freshNewses = new ArrayList<>();for (int i = 0; i < postsArray.length(); i++) {FreshNews freshNews = new FreshNews();JSONObject jsonObject = postsArray.optJSONObject(i);freshNews.setId(jsonObject.optString("id"));freshNews.setUrl(jsonObject.optString("url"));freshNews.setTitle(jsonObject.optString("title"));freshNews.setDate(jsonObject.optString("date"));freshNews.setComment_count(jsonObject.optString("comment_count"));freshNews.setAuthor(Author.parse(jsonObject.optJSONObject("author")));freshNews.setCustomFields(CustomFields.parse(jsonObject.optJSONObject("custom_fields")));freshNews.setTags(Tags.parse(jsonObject.optJSONArray("tags")));freshNewses.add(freshNews);}return freshNewses;}public static ArrayList<FreshNews> parseCache(JSONArray postsArray) {ArrayList<FreshNews> freshNewses = new ArrayList<>();for (int i = 0; i < postsArray.length(); i++) {FreshNews freshNews = new FreshNews();JSONObject jsonObject = postsArray.optJSONObject(i);freshNews.setId(jsonObject.optString("id"));freshNews.setUrl(jsonObject.optString("url"));freshNews.setTitle(jsonObject.optString("title"));freshNews.setDate(jsonObject.optString("date"));freshNews.setComment_count(jsonObject.optString("comment_count"));freshNews.setAuthor(Author.parse(jsonObject.optJSONObject("author")));freshNews.setCustomFields(CustomFields.parseCache(jsonObject.optJSONObject("custom_fields")));freshNews.setTags(Tags.parseCache(jsonObject.optJSONObject("tags")));freshNewses.add(freshNews);}return freshNewses;}

其他的代码都很相似了,虽然说起来很简单,但是在做的时候,在这个坑里耽误了一些时间,以此为戒。

其他资料

GreenDao官网
在Android Studio里使用GreenDao

【凯子哥带你做高仿】“煎蛋”Android版的高仿及优化(三)——使用GreenDao实现本地Sqlite缓存相关推荐

  1. 【凯子哥带你夯实应用层】都说“知乎”逼格高,我们来实现“知乎”回答详情页动画效果

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 2014已经远去,2015年的目标很简单,就是继续熟悉Android的上层API,虽然偶尔会为了某个问题去研 ...

  2. 【凯子哥带你做高仿】“煎蛋”Android版的高仿及优化(二)——大图显示模式、评论“盖楼”效果实现详解

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 在前一篇文章中,我们学习了如何进行逆向工程和TcpDump进行抓包,获取我们的数据接口,那么有了数据之后,我 ...

  3. 【凯子哥带你学Android】Andriod性能优化之列表卡顿——以“简书”APP为例

    这几天闲得无聊,就打开手机上的开发者模式里面的"GPU过度绘制"功能,看看别家的App做的咋样,然后很偶然的打开了"简书",然后就被它的过度绘制惊呆了,于是写了 ...

  4. 【凯子哥带你学Framework】Activity界面显示全解析

    前几天凯子哥写的Framework层的解析文章<Activity启动过程全解析>,反响还不错,这说明"写让大家都能看懂的Framework解析文章"的思想是基本正确的. ...

  5. 【凯子哥带你夯实应用层】Android的Google官方设计指南(上)

    Android 设计规范 时间 2015.3.2 版本 V1.0 翻译 杨鹏 整理 赵凯强 本文章是我公司一个大牛之前的公司同事翻译的Android的Google官方设计指导,经过我整理而成,分享给大 ...

  6. 【凯子哥带你夯实应用层】使用ActionMode实现有删除动画的多选删除功能

    转载请注明出处:http://blog.csdn.net/zhaokaiqiang1992 ActionMode是3.0之后,官方推荐的一种上下文菜单的实现方式,在之前一直用的是Context Men ...

  7. 仿煎蛋iOS项目的准备(0)

    煎蛋项目的介绍: 其主要内容翻译自其他语言的网站,目的是为了让中文网友了解其他国家的信息 .煎蛋的主要栏目包括:小游戏.小学堂.发霉啦.无聊图.妹子图.走近科学.Geek.设计快读等. 1. 写这个项 ...

  8. 高通ims架构android,深度揭密高通4/5G移动基带消息系统和状态机

    背景 本技术分析文章通过对高通的4/5G移动基带系统进行深入逆向工程提示其内部消息通信机制以及核心架构设计逻辑,本文的研究基于高通的4G基带MDM9707以及5G基带模块sdx55的固件之上分析完成, ...

  9. 高仿人人网客户端Android版项目源码

    高仿人人网客户端,有兴趣的盆友可以研究下,里面主要包含的一些UI设计与交互.(注:项目中有少许问题,apk能运行,希望开发者可以参考代码研究一下.) 源码下载:http://code.662p.com ...

最新文章

  1. FFMPEG视音频编解码学习(1)
  2. ADO.NET Entity Framework -Code Fisrt (二)
  3. Thrift异步IO服务器源码分析
  4. 【2018.4.14】模拟赛之三-ssl2393 单元格
  5. Entity FrameWork 操作使用详情
  6. js 根据公历日期 算出农历_一招教会你公历换算成农历,要不要试试看
  7. Win11任务栏一直转圈圈的解决方法
  8. .NET_.NET Copy Web 部署概念_01-3
  9. linux 所有邮件地址群发,linux sendmail群发邮件
  10. PXE安装报错:Cant' write to /dev/sda ,because it is opened read-only
  11. html title 不显示_SEO入门教程二:学习最基础的html代码知识
  12. 病毒 Worm.Logo.g
  13. oracle asm查看大小,Oracle ASM查看信息
  14. 加权移动平均法 java_加权平均和移动平均
  15. Typora 如何自动生成标题序号
  16. 使用vue开源项目vue-framework-wz遇到的问题以及解决方案
  17. Windows系统的一些基础操作(通过运行命令方式打开)
  18. Opencv C++ 学习视频整理源代码(1)
  19. 如何破解AppOps (需要root)
  20. 【阿拉伯数字转中文汉字工具类】

热门文章

  1. Android中颜色的使用
  2. NEC人工智能联合实验室成立
  3. 基于Spring+Struts+Hibernate实现的健康管理平台
  4. 基于Android的旅游信息app
  5. Zabbix 报错:Get value from agent failed: cannot connect to [[127.0.0.1]:10050]: [111] Connection refus
  6. 结识了实实在在的好处预制房屋贷款在美国加州
  7. Cocos Creator实现不规则区域点击
  8. 武汉大学 gps 计算机 陈冰,武汉大学人文社会科学研究院
  9. Java Email以及发送Email附件
  10. 模电视频笔记:详解直接耦合放大电路p146,3.1.1