【凱子哥帶你做高仿】“煎蛋”Android版的高仿及優化(三)——使用GreenDao實現本地Sqlite緩存

到目前爲止,煎蛋的Android項目算是告一段落了,功能基本都已完成,那麼今天,我就介紹一下在煎蛋這個項目裏,是怎麼完成數據緩存功能的。想看代碼的請戳煎蛋項目的GITHUB地址

轉載請註明出處: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
}

然後就可以在創建的類文件裏面,寫上下面的代碼。當然了,這是煎蛋項目裏面的,其他用法你需要自己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;

    @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;
    }

}

請無視其他無關代碼。當然了,如果你需要線程安全,你可以再加個同步鎖什麼的。

因爲所有的緩存邏輯基本相同,所以我抽取了一個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
     */
    @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);
    }

}

在這裏使用了線程安全的單例模式。那麼我們在代碼裏面怎麼用呢?很簡單,首先看我們改造之後的獲取方法

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>>() {

                @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);
                    }

                }
            }
            ));

        }

還有個問題,就是從數據庫解析緩存數據的時候,因爲請求的數據,在經過我們解析之後全都轉換成了對象,然後我們直接將對象轉換成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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章