Android上自制SQLite數(shù)據(jù)庫并且在應(yīng)用中使用自制數(shù)據(jù)庫的探究

前言

我覺得數(shù)據(jù)庫的使用在一般應(yīng)用中使用的頻率是比較高的,相比于使用Android自帶的sqlite,雖然提供了一些API操作,但是操作起來還是比較復(fù)雜的婚脱。所以自然而然有很多開源的ORM數(shù)據(jù)庫框架,譬如GreenDao勺像、ORMLite障贸、Realm等開源庫,使用起來還是比較方便的吟宦。當(dāng)然用到SQLite的機(jī)會也是挺多的篮洁,畢竟開源庫也有一定的局限性,使用原生數(shù)據(jù)庫時(shí)殃姓,我通常是使用JakeWharton大神的SqlBrite開源庫袁波,這個(gè)庫是輕量級的sql輔助庫,配合RxJava能夠輕松將數(shù)據(jù)轉(zhuǎn)為流操作。當(dāng)然扯遠(yuǎn)了蜗侈,還是說說這篇文章要提到的自制數(shù)據(jù)庫篷牌。

場景

但是在某些場景下,這些數(shù)據(jù)庫就不夠好用了踏幻,譬如枷颊,當(dāng)應(yīng)用需要選擇省市縣三級聯(lián)動的時(shí)候,一般情況是是向網(wǎng)絡(luò)請求數(shù)據(jù)该面,每次選擇一項(xiàng)時(shí)夭苗,就要向服務(wù)器請求新的對應(yīng)的聯(lián)動數(shù)據(jù),這樣做雖然能保證及時(shí)性吆倦,但是用戶體驗(yàn)就沒那么好听诸,每次都需要網(wǎng)絡(luò)請求坐求,如果遇上網(wǎng)絡(luò)不好的情況或者數(shù)據(jù)量比較大的情況的時(shí)候蚕泽,這樣設(shè)計(jì)就有問題了。而且相對來說,省市縣這樣的數(shù)據(jù)其實(shí)挺大的须妻,并且其數(shù)據(jù)相對來說比較穩(wěn)定仔蝌,不會輕易發(fā)生大的改變,所以如果應(yīng)用能夠內(nèi)置這個(gè)數(shù)據(jù)庫文件荒吏,獲取數(shù)據(jù)直接從數(shù)據(jù)庫查詢敛惊,效率自然比從服務(wù)器拿的要高。
又譬如黃歷信息绰更、一些常用的電話號碼這些信息量比較大瞧挤,但是又不用經(jīng)常修改的信息,完全可以先做本地?cái)?shù)據(jù)庫處理儡湾。

方案

如何創(chuàng)建數(shù)據(jù)庫

如果要實(shí)現(xiàn)數(shù)據(jù)庫特恬,則必須先創(chuàng)建數(shù)據(jù)庫,創(chuàng)建數(shù)據(jù)庫有兩種方案:

  1. 方案一:就是手動創(chuàng)建數(shù)據(jù)庫徐钠,我是在Java平臺上癌刽,利用jdbc來創(chuàng)建數(shù)據(jù)庫的,其創(chuàng)建方式和MySQL的連接方式是十分類似的尝丐。當(dāng)然显拜,也可以利用數(shù)據(jù)庫制作工作來創(chuàng)建,譬如我用了SqliteStudio這個(gè)工具爹袁,這個(gè)工具既可以創(chuàng)建數(shù)據(jù)庫远荠,同時(shí)也能夠增刪查改數(shù)據(jù)庫信息。但是需要注意的亮點(diǎn)就是:
    1. 數(shù)據(jù)庫文件中必須有一個(gè)名為“android_metadata”的表呢簸,這個(gè)表只包括一個(gè)字段:locale矮台,也只需要一條記錄,默認(rèn)值為“en_US”根时。
    2. 數(shù)據(jù)庫文件中的其它表瘦赫,必須包括一個(gè)名字“_id”的關(guān)鍵字字段。其實(shí)這一點(diǎn)也未必需要蛤迎,不過我的建議是創(chuàng)建表的時(shí)候都加上這個(gè)字段确虱,設(shè)置為自動增長,因?yàn)樵谑褂胠istview的時(shí)候可以進(jìn)行cursor自定綁定替裆。再者校辩,當(dāng)我們用自定義表時(shí),習(xí)慣創(chuàng)建一個(gè)協(xié)議類來存儲表名辆童、字段名等信息宜咒,通常這個(gè)Entry類推薦實(shí)現(xiàn)BaseColumns這個(gè)接口,而BaseColumns這個(gè)接口就是自帶''__id''這個(gè)字段的把鉴。
  2. 方案二:利用Android的SQLiteOpenHelper來實(shí)現(xiàn)故黑,原理就是按照正常流程創(chuàng)建一個(gè)數(shù)據(jù)庫以及創(chuàng)建表儿咱,但是不寫入信息,然后可以利用adb命令將其從系統(tǒng)中取出场晶,這個(gè)只要程序運(yùn)行一次混埠,就可以生成對應(yīng)的數(shù)據(jù)庫,并且存放在data/data/<包名>/database/目錄下面诗轻,可以直接pull出來钳宪。這種方式比較簡單安全,而且能確保取出的數(shù)據(jù)庫能夠安全使用扳炬。

