APP版本升级,数据库数据如何办?
来源:互联网 发布:网络款和实体店款 编辑:程序博客网 时间:2024/05/01 13:50
问题:Android数据库更新并保留原来的数据如何实现
Andoird的SQLiteOpenHelper类中有一个onUpgrade方法。帮助文档中只是说当数据库升级时该方法被触发。经过实践,解决了我一连串的
疑问:1. 帮助文档里说的“数据库升级”是指什么?
你开发了一个应用,当前是1.0版本。该程序用到了数据库。到1.1版本时,你在数据库的某个表中增加了一个字段。那么软件1.0版本用的数据库在软件1.1版本就要被升级了(当然这里的升级包括两个概念,一个是应用升级还有就是数据库升级)
关于应用升级我们知道直接在AndroidManifest.xml文件中修改即可。
关于数据库升级我们就需要在代码中修改:
1.
private
static
final
String DBNAME =
"ads.db"
;
2.
private
static
final
int
VERSION =
1
;
3.
4.
public
DBOpenHelper(Context context) {
5.
super
(context, DBNAME,
null
, VERSION);
6.
}
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类
1.
public
SQLiteOpenHelper(Context context, String name, CursorFactory factory,
int
version) {
2.
this
(context, name, factory, version,
null
);
3.
}
我们看到了,这个构造方法,就是我们在子类中调用的,第三个参数是一个游标工厂类,可以传递null。再看一下他的其他构造方法:
01.
public
SQLiteOpenHelper(Context context, String name, CursorFactory factory,
int
version,
02.
DatabaseErrorHandler errorHandler) {
03.
if
(version <
1
)
throw
new
IllegalArgumentException(
"Version must be >= 1, was "
+ version);
04.
05.
mContext = context;
06.
mName = name;
07.
mFactory = factory;
08.
mNewVersion = version;
09.
mErrorHandler = errorHandler;
10.
}
这里我们会看到一个信息,就是会判断版本号,如果版本号小于1的话直接抛异常了,所以我们一般将版本号设置成1。构造结束了,下面来看一下他的onCreate和onUpgrade方法:
1.
public
abstract
void
onCreate(SQLiteDatabase db);
2.
3.
public
abstract
void
onUpgrade(SQLiteDatabase db,
int
oldVersion,
int
newVersion);
这两个方法都是抽象的,需要子类去实现他们。
好吧,那下面我们该看哪个方法呢?当然是看我们使用到的方法了。我们一般在使用数据库的时候会用到的代码:
1.
DBOpenHelper openHelper =
new
DBOpenHelper(
this
);
2.
SQLiteDatabase db = openHelper.getWritableDatabase();
初始化自定义的Helper类,然后获取一个数据库对象SQLiteDatabase。那么我们就来看看你getWritableDatabase方法:
1.
public
SQLiteDatabase getWritableDatabase() {
2.
synchronized
(
this
) {
3.
return
getDatabaseLocked(
true
);
4.
}
5.
}
这里用到了同步机制了,接着看getDatabaseLocked方法:
01.
private
SQLiteDatabase getDatabaseLocked(
boolean
writable) {
02.
if
(mDatabase !=
null
) {
03.
if
(!mDatabase.isOpen()) {
04.
// Darn! The user closed the database by calling mDatabase.close().
05.
mDatabase =
null
;
06.
}
else
if
(!writable || !mDatabase.isReadOnly()) {
07.
// The database is already open for business.
08.
return
mDatabase;
09.
}
10.
}
11.
12.
if
(mIsInitializing) {
13.
throw
new
IllegalStateException(
"getDatabase called recursively"
);
14.
}
15.
16.
SQLiteDatabase db = mDatabase;
17.
try
{
18.
mIsInitializing =
true
;
19.
20.
if
(db !=
null
) {
21.
if
(writable && db.isReadOnly()) {
22.
db.reopenReadWrite();
23.
}
24.
}
else
if
(mName ==
null
) {
25.
db = SQLiteDatabase.create(
null
);
26.
}
else
{
27.
try
{
28.
if
(DEBUG_STRICT_READONLY && !writable) {
29.
final
String path = mContext.getDatabasePath(mName).getPath();
30.
db = SQLiteDatabase.openDatabase(path, mFactory,
31.
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
32.
}
else
{
33.
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
34.
Context.MODE_ENABLE_WRITE_AHEAD_LOGGING :
0
,
35.
mFactory, mErrorHandler);
36.
}
37.
}
catch
(SQLiteException ex) {
38.
if
(writable) {
39.
throw
ex;
40.
}
41.
Log.e(TAG,
"Couldn't open "
+ mName
42.
+
" for writing (will try read-only):"
, ex);
43.
final
String path = mContext.getDatabasePath(mName).getPath();
44.
db = SQLiteDatabase.openDatabase(path, mFactory,
45.
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
46.
}
47.
}
48.
49.
onConfigure(db);
50.
51.
final
int
version = db.getVersion();
52.
if
(version != mNewVersion) {
53.
if
(db.isReadOnly()) {
54.
throw
new
SQLiteException(
"Can't upgrade read-only database from version "
+
55.
db.getVersion() +
" to "
+ mNewVersion +
": "
+ mName);
56.
}
57.
58.
db.beginTransaction();
59.
try
{
60.
if
(version ==
0
) {
61.
onCreate(db);
62.
}
else
{
63.
if
(version > mNewVersion) {
64.
onDowngrade(db, version, mNewVersion);
65.
}
else
{
66.
onUpgrade(db, version, mNewVersion);
67.
}
68.
}
69.
db.setVersion(mNewVersion);
70.
db.setTransactionSuccessful();
71.
}
finally
{
72.
db.endTransaction();
73.
}
74.
}
75.
76.
onOpen(db);
77.
78.
if
(db.isReadOnly()) {
79.
Log.w(TAG,
"Opened "
+ mName +
" in read-only mode"
);
80.
}
81.
82.
mDatabase = db;
83.
return
db;
84.
}
finally
{
85.
mIsInitializing =
false
;
86.
if
(db !=
null
&& db != mDatabase) {
87.
db.close();
88.
}
89.
}
90.
}
这个方法的东西就有点多了,貌似也是最核心的部分。
首先我们看到一个字段mDatabase,是个SQLiteDatabase类型的
1.
private
SQLiteDatabase mDatabase;
下面来看一下开始部分的代码:
01.
if
(mDatabase !=
null
) {
02.
if
(!mDatabase.isOpen()) {
03.
// Darn! The user closed the database by calling mDatabase.close().
04.
mDatabase =
null
;
05.
}
else
if
(!writable || !mDatabase.isReadOnly()) {
06.
// The database is already open for business.
07.
return
mDatabase;
08.
}
09.
}
如果mDatabase不为null的话,然后在判断如果这个数据库是否是关闭的状态,如果关闭了就将其设置null。如果没有关闭,就判断他的状态是可读还是可写的状态,然后返回一个实例即可.
继续下面的代码:
01.
SQLiteDatabase db = mDatabase;
02.
try
{
03.
mIsInitializing =
true
;
04.
05.
if
(db !=
null
) {
06.
if
(writable && db.isReadOnly()) {
07.
db.reopenReadWrite();
08.
}
09.
}
else
if
(mName ==
null
) {
10.
db = SQLiteDatabase.create(
null
);
11.
}
else
{
12.
try
{
13.
if
(DEBUG_STRICT_READONLY && !writable) {
14.
final
String path = mContext.getDatabasePath(mName).getPath();
15.
db = SQLiteDatabase.openDatabase(path, mFactory,
16.
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
17.
}
else
{
18.
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
19.
Context.MODE_ENABLE_WRITE_AHEAD_LOGGING :
0
,
20.
mFactory, mErrorHandler);
21.
}
22.
}
catch
(SQLiteException ex) {
23.
if
(writable) {
24.
throw
ex;
25.
}
26.
Log.e(TAG,
"Couldn't open "
+ mName
27.
+
" for writing (will try read-only):"
, ex);
28.
final
String path = mContext.getDatabasePath(mName).getPath();
29.
db = SQLiteDatabase.openDatabase(path, mFactory,
30.
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
31.
}
32.
}
这段代码主要是初始化一个SQLiteDatabase对象。
继续:
01.
final
int
version = db.getVersion();
02.
if
(version != mNewVersion) {
03.
if
(db.isReadOnly()) {
04.
throw
new
SQLiteException(
"Can't upgrade read-only database from version "
+
05.
db.getVersion() +
" to "
+ mNewVersion +
": "
+ mName);
06.
}
07.
08.
db.beginTransaction();
09.
try
{
10.
if
(version ==
0
) {
11.
onCreate(db);
12.
}
else
{
13.
if
(version > mNewVersion) {
14.
onDowngrade(db, version, mNewVersion);
15.
}
else
{
16.
onUpgrade(db, version, mNewVersion);
17.
}
18.
}
19.
db.setVersion(mNewVersion);
20.
db.setTransactionSuccessful();
21.
}
finally
{
22.
db.endTransaction();
23.
}
24.
}
这段代码就是我们这次的主要研究对象,也会解决我们的很多疑问。代码的逻辑很简单:
首先他获取到当前数据库的版本号,这里的db就是之前代码中创建的一个SQLiteDatabase对象,然后调用它的getVersion方法获取版本号,查看getVersion的源码:
1.
public
int
getVersion() {
2.
return
((Long) DatabaseUtils.longForQuery(
this
,
"PRAGMA user_version;"
,
null
)).intValue();
3.
}
这里看到是一个DatabaseUtils类的longForQuery方法,继续找到这个方法的源码:
1.
public
static
long
longForQuery(SQLiteDatabase db, String query, String[] selectionArgs) {
2.
SQLiteStatement prog = db.compileStatement(query);
3.
try
{
4.
return
longForQuery(prog, selectionArgs);
5.
}
finally
{
6.
prog.close();
7.
}
8.
}
找到这个方法了,其实他内部很简单,就是执行一个sql语句,sql语句为:
1.
PRAGMA user_version;
那么当我们首次创建数据库的时候他的版本值是多少呢?其实这个可以猜的。可能为0。那么我们就来做个验证吧。这里的验证不是用代码的方式了,而是借助于一个软件:Sqlite Expert
这个软件用起来还是比较简单的。我们新建一个数据库,然后执行上面的SQL语句,结果如下图:
果然第一次得到的版本号是0,好的下面继续来看一下之前的代码分析,之前分析到了,获取数据库的版本号,然后就是和之前的版本进行比较,如果不相等
这里又会做一个判断,判断当前的数据库是否为只读的,如果是只读的话,是不能进行后续的更新操作,抛个异常。
1.
if
(db.isReadOnly()) {
2.
throw
new
SQLiteException(
"Can't upgrade read-only database from version "
+
3.
db.getVersion() +
" to "
+ mNewVersion +
": "
+ mName);
4.
}
如果不是只读的话,继续下面的代码:
01.
db.beginTransaction();
02.
try
{
03.
if
(version ==
0
) {
04.
onCreate(db);
05.
}
else
{
06.
if
(version > mNewVersion) {
07.
onDowngrade(db, version, mNewVersion);
08.
}
else
{
09.
onUpgrade(db, version, mNewVersion);
10.
}
11.
}
12.
db.setVersion(mNewVersion);
13.
db.setTransactionSuccessful();
14.
}
finally
{
15.
db.endTransaction();
16.
}
开启一个事务。这里会判断如果获取到的数据库的版本为0,那么就执行onCreate方法,如果版本号有增加就会执行onUpgrade方法,如果版本号有递减的话,就会执行onDowngrade方法,抛异常了。
1.
public
void
onDowngrade(SQLiteDatabase db,
int
oldVersion,
int
newVersion) {
2.
throw
new
SQLiteException(
"Can't downgrade database from version "
+
3.
oldVersion +
" to "
+ newVersion);
4.
}
然后设置数据库的版本为最新的值。结束事务。
这里在使用的时候遇到一个问题,就是我在onUpgrade方法中调用了onCreate方法,但是此时onCreate方法会报异常,当然我们将其捕获了(在外面使用Helper的时候捕获的),但是每次打开应用的时候,onUpgrade方法都会执行。当时一直在找原因。没头绪呀。
其实这里就可以找到原因,因为当onUpgrade方法报异常(因为在onUpgrade方法中调用了onCreate方法,当onCreate方法报异常时,onUpgrade方法没有捕获到这个异常就还会报异常)之后,后续代码就不执行了,那么下面的设置数据库最新版本号的代码也不会执行了,所以每次打开app的时候,会进行版本号的比对,结果还是会执行onUpgrade方法,这个方法还是会报异常,所以会出现每次打开app的时候onUpgrade方法都会执行一次。下面来看一下
01.
package
com.sohu.sqlitedemo;
02.
03.
import
android.content.Context;
04.
import
android.database.sqlite.SQLiteDatabase;
05.
import
android.database.sqlite.SQLiteDatabase.CursorFactory;
06.
import
android.database.sqlite.SQLiteOpenHelper;
07.
import
android.util.Log;
08.
import
android.widget.Toast;
09.
10.
public
class
DBOpenHelper
extends
SQLiteOpenHelper {
11.
private
static
final
String DBNAME =
"ads.db"
;
12.
private
static
final
int
VERSION =
1
;
13.
14.
public
DBOpenHelper(Context context) {
15.
super
(context, DBNAME,
null
, VERSION);
16.
}
17.
18.
@Override
19.
public
void
onCreate(SQLiteDatabase db) {
20.
Log.i(
"DEMO"
,
"oldVersion,onCreate()"
);
21.
db.execSQL(
"CREATE TABLE IF NOT EXISTS offlineBanner("
//
22.
+
"id integer primary key autoincrement,"
//
23.
+
"vid VARCHAR(100),"
//
24.
//+ "adsequences integer,"
25.
//+ "isofflineads integer,"
26.
+
"Impression VARCHAR(2000),"
//
27.
+
"Duration VARCHAR(50),"
//
28.
+
"ClickThrough VARCHAR(500),"
//
29.
+
"ClickTracking VARCHAR(500),"
//
30.
+
"MediaFile VARCHAR(500),"
//
31.
+
"creativeView VARCHAR(500),"
//
32.
+
"start VARCHAR(500),"
//
33.
+
"firstQuartile VARCHAR(500),"
//
34.
+
"midpoint VARCHAR(500),"
//
35.
+
"thirdQuartile VARCHAR(500),"
//
36.
+
"complete VARCHAR(500),"
//
37.
+
"sdkTracking VARCHAR(2000),"
//
38.
+
"sdkClick VARCHAR(2000),"
//
39.
+
"time VARCHAR(50));"
);
//
40.
//这行代码会报异常
41.
db.execSQL(
"CREATE TABLE offlinePause("
//
42.
+
"id integer primary key autoincrement,"
//
43.
+
"vid VARCHAR(100),"
//
44.
+
"Impression VARCHAR(2000),"
//
45.
+
"StaticResource VARCHAR(500),"
//
46.
+
"NonLinearClickThrough VARCHAR(500),"
//
47.
+
"sdkTracking VARCHAR(2000),"
//
48.
+
"sdkClick VARCHAR(2000),"
//
49.
+
"time VARCHAR(50));"
);
//
50.
51.
db.execSQL(
"CREATE TABLE IF NOT EXISTS download_url ("
//
52.
+
"id integer primary key autoincrement,"
//
53.
+
"url varchar(500),"
//
54.
+
"status integer,"
//
55.
+
"length integer)"
);
//
56.
}
57.
58.
@Override
59.
public
void
onUpgrade(SQLiteDatabase db,
int
oldVersion,
int
newVersion) {
60.
Log.i(
"DEMO"
,
"oldVersion="
+ oldVersion +
",newVersion"
+ newVersion);
61.
if
(oldVersion != newVersion) {
62.
db.execSQL(
"ALTER TABLE offlineBanner ADD COLUMN adsequence integer;"
);
63.
db.execSQL(
"ALTER TABLE offlineBanner ADD COLUMN isofflinead integer;"
);
64.
db.execSQL(
"DROP TABLE IF EXISTS download_url"
);
65.
}
66.
//执行方法会报异常
67.
onCreate(db);
68.
}
69.
}
报异常的原因很简单。当我需要更新的时候,就会执行onUpgrade方法,这里又执行了onCreate方法,在创建pause表的时候会报异常,因为这个表已经存在了。
当我们把代码外面加上try...catch的时候:
1.
try
{
2.
//开始使用数据库
3.
DBOpenHelper openHelper =
new
DBOpenHelper(
this
);
4.
SQLiteDatabase db = openHelper.getWritableDatabase();
5.
db.close();
6.
}
catch
(Exception e){
7.
}
问题会解决,但是onUpgrade方法就会执行多次:
所以还是要解决一下onCreate方法中的异常问题,那个异常就是因为表存在了,所以建立表的时候需要判断表是否已经存在了。这样就可以了。
上面从源码的角度了解了原理,下面来看一下实例:
做Android应用,不可避免的会与SQLite打交道。随着应用的不断升级,原有的数据库结构可能已经不再适应新的功能,这时候,就需要对SQLite数据库的结构进行升级了。 SQLite提供了ALTER TABLE命令,允许用户重命名或添加新的字段到已有表中,但是不能从表中删除字段。
并且只能在表的末尾添加字段,比如,为 Subscription添加两个字段:1.
ALTER TABLE Subscription ADD COLUMN Activation BLOB;
2.
ALTER TABLE Subscription ADD COLUMN Key BLOB;
另外,如果遇到复杂的修改操作,比如在修改的同时,需要进行数据的转移,那么可以采取在一个事务中执行如下语句来实现修改表的需求。
1. 将表名改为临时表1.
ALTER TABLE Subscription RENAME TO __temp__Subscription;
2. 创建新表
1.
CREATE TABLE Subscription (OrderId VARCHAR(
32
) PRIMARY KEY ,UserName VARCHAR(
32
) NOT NULL ,ProductId VARCHAR(
16
)
2.
NOT NULL);
3. 导入数据
1.
INSERT INTO Subscription SELECT OrderId, “”, ProductId FROM __temp__Subscription;
1.
INSERT INTO Subscription() SELECT OrderId, “”, ProductId FROM __temp__Subscription;
4. 删除临时表
1.
DROP TABLE __temp__Subscription;
当然,如果遇到减少字段的情况,也可以通过创建临时表的方式来实现。
Android应用程序更新的时候如果数据库修改了字段需要更新数据库,并且保留原来的数据库数据:
这是原有的数据库表
1.
CREATE_BOOK =
"create table book(bookId integer primarykey,bookName text);"
;
1.
CREATE_BOOK =
"create table book(bookId integer primarykey,bookName text,bookContent text);"
;
1.
CREATE_TEMP_BOOK =
"alter table book rename to _temp_book"
;
1.
INSERT_DATA =
"insert into book select *,' ' from _temp_book"
;(注意
' '
是为新加的字段插入默认值的必须加上,否则就会出错)
然后我们把备份表干掉就行啦。
1.
DROP_BOOK =
"drop table _temp_book"
;
然后把数据库的版本后修改一下,再次创建数据库操作对象的时候就会自动更新(注:更新的时候第一个创建的操作数据的对象必须是可写的,也就是通过这个方法getWritableDatabase()获取的数据库操作对象)
然后在onUpgrade()方法中执行上述sql语句就OK
01.
public
class
DBservice
extends
SQLiteOpenHelper{
02.
private
String CREATE_BOOK =
"create table book(bookId integer primarykey,bookName text);"
;
03.
private
String CREATE_TEMP_BOOK =
"alter table book rename to _temp_book"
;
04.
private
String INSERT_DATA =
"insert into book select *,'' from _temp_book"
;
05.
private
String DROP_BOOK =
"drop table _temp_book"
;
06.
07.
public
DBservice(Context context, String name, CursorFactory factory,
int
version) {
08.
super
(context, name, factory, version);
09.
}
10.
11.
@Override
12.
public
void
onCreate(SQLiteDatabase db) {
13.
db.execSQL(CREATE_BOOK);
14.
}
15.
16.
@Override
17.
public
void
onUpgrade(SQLiteDatabase db,
int
oldVersion,
int
newVersion) {
18.
switch
(newVersion) {
19.
case
2
:
20.
db.execSQL(CREATE_TEMP_BOOK);
21.
db.execSQL(CREATE_BOOK);
22.
db.execSQL(INSERT_DATA);
23.
db.execSQL(DROP_BOOK);
24.
break
;
25.
}
26.
}
27.
}
总结:这次遇到的问题,开始的时候不知道怎么解决,那我们就继续去看那个操蛋的代码吧,这里的源代码其实不难的~~,但是我们要养成看源代码的习惯。
- APP版本升级,数据库数据如何办?
- iOS 版本迭代 App升级安装 CoreData数据库升级
- CoreData(数据库升级 )版本迁移-iOS App升级安装
- CoreData(数据库升级 )版本迁移-iOS App升级安装
- Android 升级版本 如何保存旧数据
- 版本升级 如何保存旧数据
- Android 升级版本 如何保存旧数据
- Android 升级版本 如何保存旧数据
- android app 版本升级
- App版本升级接口
- app版本升级
- iOS APP升级版本
- APP版本强制升级
- APP升级版本比较
- iOS-App版本升级时数据库的迁移更新
- Android 版本升级涉及到的数据库数据迁移问题
- 浅谈Android数据库版本升级及数据的迁移
- Android Sqlite数据库跨版本升级 保存之前数据
- mysql安装
- Python Import机制备忘
- 手机号正则表达式匹配
- Java-IO之ProgressMonitorInputStream
- hdu5700区间交(线段树)
- APP版本升级,数据库数据如何办?
- 源码编译mysql5.6
- Appium控件定位
- c++容器使用经验总结
- PHP入门(7) 静态属性的访问
- SyntaxError: Non-ASCII character '\xb2' in file
- 标准模板库STL之vector
- c语言==指针(8)
- 3 控制器设计