Android開發中拷貝db檔案寫入SQLite

新根發表於2017-05-11

在Android開發中,需要新增附帶的db資料庫,用於實現某些需求。例如,選擇城市的功能,需要新增city.db。

使用SQLite Database Browser,這款視覺化工具來操作db檔案。

SQLite DataBase Browser開啟city.db,如下圖所示:

這裡寫圖片描述

本篇介紹查詢城市

1. 新增city.db檔案:

通過是將db檔案放置到raw資料夾下,因此,在/res/raw資料夾下放置city.db檔案。

這裡寫圖片描述

2. 將外部db檔案資訊拷貝到運用程式的資料庫中:

資料庫本質上是指定路徑下一個db檔案。

資料庫的路徑:

 存放在/data/data/<package name>/databases/目錄下。

拷貝一個檔案資訊到另外一個檔案中,可以用檔案流來實現。建立一個檔案流的操作類:

public class WriterDBUtils {
    //這裡的資訊欄位和外部db檔案中資訊保持一致。
    public static final String CITYDB_NAME = "city.db";
    //表名
    public static final String TABLE_PROVICE = "m_province";
    public static final String TABLE_CITY = "m_city";
    //欄位名
    public static final String COLUMN_PID = "pid";
    public static  final String COLUMN_CNAME = "cname";
    public static final String COLUMN_PNAME = "pname";
    /**
     *利用檔案流進行拷貝
     */
     public static void copyDBFromRaw(Context context) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            StringBuffer stringBuffer = new StringBuffer();
            stringBuffer.append("/data/data/");
            stringBuffer.append(context.getPackageName());
            stringBuffer.append("/databases");
            File dir=new File(stringBuffer.toString());
            if(!dir.exists()){//防止databases資料夾不存在,不然,會報ENOENT (No such file or directory)的異常
                dir.mkdirs();
            }
            stringBuffer.append("/");
            stringBuffer.append(CITYDB_NAME);
            File file = new File(stringBuffer.toString());
            if (file == null || !file.exists()) {//資料庫不存在,則進行拷貝資料庫的操作。
                inputStream = context.getResources().openRawResource(R.raw.city);
                outputStream = new FileOutputStream(file.getAbsolutePath());
                byte[] b = new byte[1024];
                int length;
                while ((length = inputStream.read(b)) > 0) {
                    outputStream.write(b, 0, length);
                }
                //寫完後重新整理
                outputStream.flush();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (inputStream != null) {//關閉流,釋放資源
                    inputStream.close();
                }
                if(outputStream!=null){
                    outputStream.close();
                }
            } catch (Exception e) {
                    e.printStackTrace();
            }

        }
    }
}

檔案流是耗時任務,不能在UI執行緒中執行,可以考慮後臺服務中IntentService來執行。IntentService自帶非同步執行緒,執行完後自動關閉服務。

public class WriteCityDBIntentService extends IntentService {
    public static final String TAG=WriteCityDBIntentService.class.getSimpleName();
    public WriteCityDBIntentService() {
        super(TAG);
    }
    /**
     *  非同步執行,不在主執行緒執行,執行完後自動停止Service。
     * @param intent
     */
    @Override
    protected void onHandleIntent(Intent intent) {
        WriterDBUtils.copyDBFromRaw(BaseApplication.getAppContext());
    }
}

別忘記,Service是四大元件之一,需要註冊。

  <service android:name=".service.WriteCityDBIntentService"></service>

備註下異常

 ENOENT (No such file or directory)的異常

 解決方式:

 1. 沒有給讀寫許可權
 2. 路徑不對,少了"/",路徑下少了那個資料夾(沒有建立)。

3. 根據資料庫中資料,實現選擇城市功能:

採用DAO設計模式,運算元據庫。RxJava執行資料庫操作,RxAndroid上更新UI。

這裡,列舉查詢省份的操作:
查詢省份的SQL:

public class ProvinceDao implements DAO<Province> {
    @Override
    public List<Province> queryAll() {
        SQLiteDatabase database = null;
        Cursor cursor = null;
        List<Province> list = null;
        try {
            database = context.openOrCreateDatabase(WriterDBUtils.CITYDB_NAME, Context.MODE_PRIVATE, null);
            if (database != null) {
                cursor = database.query(WriterDBUtils.TABLE_PROVICE, new String[]{WriterDBUtils.COLUMN_PID,WriterDBUtils. COLUMN_PNAME}, null, null, null, null, null);
                if (cursor != null && cursor.moveToFirst()) {
                    list = new ArrayList<>();
                    do {
                        list.add(ValuesTransform.transformProvince(cursor));
                    } while (cursor.moveToNext());
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {//釋放資源
            if (cursor != null) {
                cursor.close();
            }
            if (database != null) {
                database.close();
            }
        }
        return list;
    }
}

RxJava非同步執行,RxAndroid更新UI:

  /**
     * 查詢省份資訊
     */
    public void queryProvince(){
       Subscription subscription= Observable.create(new Observable.OnSubscribe<List<Province>>() {
            @Override
            public void call(Subscriber<? super List<Province>> subscriber) {
                //執行查詢操作
                List<Province> list= provinceDAO.queryAll();
                subscriber.onNext(list);
            }
        }).subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())//訂閱者執行的執行緒,UI執行緒
                .subscribe(new Action1<List<Province>>() {
            @Override
            public void call(List<Province> provinces) {
                //更新UI
                provinceAdapter.addData(provinces);
            }
        });
        this.compositeSubscription.add(subscription);
    }

4. 專案效果展示:

這裡寫圖片描述

專案程式碼Github上https://github.com/13767004362/SQLitePractice

資源參考

相關文章