Android中當資料庫需要更新時我們該怎麼辦?,android資料庫
問題:Android資料庫更新並保留原來的資料如何?
Andoird的SQLiteOpenHelper類中有一個onUpgrade方法。協助文檔中只是說當資料庫升級時該方法被觸發。經過實踐,解決了我一連串的
疑問:
1. 協助文檔裡說的“資料庫升級”是指什嗎?
你開發了一個應用,當前是1.0版本。該程式用到了資料庫。到1.1版本時,你在資料庫的某個表中增加了一個欄位。那麼軟體1.0版本用的資料庫在軟體1.1版本就要被升級了(當然這裡的升級包括兩個概念,一個是應用升級還有就是資料庫升級)
關於應用升級我們知道直接在AndroidManifest.xml檔案中修改即可。
關於資料庫升級我們就需要在代碼中修改:
private static final String DBNAME = "ads.db";private static final int VERSION = 1;public DBOpenHelper(Context context) {super(context, DBNAME, null, VERSION);}我們在使用資料庫的時候,都會自訂一個Helper類,需要實現SQLiteOpenHelper類(是個抽象類別),然後實現他的onCreate和onUpgrade方法。上面的一段程式碼片段就是我們自訂的Helper類中的,這裡我們會看到在構造方法中,我們會調用父類(就是SQLiteOpenHelper類的構造方法),這裡需要傳遞Context變數、資料庫名稱、以及資料庫的版本號碼(第三個參數是一個工廠類,可以傳遞null)。所以我們就可以在這個地方進行資料庫的升級了,當然我們一般第一次把資料庫的版本號碼設定成1,以後遞增即可,這個的原因,後面在分析源碼的時候會提到。
2. 資料庫升級應該注意什嗎?
軟體的1.0版本升級到1.1版本時,資料庫中老的資料不能丟。那麼在1.1版本的應用中就要有地方能夠檢測出來新的資料庫與老的資料庫不相容。並且能夠有辦法把1.0應用中的資料庫升級到1.1應用時能夠使用的資料庫。換句話說,要在1.0應用的資料庫中的那個表中增加那個欄位,並賦予這個欄位預設值。
3. 應用如何知道資料庫需要升級?
SQLiteOpenHelper類的建構函式有一個參數是
int version,它的意思就是指資料庫版本號碼。比如在應用1.0版本中,我們使用
SQLiteOpenHelper訪問資料庫時,該參數為1,那麼資料庫版本號碼1就會寫在我們的資料庫中。到了1.1版本,我們的資料庫需要發生變化,那麼我們1.1版本的程式中就要使用一個大於1的整數來構造SQLiteOpenHelper類,用於訪問新的資料庫,比如2。當我們的1.1新程式讀取1.0版本的老資料庫時,就發現老資料庫裡儲存的資料庫版本是1,而我們新程式訪問它時填的版本號碼為2,系統就知道資料庫需要升級。
4. 何時觸發資料庫升級?如何升級?
當系統在構造SQLiteOpenHelper類的對象時,如果發現版本號碼不一樣,就會自動調用onUpgrade函數,讓你在這裡對資料庫進行升級。根據上述情境,在這個函數中把老版本資料庫的相應表中增加欄位,並給每條記錄增加預設值即可。
新版本號碼和老版本號碼都會作為onUpgrade函數的參數傳進來,便於開發人員知道資料庫應該從哪個版本升級到哪個版本。
升級完成後,資料庫會自動儲存最新的版本號碼為當前資料庫版本號碼。
下面就從源碼的角度分析一下,執行流程:
首先來看一下SQLiteOpenHelper.java類
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) { this(context, name, factory, version, null); }
我們看到了,這個構造方法,就是我們在子類中調用的,第三個參數是一個遊標工廠類,可以傳遞null。再看一下他的其他構造方法:
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version, DatabaseErrorHandler errorHandler) { if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version); mContext = context; mName = name; mFactory = factory; mNewVersion = version; mErrorHandler = errorHandler; }
這裡我們會看到一個資訊,就是會判斷版本號碼,如果版本號碼小於1的話直接拋異常了,所以我們一般將版本號碼設定成1。構造結束了,下面來看一下他的onCreate和onUpgrade方法:
public abstract void onCreate(SQLiteDatabase db); public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);
這兩個方法都是抽象的,需要子類去實現他們。
好吧,那下面我們該看哪個方法呢?當然是看我們使用到的方法了。我們一般在使用資料庫的時候會用到的代碼:
DBOpenHelper openHelper = new DBOpenHelper(this);SQLiteDatabase db = openHelper.getWritableDatabase();
初始化自訂的Helper類,然後擷取一個資料庫物件SQLiteDatabase。那麼我們就來看看你getWritableDatabase方法:
public SQLiteDatabase getWritableDatabase() { synchronized (this) { return getDatabaseLocked(true); } }
這裡用到了同步機制了,接著看getDatabaseLocked方法:
private SQLiteDatabase getDatabaseLocked(boolean writable) { if (mDatabase != null) { if (!mDatabase.isOpen()) { // Darn! The user closed the database by calling mDatabase.close(). mDatabase = null; } else if (!writable || !mDatabase.isReadOnly()) { // The database is already open for business. return mDatabase; } } if (mIsInitializing) { throw new IllegalStateException("getDatabase called recursively"); } SQLiteDatabase db = mDatabase; try { mIsInitializing = true; if (db != null) { if (writable && db.isReadOnly()) { db.reopenReadWrite(); } } else if (mName == null) { db = SQLiteDatabase.create(null); } else { try { if (DEBUG_STRICT_READONLY && !writable) { final String path = mContext.getDatabasePath(mName).getPath(); db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler); } else { db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ? Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0, mFactory, mErrorHandler); } } catch (SQLiteException ex) { if (writable) { throw ex; } Log.e(TAG, "Couldn't open " + mName + " for writing (will try read-only):", ex); final String path = mContext.getDatabasePath(mName).getPath(); db = SQLiteDatabase.openDatabase(path, mFactory, SQLiteDatabase.OPEN_READONLY, mErrorHandler); } } onConfigure(db); final int version = db.getVersion(); if (version != mNewVersion) { if (db.isReadOnly()) { throw new SQLiteException("Can't upgrade read-only database from version " + db.getVersion() + " to " + mNewVersion + ": " + mName); } db.beginTransaction(); try { if (version == 0) { onCreate(db); } else { if (version > mNewVersion) { onDowngrade(db, version, mNewVersion); } else { onUpgrade(db, version, mNewVersion); } } db.setVersion(mNewVersion); db.setTransactionSuccessful(); } finally { db.endTransaction(); } } onOpen(db); if (db.isReadOnly()) { Log.w(TAG, "Opened " + mName + " in read-only mode"); } mDatabase = db; return db; } finally { mIsInitializing = false; if (db != null && db != mDatabase) { db.close(); } } }
這個方法的東西就有點多了,貌似也是最核心的部分。
首先我們看到一個欄位mDatabase,是個SQLiteDatabase類型的
private SQLiteDatabase mDatabase;
下面來看一下開始部分的代碼:
if (mDatabase != null) {if (!mDatabase.isOpen()) {// Darn! The user closed the database by calling mDatabase.close().mDatabase = null;} else if (!writable || !mDatabase.isReadOnly()) {// The database is already open for business.return mDatabase;}}
如果mDatabase不為null的話,然後在判斷如果這個資料庫是否是關閉的狀態,如果關閉了就將其設定null。如果沒有關閉,就判斷他的狀態是可讀還是可寫的狀態,然後返回一個執行個體即可.
繼續下面的代碼:
SQLiteDatabase db = mDatabase;try {mIsInitializing = true;if (db != null) {if (writable && db.isReadOnly()) {db.reopenReadWrite();}} else if (mName == null) {db = SQLiteDatabase.create(null);} else {try {if (DEBUG_STRICT_READONLY && !writable) {final String path = mContext.getDatabasePath(mName).getPath();db = SQLiteDatabase.openDatabase(path, mFactory,SQLiteDatabase.OPEN_READONLY, mErrorHandler);} else {db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,mFactory, mErrorHandler);}} catch (SQLiteException ex) {if (writable) {throw ex;}Log.e(TAG, "Couldn't open " + mName+ " for writing (will try read-only):", ex);final String path = mContext.getDatabasePath(mName).getPath();db = SQLiteDatabase.openDatabase(path, mFactory,SQLiteDatabase.OPEN_READONLY, mErrorHandler);}}
這段代碼主要是初始化一個SQLiteDatabase對象。
繼續:
final int version = db.getVersion();if (version != mNewVersion) {if (db.isReadOnly()) {throw new SQLiteException("Can't upgrade read-only database from version " +db.getVersion() + " to " + mNewVersion + ": " + mName);}db.beginTransaction();try {if (version == 0) {onCreate(db);} else {if (version > mNewVersion) {onDowngrade(db, version, mNewVersion);} else {onUpgrade(db, version, mNewVersion);}}db.setVersion(mNewVersion);db.setTransactionSuccessful();} finally {db.endTransaction();}}
這段代碼就是我們這次的主要研究對象,也會解決我們的很多疑問。代碼的邏輯很簡單:
首先他擷取到當前資料庫的版本號碼,這裡的db就是之前代碼中建立的一個SQLiteDatabase對象,然後調用它的getVersion方法擷取版本號碼,查看getVersion的源碼:
public int getVersion() { return ((Long) DatabaseUtils.longForQuery(this, "PRAGMA user_version;", null)).intValue(); }
這裡看到是一個DatabaseUtils類的longForQuery方法,繼續找到這個方法的源碼:
public static long longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) { SQLiteStatement prog = db.compileStatement(query); try { return longForQuery(prog, selectionArgs); } finally { prog.close(); } }
找到這個方法了,其實他內部很簡單,就是執行一個sql語句,sql語句為:
PRAGMA user_version;
這裡查詢的結果是long類型的,所以我們在定義資料庫的版本的時候一般是整數,遞增也是整數。
那麼當我們首次建立資料庫的時候他的版本值是多少呢?其實這個可以猜的。可能為0。那麼我們就來做個驗證吧。這裡的驗證不是用代碼的方式了,而是藉助於一個軟體:Sqlite Expert
這個軟體用起來還是比較簡單的。我們建立一個資料庫,然後執行上面的SQL語句,結果如:
果然第一次得到的版本號碼是0,好的下面繼續來看一下之前的程式碼分析,之前分析到了,擷取資料庫的版本號碼,然後就是和之前的版本進行比較,如果不相等
這裡又會做一個判斷,判斷當前的資料庫是否為唯讀,如果是唯讀話,是不能進行後續的更新操作,拋個異常。
if (db.isReadOnly()) {throw new SQLiteException("Can't upgrade read-only database from version " +db.getVersion() + " to " + mNewVersion + ": " + mName);}
如果不是唯讀話,繼續下面的代碼:
db.beginTransaction();try {if (version == 0) {onCreate(db);} else {if (version > mNewVersion) {onDowngrade(db, version, mNewVersion);} else {onUpgrade(db, version, mNewVersion);}}db.setVersion(mNewVersion);db.setTransactionSuccessful();} finally {db.endTransaction();}
開啟一個事務。這裡會判斷如果擷取到的資料庫的版本為0,那麼就執行onCreate方法,如果版本號碼有增加就會執行onUpgrade方法,如果版本號碼有遞減的話,就會執行onDowngrade方法,拋異常了。
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { throw new SQLiteException("Can't downgrade database from version " + oldVersion + " to " + newVersion);}
然後設定資料庫的版本為最新的值。結束事務。
這裡在使用的時候遇到一個問題,就是我在onUpgrade方法中調用了onCreate方法,但是此時onCreate方法會報異常,當然我們將其捕獲了(在外面使用Helper的時候捕獲的),但是每次開啟應用的時候,onUpgrade方法都會執行。當時一直在找原因。沒頭緒呀。
其實這裡就可以找到原因,因為當onUpgrade方法報異常(因為在onUpgrade方法中調用了onCreate方法,當onCreate方法報異常時,onUpgrade方法沒有捕獲到這個異常就還會報異常)之後,後續代碼就不執行了,那麼下面的設定資料庫最新版本號碼的代碼也不會執行了,所以每次開啟app的時候,會進行版本號碼的比對,結果還是會執行onUpgrade方法,這個方法還是會報異常,所以會出現每次開啟app的時候onUpgrade方法都會執行一次。下面來看一下
package com.sohu.sqlitedemo;import android.content.Context;import android.database.sqlite.SQLiteDatabase;import android.database.sqlite.SQLiteDatabase.CursorFactory;import android.database.sqlite.SQLiteOpenHelper;import android.util.Log;import android.widget.Toast;public class DBOpenHelper extends SQLiteOpenHelper {private static final String DBNAME = "ads.db";private static final int VERSION = 1;public DBOpenHelper(Context context) {super(context, DBNAME, null, VERSION);}@Overridepublic void onCreate(SQLiteDatabase db) {Log.i("DEMO","oldVersion,onCreate()");db.execSQL("CREATE TABLE IF NOT EXISTS offlineBanner("//+ "id integer primary key autoincrement," //+ "vid VARCHAR(100),"// //+ "adsequences integer,"//+ "isofflineads integer,"+ "Impression VARCHAR(2000),"//+ "Duration VARCHAR(50)," //+ "ClickThrough VARCHAR(500),"// + "ClickTracking VARCHAR(500)," //+ "MediaFile VARCHAR(500)," //+ "creativeView VARCHAR(500),"// + "start VARCHAR(500)," //+ "firstQuartile VARCHAR(500),"// + "midpoint VARCHAR(500)," //+ "thirdQuartile VARCHAR(500),"// + "complete VARCHAR(500)," //+ "sdkTracking VARCHAR(2000),"// + "sdkClick VARCHAR(2000)," //+ "time VARCHAR(50));");////這行代碼會報異常db.execSQL("CREATE TABLE offlinePause("//+ "id integer primary key autoincrement,"// + "vid VARCHAR(100)," //+ "Impression VARCHAR(2000)," //+ "StaticResource VARCHAR(500),"// + "NonLinearClickThrough VARCHAR(500),"// + "sdkTracking VARCHAR(2000)," //+ "sdkClick VARCHAR(2000)," //+ "time VARCHAR(50));");//db.execSQL("CREATE TABLE IF NOT EXISTS download_url ("//+ "id integer primary key autoincrement,"// + "url varchar(500),"//+ "status integer,"//+ "length integer)");//}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {Log.i("DEMO","oldVersion=" + oldVersion + ",newVersion" + newVersion);if (oldVersion != newVersion) {db.execSQL("ALTER TABLE offlineBanner ADD COLUMN adsequence integer;");db.execSQL("ALTER TABLE offlineBanner ADD COLUMN isofflinead integer;");db.execSQL("DROP TABLE IF EXISTS download_url");}//執行方法會報異常onCreate(db);}}
報異常的原因很簡單。當我需要更新的時候,就會執行onUpgrade方法,這裡又執行了onCreate方法,在建立pause表的時候會報異常,因為這個表已經存在了。
當我們把代碼外面加上try...catch的時候:
try{//開始使用資料庫DBOpenHelper openHelper = new DBOpenHelper(this);SQLiteDatabase db = openHelper.getWritableDatabase();db.close();}catch (Exception e){}
問題會解決,但是onUpgrade方法就會執行多次:
所以還是要解決一下onCreate方法中的異常問題,那個異常就是因為表存在了,所以建立表的時候需要判斷表是否已經存在了。這樣就可以了。
上面從源碼的角度瞭解了原理,下面來看一下執行個體:
做Android應用,不可避免的會與SQLite打交道。隨著應用的不斷升級,原有的資料庫結構可能已經不再適應新的功能,這時候,就需要對SQLite資料庫的結構進行升級了。 SQLite提供了ALTER TABLE命令,允許使用者重新命名或添加新的欄位到已有表中,但是不能從表中刪除欄位。
並且只能在表的末尾添加欄位,比如,為 Subscription添加兩個欄位:
ALTER TABLE Subscription ADD COLUMN Activation BLOB;ALTER TABLE Subscription ADD COLUMN Key BLOB;
另外,如果遇到複雜的修改操作,比如在修改的同時,需要進行資料的轉移,那麼可以採取在一個事務中執行如下語句來實現修改表的需求。
1. 將表名改為暫存資料表
ALTER TABLE Subscription RENAME TO __temp__Subscription;
2. 建立新表
CREATE TABLE Subscription (OrderId VARCHAR(32) PRIMARY KEY ,UserName VARCHAR(32) NOT NULL ,ProductId VARCHAR(16) NOT NULL);
3. 匯入資料
INSERT INTO Subscription SELECT OrderId, “”, ProductId FROM __temp__Subscription;
或者
INSERT INTO Subscription() SELECT OrderId, “”, ProductId FROM __temp__Subscription;
* 注意 雙引號”” 是用來補充原來不存在的資料的
4. 刪除暫存資料表
DROP TABLE __temp__Subscription;
通過以上四個步驟,就可以完成舊資料庫結構向新資料庫結構的遷移,並且其中還可以保證資料不會應為升級而流失。
當然,如果遇到減少欄位的情況,也可以通過建立暫存資料表的方式來實現。
Android應用程式更新的時候如果資料庫修改了欄位需要更新資料庫,並且保留原來的資料庫資料:
這是原有的資料庫表
CREATE_BOOK = "create table book(bookId integer primarykey,bookName text);";
然後我們增加一個欄位:
CREATE_BOOK = "create table book(bookId integer primarykey,bookName text,bookContent text);";
首先我們需要把原來的資料庫表重新命名一下
CREATE_TEMP_BOOK = "alter table book rename to _temp_book";
然後把備份表中的資料copy到新建立的資料庫表中
INSERT_DATA = "insert into book select *,' ' from _temp_book";(注意' '是為新加的欄位插入預設值的必須加上,否則就會出錯)
然後我們把備份表幹掉就行啦。
DROP_BOOK = "drop table _temp_book";
然後把資料庫的版本後修改一下,再次建立資料庫操作對象的時候就會自動更新(註:更新的時候第一個建立的操作資料的對象必須是可寫的,也就是通過這個方法getWritableDatabase()擷取的資料庫操作對象)
然後在onUpgrade()方法中執行上述sql語句就OK
public class DBservice extends SQLiteOpenHelper{private String CREATE_BOOK = "create table book(bookId integer primarykey,bookName text);";private String CREATE_TEMP_BOOK = "alter table book rename to _temp_book";private String INSERT_DATA = "insert into book select *,'' from _temp_book";private String DROP_BOOK = "drop table _temp_book";public DBservice(Context context, String name, CursorFactory factory,int version) {super(context, name, factory, version);}@Overridepublic void onCreate(SQLiteDatabase db) {db.execSQL(CREATE_BOOK);}@Overridepublic void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {switch (newVersion) {case 2:db.execSQL(CREATE_TEMP_BOOK);db.execSQL(CREATE_BOOK);db.execSQL(INSERT_DATA);db.execSQL(DROP_BOOK);break;}}}
總結:這次遇到的問題,開始的時候不知道怎麼解決,那我們就繼續去看那個操蛋的代碼吧,這裡的原始碼其實不難的~~,但是我們要養成看原始碼的習慣。
android開發 更改了資料庫後怎更新
給你提供幾種方法吧:
在查看學生資訊的那個Activity裡面,重寫onResume方法,在這個方法裡面擷取資料來顯示視圖,無論是這個activity新建立,還是從後台不可見轉為前台,都會走這個生命週期。
資料顯示頁面監聽資料的變化來更新輸入。如果管理資料使用的是 ContentProvider管理的話,直接使用ContentObserver來監聽相應的Uri即可,如果不是這種方式的話,可以在操作學生資料庫的時候發送一個廣播,然後現實學生頁面的Activity去監聽這個廣播,收到廣播後去更新介面。
如果是從查看資訊介面進入的修改資訊介面,那麼可以使用startActivityForResult方法啟動資訊修改介面,資訊修改後調用setResult方法返回修改後的資料給資訊顯示介面,資訊顯示介面重寫onActivityResult方法來擷取新的學生資訊,從而更新介面
暫時就想到這幾種方法吧,希望對你有協助~
android資料庫更新調試錯誤
你的KEY_NUM是什麼類型的,如何是TEXT的話,你的num如果不是數字,應該寫成:
KEY_NUM + "='" + num + "'";
比如: name = 'tom'