獲取數(shù)據(jù)

如何往數(shù)據(jù)庫里寫東西吏颖,當(dāng)然實(shí)現(xiàn)的方式還是有很多種,我選擇了用Java實(shí)現(xiàn)恨樟,因?yàn)楫吘贡容^熟悉侦高,我的想法是通過JDBC來打開數(shù)據(jù)庫并進(jìn)行讀寫,就拿我的獲取黃歷信息來說厌杜,由于只提供了獲取一天的接口奉呛,我只能夠一天天去獲取,而且獲取七十年的數(shù)據(jù)夯尽,這個(gè)當(dāng)然是比較大的并且不會變化的數(shù)據(jù)瞧壮。我的方案是先獲取指定日期的每天的天數(shù),再利用RxJava去循環(huán)獲取并存入數(shù)據(jù)庫匙握。主要操作類具體代碼如下:

public class HuangLiDbManager {

    private static final String TAG = "HuangLiDbManager";

    private static HuangLiDbManager sInstance;

    public static HuangLiDbManager getInstance() {
        if (sInstance == null) {
            synchronized (HuangLiDbManager.class) {
                if (sInstance == null)
                    sInstance = new HuangLiDbManager();
            }
        }
        return sInstance;
    }

    /**
     * 通過不停循環(huán)請求數(shù)據(jù)并且將數(shù)據(jù)存入sqlite
     * @param dates
     */
    public void requestData(List<String> dates) {
        final OkHttpClient client = new OkHttpClient.Builder()
                .connectTimeout(20, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)
                .build();
        Observable.from(dates)
                .flatMap(new Func1<String, Observable<HuangLiInfo>>() {
                    @Override
                    public Observable<HuangLiInfo> call(final String s) {
                        return Observable.create(new Observable.OnSubscribe<HuangLiInfo>() {
                            @Override
                            public void call(final Subscriber<? super HuangLiInfo> subscriber) {
                                final Request request = new Request.Builder()
                                        .get()
                                        .url(APIConstant.HUANGLI_BASE_URL + s + "?key=" + APIConstant.HUANGLI_API_KEY)
                                        .build();
                                Call callback = client.newCall(request);
                                callback.enqueue(new Callback() {
                                    @Override
                                    public void onFailure(Call call, IOException e) {
                                        subscriber.onError(e);
                                    }

                                    @Override
                                    public void onResponse(Call call, Response response) throws IOException {
                                       subscriber.onNext(new HuangLiInfo(s, response.body().string()));
                                    }
                                });
                            }
                        });
                    }
                })
                .distinct()
                .doOnNext(new Action1<HuangLiInfo>() {
                    @Override
                    public void call(HuangLiInfo huangLiInfo) {
                        saveDataToSqlite(huangLiInfo);
                        System.out.println(huangLiInfo.getDate());
                    }
                })
                .observeOn(Schedulers.newThread())
                .subscribe();
    }

