应用的更新有两种情况,一是系统应用随着系统OTA更新,二是用户从市场上单独更新app。无论哪种情况,若是前后版本的应用的数据库结构发生了变化,应用需要自行处理前后结构的转换工作。否则,更新后的应用访问数据库时,访问的是更新前的代码建立的数据库,会出现NoSuchColumnException,导致应用FC。
需要说明的是,不只是数据库,所有持久性数据的结构的修改,都需要考虑前后版本的兼容性。否则,轻则丢失用户数据,重则大量的FC,应用无法正常使用。
Android 的SQLiteOpenHelper.java 己经内建了数据库升级或是降级的支持:
public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion); public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) |
应用一般由继承了 SQLiteOpenHelper.java 的工具类提供对数据库的封装和访问接口,并重写onCreate,onUpgrade,onDowngrade等。 如ContactsDatabaseHelper.java#996中有:
996 protected ContactsDatabaseHelper( 997 Context context, String databaseName, boolean optimizationEnabled) { 998 super(context, databaseName, null, DATABASE_VERSION); //注意这个 999 mDatabaseOptimizationEnabled = optimizationEnabled; 1000 Resources resources = context.getResources(); 1001 1002 mContext = context; 1003 mSyncState = new SyncStateContentProviderHelper(); 1004 mCountryMonitor = new CountryMonitor(context); 1005 mUseStrictPhoneNumberComparison = resources.getBoolean( 1006 com.android.internal.R.bool.config_use_strict_phone_number_comparation); 1007 } |
可以看到,在构造函数里,传递了一个DATABASE_VERSION给super构造,做为当前数库的版本号。 这个DATABASE_VERSION是应用自己定义的一个int值。一般来讲,每当数据库结构变化时,需要把DATABASE_VERSION变大。在ContactsDatabase中,由于结构复杂,变化次数多,还特别注明了不同Android间大版本的历史:
104 105 /** 106 * Contacts DB version ranges: 107 * <pre> 108 * 0-98 Cupcake/Donut 109 * 100-199 Eclair 110 * 200-299 Eclair-MR1 111 * 300-349 Froyo 112 * 350-399 Gingerbread 113 * 400-499 Honeycomb 114 * 500-549 Honeycomb-MR1 115 * 550-599 Honeycomb-MR2 116 * 600-699 Ice Cream Sandwich 117 * 700-799 Jelly Bean 118 * 800-899 Kitkat 119 * 900-999 L 120 * </pre> 121 */ 122 static final int DATABASE_VERSION = 910; |
另外,还有onCreate. onCreate用来调用"create table" 建立数据库结构的。 如 ContactsDatabaseHelper.java#1150
1149 @Override 1150 public void onCreate(SQLiteDatabase db) { 1151 Log.i(TAG, "Bootstrapping database version: " + DATABASE_VERSION); 1152 1153 mSyncState.createDatabase(db); 1154 1155 // Create the properties table first so the create time is available as soon as possible. 1156 // The create time is needed by BOOT_COMPLETE to send broadcasts. 1157 db.execSQL("CREATE TABLE " + Tables.PROPERTIES + " (" + 1158 PropertiesColumns.PROPERTY_KEY + " TEXT PRIMARY KEY, " + 1159 PropertiesColumns.PROPERTY_VALUE + " TEXT " + 1160 ");"); 1161 setProperty(db, DbProperties.DATABASE_TIME_CREATED, String.valueOf( 1162 System.currentTimeMillis())); 1163 1164 db.execSQL("CREATE TABLE " + Tables.ACCOUNTS + " (" + 1165 AccountsColumns._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + 1166 AccountsColumns.ACCOUNT_NAME + " TEXT, " + 1167 AccountsColumns.ACCOUNT_TYPE + " TEXT, " + 1168 AccountsColumns.DATA_SET + " TEXT" + 1169 ");"); ...... |
需要注意的是,onCreate只会在应用第一次安装时调用,在应用更新时不会被调用的。 当应用更新时,onUpgrade就需要出场了。
2197 @Override 2198 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 2199 if (oldVersion < 99) { //这里还是比较暴力的,如果数据库版本太旧了,就drop掉所有的表,然后调用onCreate再全部重新创建 2200 Log.i(TAG, "Upgrading from version " + oldVersion + " to " + newVersion 2201 + ", data will be lost!"); 2202 2203 db.execSQL("DROP TABLE IF EXISTS " + Tables.CONTACTS + ";"); ..... 2222 onCreate(db); 2223 return; 2224 } 2225 2235 2236 if (oldVersion == 99) { .... //做些升级到100的处理 2238 oldVersion++; // 变成100,这样下面继续走100升101的逻辑 2239 } 2240 2241 if (oldVersion == 100) { .... //100升101 2250 oldVersion++; 2251 } ... |
虽然大多数情况下用户都是更新到应用的新版本,DB_VERSION通常时增加的,但是不排除降版本的情况。此时onDowngrade就要出场了:
2187 @Override 2188 public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) { 2189 Log.i(TAG, "ContactsProvider cannot proceed because downgrading your database is not " + 2190 "supported. To continue, please either re-upgrade to your previous Android " + 2191 "version, or clear all application data in Contacts Storage (this will result " + 2192 "in the loss of all local contacts that are not synced). To avoid data loss, " + 2193 "your contacts database will not be wiped automatically."); 2194 super.onDowngrade(db, oldVersion, newVersion); 2195 } 2196 |
对于Contacts DB来讲,处理方法是十分暴力的,直接打了个Log说不支持,然后就啥事不干了。事实上,很多android app对DB downgrade的处理都不完全支持。这也是为什么OTA降级系统版本后,常常会出现更多的应用错误。
下面着重讲一下onUpgrade.
onUpgrade传入的三个参数中,oldVersion表示旧的版本号,也就是更新前的app的数据库版本号,newVersion表示更新后的数据库版本号,应当就是当前代码中的构造函数的DATABASE_VERSION。由于用户的升级不一定是连续的,
因此,要求对于任一可能的oldVersion取值(也就是数据库曾用过的版本号),都能通过onUpgrade正确的升级到当前版本(newVersion)。
onUpgrade/onDowngrade并不对内部写法做要求,但有一些常见的写法可以参考。
比如,曾用版本是1,2,3,当前版本是4。一般的写法是用一大堆if或是switch:
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { switch(oldVersion){ case 1: doUpgradeTo2(db) case 2: doUpgradeTo3(db) case 3: doUpgradeTo4(db) } } |
千万注意,这里没有break,无论是oldVersion是1,2,3 都能一级级的最终到4。 当下次要升级到版本5时,只要处理4->5的就行了,如:
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { switch(oldVersion){ case 1: doUpgradeTo2(db) case 2: doUpgradeTo3(db) case 3: doUpgradeTo4(db) case 4: doUpgradeTo5(db) //只要写4->5的就行了,若是1,2,3跑到这里时,己经升级到4了 } } |
综上,app对数据库的升级的处理方法是:
- 继承并实现SQLiteOpenHelper.java 工具类 MySqlLiteHelper
- 定义一个DATABASE_VERSION, 并且在MySqlLiteHelper 的构造函数中,调用 super(context, databaseName, null, DATABASE_VERSION);
- 在onCreate中实现数据库的构造
- 每次修改数据库结构时,增加DATABASE_VERSION,并在onUpgrade中实现旧版本到新版本的数据库升级代码。确保对DATABASE_VERSION的任一历史值,都能升级到最新版本。
- 如有需要,在onDowngrade中实现版本降级的代码。