使用GreenDao實現本地Sqlite緩存
來自: http://blog.csdn.net/zhaokaiqiang1992/article/details/45700475
到目前為止,煎蛋的Android項目算是告一段落了,功能基本都已完成,那么今天,我就介紹一下在煎蛋這個項目里,是怎么完成數據緩存功能的。想看代碼的請戳煎蛋項目的GITHUB地址
</blockquote>
轉載請注明出處:http://blog.csdn.net/zhaokaiqiang1992
緩存功能的解決方案
因為算是一個閱讀類的應用,所以說如果在無網絡情況下,用戶打開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 }</pre>
然后就可以在創建的類文件里面,寫上下面的代碼。當然了,這是煎蛋項目里面的,其他用法你需要自己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"); }
}</pre>
因為我們需要緩存所有的功能模塊,所以呢,調用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; @Override public 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; }
}</pre>
請無視其他無關代碼。當然了,如果你需要線程安全,你可以再加個同步鎖什么的。
因為所有的緩存邏輯基本相同,所以我抽取了一個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);
}</pre>
所有子類都必須實現這三個方法,完成數據的清空、添加和獲取操作。比如,我們以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 */ @Override public 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 */ @Override public void addResultCache(String result, int page) { JokeCache jokeCache = new JokeCache(); jokeCache.setResult(result); jokeCache.setPage(page); jokeCache.setTime(System.currentTimeMillis()); mJokeCacheDao.insert(jokeCache); }
}</pre>
在這里使用了線程安全的單例模式。那么我們在代碼里面怎么用呢?很簡單,首先看我們改造之后的獲取方法
public void loadFirst() { page = 1; loadDataByNetworkType(); }public void loadNextPage() { page++; loadDataByNetworkType(); } /** * 根據不同的網絡狀態選擇不同的加載策略 */ private void loadDataByNetworkType() { if (NetWorkUtil.isNetWorkConnected(getActivity())) { loadData(); } else { loadCache(); } }</pre>
之前的獲取數據方法,都換成了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(); }</pre>
我們從緩存中獲取數據,然后添加給適配器,然后刷新即可。
有的同學可能注意到了,那么我們什么時候添加的緩存呀?
因為段子這個功能,需要請求兩次接口,第一次是數據,第二次是評論數量,所以我們只能在獲取到評論數量之后,再緩存我們的數據,就像下面這樣:
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>>() { @Override public 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() { @Override public void onErrorResponse(VolleyError error) { ShowToast.Short(ToastMsg.LOAD_FAILED); mLoadFinisCallBack.loadFinish(null); google_progress.setVisibility(View.GONE); if (mSwipeRefreshLayout.isRefreshing()) { mSwipeRefreshLayout.setRefreshing(false); } } } )); }</pre>
還有個問題,就是從數據庫解析緩存數據的時候,因為請求的數據,在經過我們解析之后全都轉換成了對象,然后我們直接將對象轉換成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; }</pre>
其他的代碼都很相似了,雖然說起來很簡單,但是在做的時候,在這個坑里耽誤了一些時間,以此為戒。
其他資料
</div>