    /**
     * 利用jdbc打開sqlite數(shù)據(jù)庫咆槽,并同步存入數(shù)據(jù)庫,避免多線程造成的問題
     * @param info
     */
    private synchronized void saveDataToSqlite(HuangLiInfo info) {
        Connection connection = null;
        try {
            Class.forName("org.sqlite.JDBC");
            connection = DriverManager.getConnection("jdbc:sqlite:HuangLi.db");
            Statement statement = connection.createStatement();
            statement.setQueryTimeout(30);
            statement.executeUpdate(String.format("insert into huangli(date,content) values('%s','%s')", info.getDate(), info.getContent()));
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

話說我獲取七十年的數(shù)據(jù)圈纺,足足跑了好幾個(gè)小時(shí)秦忿,不得不說寫的方式還是有待提高的,不足之處蛾娶,多多指教灯谣。還有就是RxJava真的是太好用了,這樣的邏輯用流操作很方便蛔琅。

利用數(shù)據(jù)庫

當(dāng)獲得有數(shù)據(jù)的數(shù)據(jù)庫后胎许,接下來就應(yīng)該該利用這個(gè)數(shù)據(jù)庫了。如果數(shù)據(jù)庫過大罗售,建議進(jìn)行壓縮或者進(jìn)行文件分割辜窑,因?yàn)锳ndroid的assets目錄下的文件是有文件大小限制的,據(jù)說在2.3以前都是不支持1M大小的文件讀取的寨躁,會報(bào)錯(cuò)

This file can not be opened as a file descriptor; it is probably compressed

所以如果文件過大穆碎,可以考慮將文件分割若干份1M大小的文件,在讀取時(shí)將文件合并职恳,可以參考這篇博客 所禀。

不過據(jù)我實(shí)踐所得谜悟,Android在6.0以上版本似乎沒有對assets下的大文件讀取有過大限制,我將數(shù)據(jù)庫文件壓縮后北秽,只有不到6M,放進(jìn)去后最筒,AssetManager是可以正常讀取的贺氓,并不會報(bào)錯(cuò),不知道是不是官方提高了asset下文件大小的限制床蜘,這個(gè)可以以后研究一下辙培。我的做法是將大文件壓縮后,然后當(dāng)程序第一次運(yùn)行時(shí)邢锯,將其解壓到對應(yīng)路徑的database目錄下扬蕊。具體操作類如下:

數(shù)據(jù)庫創(chuàng)建用到了HuangliDbHelper

public class HuangliDbHelper extends SQLiteOpenHelper {

    private static final String TAG = "HuangliDbHelper";

    //用戶數(shù)據(jù)庫文件的版本
    private static final int DB_VERSION = 1;
    public static String DB_PATH = "/data/data/com.nickming.huanglidemo/databases/";

    public static String DB_NAME = "HuangLi.db";

    private Context mContext;


    public HuangliDbHelper(Context context) {
        super(context, DB_PATH + DB_NAME, null, DB_VERSION);
        this.mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }

    public void createDataBase() {
        boolean dbExist = checkDataBase();
        if (dbExist) {
            //數(shù)據(jù)庫已存在,不做任何操作
        } else {
            //創(chuàng)建數(shù)據(jù)庫
            try {
                File dir = new File(DB_PATH);
                if (!dir.exists()) {
                    dir.mkdirs();
                }
                File dbf = new File(DB_PATH + DB_NAME);
                if (dbf.exists()) {
                    dbf.delete();
                }
                SQLiteDatabase.openOrCreateDatabase(dbf, null);
                //復(fù)制并且解壓壓縮文件到數(shù)據(jù)庫目錄下
                ZipUtil.unZipAssetFileToDatabaseDirectory(mContext, "HuangLi.zip", DB_PATH);
            } catch (IOException e) {
                throw new Error("數(shù)據(jù)庫創(chuàng)建失敗");
            }
        }
    }

    /**
     * 檢查數(shù)據(jù)庫是否存在
     *
     * @return
     */
    private boolean checkDataBase() {
        SQLiteDatabase checkDB = null;
        String myPath = DB_PATH + DB_NAME;
        try {
            checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
        } catch (SQLiteException e) {
            Log.i(TAG, "checkDataBase: 數(shù)據(jù)庫不存在");
        }
        if (checkDB != null) {
            checkDB.close();
        }
        return checkDB != null ? true : false;
    }
}

在創(chuàng)建好丹擎,在Repository類里可以創(chuàng)建這個(gè)實(shí)例并且調(diào)用createDataBase()這個(gè)方法來復(fù)制尾抑,接下來就是可以正常的對這個(gè)數(shù)據(jù)進(jìn)行正常的增刪改查操作,再封裝一層蒂培,后面我就不寫了再愈。

結(jié)語

其實(shí)整個(gè)流程還是比較簡單的,在這個(gè)過程中也學(xué)到了很多的知識护戳,遇到了不少坑翎冲,僅此記錄一下,以防以后再次遇到這樣的需求不會踩坑哈媳荒!

晚安抗悍!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市钳枕,隨后出現(xiàn)的幾起案子缴渊,更是在濱河造成了極大的恐慌牧抽,老刑警劉巖女揭,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宾毒,死亡現(xiàn)場離奇詭異葛圃,居然都是意外死亡羽资,警方通過查閱死者的電腦和手機(jī)舌界,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門压汪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來牵囤,“玉大人硬爆,你說我怎么就攤上這事欣舵。” “怎么了缀磕?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵缘圈,是天一觀的道長劣光。 經(jīng)常有香客問我,道長糟把,這世上最難降的妖魔是什么绢涡? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮遣疯,結(jié)果婚禮上雄可,老公的妹妹穿的比我還像新娘。我一直安慰自己缠犀,他們只是感情好数苫,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著辨液,像睡著了一般虐急。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上滔迈,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天止吁,我揣著相機(jī)與錄音,去河邊找鬼燎悍。 笑死赏殃,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的间涵。 我是一名探鬼主播仁热,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼勾哩!你這毒婦竟也來了抗蠢?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤思劳,失蹤者是張志新(化名)和其女友劉穎迅矛,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潜叛,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡秽褒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了威兜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片销斟。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖椒舵,靈堂內(nèi)的尸體忽然破棺而出蚂踊,到底是詐尸還是另有隱情,我是刑警寧澤笔宿,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布犁钟,位于F島的核電站棱诱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏涝动。R本人自食惡果不足惜迈勋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望醋粟。 院中可真熱鬧靡菇,春花似錦、人聲如沸昔穴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽吗货。三九已至,卻和暖如春狈网,著一層夾襖步出監(jiān)牢的瞬間宙搬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工拓哺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留勇垛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓士鸥,卻偏偏與公主長得像闲孤,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子烤礁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容