Android上自制SQLite数据库并且在应用中使用自制数据库的探究

前言

我觉得数据库的使用在一般应用中使用的频率是比较高的,相比于使用Android自带的sqlite,虽然提供了一些API操作,但是操作起来还是比较复杂的。所以自然而然有很多开源的ORM数据库框架,譬如GreenDao、ORMLite、Realm等开源库,使用起来还是比较方便的。当然用到SQLite的机会也是挺多的,毕竟开源库也有一定的局限性,使用原生数据库时,我通常是使用JakeWharton大神的SqlBrite开源库,这个库是轻量级的sql辅助库,配合RxJava能够轻松将数据转为流操作。当然扯远了,还是说说这篇文章要提到的自制数据库。

场景

但是在某些场景下,这些数据库就不够好用了,譬如,当应用需要选择省市县三级联动的时候,一般情况是是向网络请求数据,每次选择一项时,就要向服务器请求新的对应的联动数据,这样做虽然能保证及时性,但是用户体验就没那么好,每次都需要网络请求,如果遇上网络不好的情况或者数据量比较大的情况的时候,这样设计就有问题了。而且相对来说,省市县这样的数据其实挺大的,并且其数据相对来说比较稳定,不会轻易发生大的改变,所以如果应用能够内置这个数据库文件,获取数据直接从数据库查询,效率自然比从服务器拿的要高。
又譬如黄历信息、一些常用的电话号码这些信息量比较大,但是又不用经常修改的信息,完全可以先做本地数据库处理。

方案

如何创建数据库

如果要实现数据库,则必须先创建数据库,创建数据库有两种方案:

  1. 方案一:就是手动创建数据库,我是在Java平台上,利用jdbc来创建数据库的,其创建方式和MySQL的连接方式是十分类似的。当然,也可以利用数据库制作工作来创建,譬如我用了SqliteStudio这个工具,这个工具既可以创建数据库,同时也能够增删查改数据库信息。但是需要注意的亮点就是:
    1. 数据库文件中必须有一个名为“android_metadata”的表,这个表只包括一个字段:locale,也只需要一条记录,默认值为“en_US”。
    2. 数据库文件中的其它表,必须包括一个名字“_id”的关键字字段。其实这一点也未必需要,不过我的建议是创建表的时候都加上这个字段,设置为自动增长,因为在使用listview的时候可以进行cursor自定绑定。再者,当我们用自定义表时,习惯创建一个协议类来存储表名、字段名等信息,通常这个Entry类推荐实现BaseColumns这个接口,而BaseColumns这个接口就是自带''__id''这个字段的。
  2. 方案二:利用Android的SQLiteOpenHelper来实现,原理就是按照正常流程创建一个数据库以及创建表,但是不写入信息,然后可以利用adb命令将其从系统中取出,这个只要程序运行一次,就可以生成对应的数据库,并且存放在data/data/<包名>/database/目录下面,可以直接pull出来。这种方式比较简单安全,而且能确保取出的数据库能够安全使用。

获取数据

如何往数据库里写东西,当然实现的方式还是有很多种,我选择了用Java实现,因为毕竟比较熟悉,我的想法是通过JDBC来打开数据库并进行读写,就拿我的获取黄历信息来说,由于只提供了获取一天的接口,我只能够一天天去获取,而且获取七十年的数据,这个当然是比较大的并且不会变化的数据。我的方案是先获取指定日期的每天的天数,再利用RxJava去循环获取并存入数据库。主要操作类具体代码如下:

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

    /**
     * 通过不停循环请求数据并且将数据存入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数据库,并同步存入数据库,避免多线程造成的问题
     * @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();
                }
            }
        }
    }
}

话说我获取七十年的数据,足足跑了好几个小时,不得不说写的方式还是有待提高的,不足之处,多多指教。还有就是RxJava真的是太好用了,这样的逻辑用流操作很方便。

利用数据库

当获得有数据的数据库后,接下来就应该该利用这个数据库了。如果数据库过大,建议进行压缩或者进行文件分割,因为Android的assets目录下的文件是有文件大小限制的,据说在2.3以前都是不支持1M大小的文件读取的,会报错

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

所以如果文件过大,可以考虑将文件分割若干份1M大小的文件,在读取时将文件合并,可以参考这篇博客

不过据我实践所得,Android在6.0以上版本似乎没有对assets下的大文件读取有过大限制,我将数据库文件压缩后,只有不到6M,放进去后,AssetManager是可以正常读取的,并不会报错,不知道是不是官方提高了asset下文件大小的限制,这个可以以后研究一下。我的做法是将大文件压缩后,然后当程序第一次运行时,将其解压到对应路径的database目录下。具体操作类如下:

数据库创建用到了HuangliDbHelper

public class HuangliDbHelper extends SQLiteOpenHelper {

    private static final String TAG = "HuangliDbHelper";

    //用户数据库文件的版本
    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) {
            //数据库已存在,不做任何操作
        } else {
            //创建数据库
            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);
                //复制并且解压压缩文件到数据库目录下
                ZipUtil.unZipAssetFileToDatabaseDirectory(mContext, "HuangLi.zip", DB_PATH);
            } catch (IOException e) {
                throw new Error("数据库创建失败");
            }
        }
    }

    /**
     * 检查数据库是否存在
     *
     * @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: 数据库不存在");
        }
        if (checkDB != null) {
            checkDB.close();
        }
        return checkDB != null ? true : false;
    }
}

在创建好,在Repository类里可以创建这个实例并且调用createDataBase()这个方法来复制,接下来就是可以正常的对这个数据进行正常的增删改查操作,再封装一层,后面我就不写了。

结语

其实整个流程还是比较简单的,在这个过程中也学到了很多的知识,遇到了不少坑,仅此记录一下,以防以后再次遇到这样的需求不会踩坑哈!

晚安!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350

推荐阅读更多精彩内容