本文介绍android平台进行数据存储的五大方式,分别如下:
1 使用sharedpreferences存储数据
2 文件存储数据
3 sqlite数据库存储数据
4 使用contentprovider存储数据
5 网络存储数据
下面详细讲解这五种方式的特点
第一种: 使用sharedpreferences存储数据
适用范围:保存少量的数据,且这些数据的格式非常简单:字符串型、基本类型的值。比如应用程序的各种配置信息(如是否打开音效、是否使用震动效果、小游戏的玩家积分等),解锁口 令密码等
核心原理:保存基于xml文件存储的key-value键值对数据,通常用来存储一些简单的配置信息。通过ddms的file explorer面板,展开文件浏览树,很明显sharedpreferences数据总是存储在/data/data/<package name>/shared_prefs目录下。sharedpreferences对象本身只能获取数据而不支持存储和修改,存储修改是通过sharedpreferences.edit()获取的内部接口editor对象实现。 sharedpreferences本身是一 个接口,程序无法直接创建sharedpreferences实例,只能通过context提供的getsharedpreferences(string name, int mode)方法来获取sharedpreferences实例,该方法中name表示要操作的xml文件名,第二个参数具体如下:
context.mode_private: 指定该sharedpreferences数据只能被本应用程序读、写。
context.mode_world_readable: 指定该sharedpreferences数据能被其他应用程序读,但不能写。
context.mode_world_writeable: 指定该sharedpreferences数据能被其他应用程序读,写
editor有如下主要重要方法:
sharedpreferences.editor clear():清空sharedpreferences里所有数据
sharedpreferences.editor putxxx(string key , xxx value): 向sharedpreferences存入指定key对应的数据,其中xxx 可以是boolean,float,int等各种基本类型据
sharedpreferences.editor remove(): 删除sharedpreferences中指定key对应的数据项
boolean commit(): 当editor编辑完成后,使用该方法提交修改
实际案例:运行界面如下
这里只提供了两个按钮和一个输入文本框,布局简单,故在此不给出界面布局文件了,程序核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
class viewocl implements view.onclicklistener{
@override
public void onclick(view v) {
switch (v.getid()){
case r.id.btnset:
//步骤1:获取输入值
string code = txtcode.gettext().tostring().trim();
//步骤2-1:创建一个sharedpreferences.editor接口对象,lock表示要写入的xml文件名,mode_world_writeable写操作
sharedpreferences.editor editor = getsharedpreferences( "lock" , mode_world_writeable).edit();
//步骤2-2:将获取过来的值放入文件
editor.putstring( "code" , code);
//步骤3:提交
editor.commit();
toast.maketext(getapplicationcontext(), "口令设置成功" , toast.length_long).show();
break ;
case r.id.btnget:
//步骤1:创建一个sharedpreferences接口对象
sharedpreferences read = getsharedpreferences( "lock" , mode_world_readable);
//步骤2:获取文件中的值
string value = read.getstring( "code" , "" );
toast.maketext(getapplicationcontext(), "口令为:" +value, toast.length_long).show();
break ;
}
}
}
|
读写其他应用的sharedpreferences: 步骤如下
1、在创建sharedpreferences时,指定mode_world_readable模式,表明该sharedpreferences数据可以被其他程序读取
2、创建其他应用程序对应的context:
context pvcount = createpackagecontext("com.tony.app", context.context_ignore_security);这里的com.tony.app就是其他程序的包名
3、使用其他程序的context获取对应的sharedpreferences
sharedpreferences read = pvcount.getsharedpreferences("lock", context.mode_world_readable);
4、如果是写入数据,使用editor接口即可,所有其他操作均和前面一致。
sharedpreferences对象与sqlite数据库相比,免去了创建数据库,创建表,写sql语句等诸多操作,相对而言更加方便,简洁。但是sharedpreferences也有其自身缺陷,比如其职能存储boolean,int,float,long和string五种简单的数据类型,比如其无法进行条件查询等。所以不论sharedpreferences的数据存储操作是如何简单,它也只能是存储方式的一种补充,而无法完全替代如sqlite数据库这样的其他数据存储方式。
第二种: 文件存储数据
核心原理: context提供了两个方法来打开数据文件里的文件io流 fileinputstream openfileinput(string name); fileoutputstream(string name , int mode),这两个方法第一个参数 用于指定文件名,第二个参数指定打开文件的模式。具体有以下值可选:
mode_private:为默认操作模式,代表该文件是私有数据,只能被应用本身访问,在该模式下,写入的内容会覆盖原文件的内容,如果想把新写入的内容追加到原文件中。可 以使用context.mode_append
mode_append:模式会检查文件是否存在,存在就往文件追加内容,否则就创建新文件。
mode_world_readable:表示当前文件可以被其他应用读取;
mode_world_writeable:表示当前文件可以被其他应用写入。
除此之外,context还提供了如下几个重要的方法:
getdir(string name , int mode):在应用程序的数据文件夹下获取或者创建name对应的子目录
file getfilesdir():获取该应用程序的数据文件夹得绝对路径
string[] filelist():返回该应用数据文件夹的全部文件
实际案例:界面沿用上图
核心代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
public string read() {
try {
fileinputstream instream = this .openfileinput( "message.txt" );
byte [] buffer = new byte [ 1024 ];
int hasread = 0 ;
stringbuilder sb = new stringbuilder();
while ((hasread = instream.read(buffer)) != - 1 ) {
sb.append( new string(buffer, 0 , hasread));
}
instream.close();
return sb.tostring();
} catch (exception e) {
e.printstacktrace();
}
return null ;
}
public void write(string msg){
// 步骤1:获取输入值
if (msg == null ) return ;
try {
// 步骤2:创建一个fileoutputstream对象,mode_append追加模式
fileoutputstream fos = openfileoutput( "message.txt" ,
mode_append);
// 步骤3:将获取过来的值放入文件
fos.write(msg.getbytes());
// 步骤4:关闭数据流
fos.close();
} catch (exception e) {
e.printstacktrace();
}
}
|
openfileoutput()方法的第一参数用于指定文件名称,不能包含路径分隔符“/” ,如果文件不存在,android 会自动创建它。创建的文件保存在/data/data/<package name>/files目录,如: /data/data/cn.tony.app/files/message.txt,
下面讲解某些特殊文件读写需要注意的地方:
读写sdcard上的文件
其中读写步骤按如下进行:
1、调用environment的getexternalstoragestate()方法判断手机上是否插了sd卡,且应用程序具有读写sd卡的权限,如下代码将返回true
environment.getexternalstoragestate().equals(environment.media_mounted)
2、调用environment.getexternalstoragedirectory()方法来获取外部存储器,也就是sd卡的目录,或者使用"/mnt/sdcard/"目录
3、使用io流操作sd卡上的文件
注意点:手机应该已插入sd卡,对于模拟器而言,可通过mksdcard命令来创建虚拟存储卡
必须在androidmanifest.xml上配置读写sd卡的权限
1
2
|
<uses-permission android:name= "android.permission.mount_unmount_filesystems" />
<uses-permission android:name= "android.permission.write_external_storage" />
|
案例代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
|
// 文件写操作函数
private void write(string content) {
if (environment.getexternalstoragestate().equals(
environment.media_mounted)) { // 如果sdcard存在
file file = new file(environment.getexternalstoragedirectory()
.tostring()
+ file.separator
+ dir
+ file.separator
+ filename); // 定义file类对象
if (!file.getparentfile().exists()) { // 父文件夹不存在
file.getparentfile().mkdirs(); // 创建文件夹
}
printstream out = null ; // 打印流对象用于输出
try {
out = new printstream( new fileoutputstream(file, true )); // 追加文件
out.println(content);
} catch (exception e) {
e.printstacktrace();
} finally {
if (out != null ) {
out.close(); // 关闭打印流
}
}
} else { // sdcard不存在,使用toast提示用户
toast.maketext( this , "保存失败,sd卡不存在!" , toast.length_long).show();
}
}
// 文件读操作函数
private string read() {
if (environment.getexternalstoragestate().equals(
environment.media_mounted)) { // 如果sdcard存在
file file = new file(environment.getexternalstoragedirectory()
.tostring()
+ file.separator
+ dir
+ file.separator
+ filename); // 定义file类对象
if (!file.getparentfile().exists()) { // 父文件夹不存在
file.getparentfile().mkdirs(); // 创建文件夹
}
scanner scan = null ; // 扫描输入
stringbuilder sb = new stringbuilder();
try {
scan = new scanner( new fileinputstream(file)); // 实例化scanner
while (scan.hasnext()) { // 循环读取
sb.append(scan.next() + "\n" ); // 设置文本
}
return sb.tostring();
} catch (exception e) {
e.printstacktrace();
} finally {
if (scan != null ) {
scan.close(); // 关闭打印流
}
}
} else { // sdcard不存在,使用toast提示用户
toast.maketext( this , "读取失败,sd卡不存在!" , toast.length_long).show();
}
return null ;
}
|
第三种:sqlite存储数据
sqlite是轻量级嵌入式数据库引擎,它支持 sql 语言,并且只利用很少的内存就有很好的性能。现在的主流移动设备像android、iphone等都使用sqlite作为复杂数据的存储引擎,在我们为移动设备开发应用程序时,也许就要使用到sqlite来存储我们大量的数据,所以我们就需要掌握移动设备上的sqlite开发技巧
sqlitedatabase类为我们提供了很多种方法,上面的代码中基本上囊括了大部分的数据库操作;对于添加、更新和删除来说,我们都可以使用
1
2
3
|
db.executesql(string sql);
db.executesql(string sql, object[] bindargs);
//sql语句中使用占位符,然后第二个参数是实际的参数集
|
除了统一的形式之外,他们还有各自的操作方法:
1
2
3
|
db.insert(string table, string nullcolumnhack, contentvalues values);
db.update(string table, contentvalues values, string whereclause, string whereargs);
db.delete(string table, string whereclause, string whereargs);
|
以上三个方法的第一个参数都是表示要操作的表名;insert中的第二个参数表示如果插入的数据每一列都为空的话,需要指定此行中某一列的名称,系统将此列设置为null,不至于出现错误;insert中的第三个参数是contentvalues类型的变量,是键值对组成的map,key代表列名,value代表该列要插入的值;update的第二个参数也很类似,只不过它是更新该字段key为最新的value值,第三个参数whereclause表示where表达式,比如“age > ? and age < ?”等,最后的whereargs参数是占位符的实际参数值;delete方法的参数也是一样
下面给出demo
数据的添加
1.使用insert方法
1
2
3
4
5
6
7
8
|
contentvalues cv = new contentvalues(); //实例化一个contentvalues用来装载待插入的数据
cv.put( "title" , "you are beautiful" ); //添加title
cv.put( "weather" , "sun" ); //添加weather
cv.put( "context" , "xxxx" ); //添加context
string publish = new simpledateformat( "yyyy-mm-dd hh:mm:ss" )
.format( new date());
cv.put( "publish " ,publish); //添加publish
db.insert( "diary" , null ,cv); //执行插入操作
|
2.使用execsql方式来实现
1
2
|
string sql = "insert into user(username,password) values ( 'jack johnson' , 'ilovepopmuisc' ); //插入操作的sql语句
db.execsql(sql); //执行sql语句
|
数据的删除
同样有2种方式可以实现
1
2
3
|
string whereclause = "username=?" ; //删除的条件
string[] whereargs = { "jack johnson" }; //删除的条件参数
db.delete( "user" ,whereclause,whereargs); //执行删除
|
使用execsql方式的实现
1
2
|
string sql = "delete from user where username='jack johnson'" ; //删除操作的sql语句
db.execsql(sql); //执行删除操作
|
数据修改
同上,仍是2种方式
1
2
3
4
5
|
contentvalues cv = new contentvalues(); //实例化contentvalues
cv.put( "password" , "ihatepopmusic" ); //添加要更改的字段及内容
string whereclause = "username=?" ; //修改条件
string[] whereargs = { "jack johnson" }; //修改条件的参数
db.update( "user" ,cv,whereclause,whereargs); //执行修改
|
使用execsql方式的实现
1
2
|
string sql = "update user set password = 'ihatepopmusic' where username='jack johnson'" ; //修改的sql语句
db.execsql(sql); //执行修改
|
数据查询
下面来说说查询操作。查询操作相对于上面的几种操作要复杂些,因为我们经常要面对着各种各样的查询条件,所以系统也考虑到这种复杂性,为我们提供了较为丰富的查询形式:
1
2
3
4
|
db.rawquery(string sql, string[] selectionargs);
db.query(string table, string[] columns, string selection, string[] selectionargs, string groupby, string having, string orderby);
db.query(string table, string[] columns, string selection, string[] selectionargs, string groupby, string having, string orderby, string limit);
db.query(string distinct, string table, string[] columns, string selection, string[] selectionargs, string groupby, string having, string orderby, string limit);
|
上面几种都是常用的查询方法,第一种最为简单,将所有的sql语句都组织到一个字符串中,使用占位符代替实际参数,selectionargs就是占位符实际参数集;
各参数说明:
table:表名称
colums:表示要查询的列所有名称集
selection:表示where之后的条件语句,可以使用占位符
selectionargs:条件语句的参数数组
groupby:指定分组的列名
having:指定分组条件,配合groupby使用
orderby:y指定排序的列名
limit:指定分页参数
distinct:指定“true”或“false”表示要不要过滤重复值
cursor:返回值,相当于结果集resultset
最后,他们同时返回一个cursor对象,代表数据集的游标,有点类似于javase中的resultset。下面是cursor对象的常用方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
c.move( int offset); //以当前位置为参考,移动到指定行
c.movetofirst(); //移动到第一行
c.movetolast(); //移动到最后一行
c.movetoposition( int position); //移动到指定行
c.movetoprevious(); //移动到前一行
c.movetonext(); //移动到下一行
c.isfirst(); //是否指向第一条
c.islast(); //是否指向最后一条
c.isbeforefirst(); //是否指向第一条之前
c.isafterlast(); //是否指向最后一条之后
c.isnull( int columnindex); //指定列是否为空(列基数为0)
c.isclosed(); //游标是否已关闭
c.getcount(); //总数据项数
c.getposition(); //返回当前游标所指向的行数
c.getcolumnindex(string columnname); //返回某列名对应的列索引值
c.getstring( int columnindex); //返回当前行指定列的值
|
实现代码
1
2
3
4
5
6
7
8
9
|
string[] params = { 12345 , 123456 };
cursor cursor = db.query( "user" ,columns, "id=?" ,params, null , null , null ); //查询并获得游标
if (cursor.movetofirst()){ //判断游标是否为空
for ( int i= 0 ;i<cursor.getcount();i++){
cursor.move(i); //移动到指定记录
string username = cursor.getstring(cursor.getcolumnindex( "username" );
string password = cursor.getstring(cursor.getcolumnindex( "password" ));
}
}
|
通过rawquery实现的带参数查询
1
2
3
4
5
6
7
8
9
10
11
|
cursor result=db.rawquery( "select id, name, inventory from mytable" );
//cursor c = db.rawquery("s name, inventory from mytable where id=?",new stirng[]{"123456"});
result.movetofirst();
while (!result.isafterlast()) {
int id=result.getint( 0 );
string name=result.getstring( 1 );
int inventory=result.getint( 2 );
// do something useful with these
result.movetonext();
}
result.close();
|
在上面的代码示例中,已经用到了这几个常用方法中的一些,关于更多的信息,大家可以参考官方文档中的说明。
最后当我们完成了对数据库的操作后,记得调用sqlitedatabase的close()方法释放数据库连接,否则容易出现sqliteexception。
上面就是sqlite的基本应用,但在实际开发中,为了能够更好的管理和维护数据库,我们会封装一个继承自sqliteopenhelper类的数据库操作类,然后以这个类为基础,再封装我们的业务逻辑方法。
这里直接使用案例讲解:下面是案例demo的界面
sqliteopenhelper类介绍
sqliteopenhelper是sqlitedatabase的一个帮助类,用来管理数据库的创建和版本的更新。一般是建立一个类继承它,并实现它的oncreate和onupgrade方法。
方法名 | 方法描述 |
---|---|
sqliteopenhelper(context context,string name,sqlitedatabase.cursorfactory factory,int version) |
构造方法,其中 context 程序上下文环境 即:xxxactivity.this; name :数据库名字; factory:游标工厂,默认为null,即为使用默认工厂; version 数据库版本号 |
oncreate(sqlitedatabase db) | 创建数据库时调用 |
onupgrade(sqlitedatabase db,int oldversion , int newversion) | 版本更新时调用 |
getreadabledatabase() | 创建或打开一个只读数据库 |
getwritabledatabase() | 创建或打开一个读写数据库 |
首先创建数据库类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
import android.content.context;
import android.database.sqlite.sqlitedatabase;
import android.database.sqlite.sqlitedatabase.cursorfactory;
import android.database.sqlite.sqliteopenhelper;
public class sqlitedbhelper extends sqliteopenhelper {
// 步骤1:设置常数参量
private static final string database_name = "diary_db" ;
private static final int version = 1 ;
private static final string table_name = "diary" ;
// 步骤2:重载构造方法
public sqlitedbhelper(context context) {
super (context, database_name, null , version);
}
/*
* 参数介绍:context 程序上下文环境 即:xxxactivity.this
* name 数据库名字
* factory 接收数据,一般情况为null
* version 数据库版本号
*/
public sqlitedbhelper(context context, string name, cursorfactory factory,
int version) {
super (context, name, factory, version);
}
//数据库第一次被创建时,oncreate()会被调用
@override
public void oncreate(sqlitedatabase db) {
// 步骤3:数据库表的创建
string strsql = "create table "
+ table_name
+ "(tid integer primary key autoincrement,title varchar(20),weather varchar(10),context text,publish date)" ;
//步骤4:使用参数db,创建对象
db.execsql(strsql);
}
//数据库版本变化时,会调用onupgrade()
@override
public void onupgrade(sqlitedatabase arg0, int arg1, int arg2) {
}
}
|
正如上面所述,数据库第一次创建时oncreate方法会被调用,我们可以执行创建表的语句,当系统发现版本变化之后,会调用onupgrade方法,我们可以执行修改表结构等语句。
我们需要一个dao,来封装我们所有的业务方法,代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
import android.content.context;
import android.database.cursor;
import android.database.sqlite.sqlitedatabase;
import com.chinasoft.dbhelper.sqlitedbhelper;
public class diarydao {
private sqlitedbhelper sqlitedbhelper;
private sqlitedatabase db;
// 重写构造方法
public diarydao(context context) {
this .sqlitedbhelper = new sqlitedbhelper(context);
db = sqlitedbhelper.getwritabledatabase();
}
// 读操作
public string execquery( final string strsql) {
try {
system.out.println( "strsql>" + strsql);
// cursor相当于jdbc中的resultset
cursor cursor = db.rawquery(strsql, null );
// 始终让cursor指向数据库表的第1行记录
cursor.movetofirst();
// 定义一个stringbuffer的对象,用于动态拼接字符串
stringbuffer sb = new stringbuffer();
// 循环游标,如果不是最后一项记录
while (!cursor.isafterlast()) {
sb.append(cursor.getint( 0 ) + "/" + cursor.getstring( 1 ) + "/"
+ cursor.getstring( 2 ) + "/" + cursor.getstring( 3 ) + "/"
+ cursor.getstring( 4 )+ "#" );
//cursor游标移动
cursor.movetonext();
}
db.close();
return sb.deletecharat(sb.length()- 1 ).tostring();
} catch (runtimeexception e) {
e.printstacktrace();
return null ;
}
}
// 写操作
public boolean execother( final string strsql) {
db.begintransaction(); //开始事务
try {
system.out.println( "strsql" + strsql);
db.execsql(strsql);
db.settransactionsuccessful(); //设置事务成功完成
db.close();
return true ;
} catch (runtimeexception e) {
e.printstacktrace();
return false ;
} finally {
db.endtransaction(); //结束事务
}
}
}
|
我们在dao构造方法中实例化sqlitedbhelper并获取一个sqlitedatabase对象,作为整个应用的数据库实例;在增删改信息时,我们采用了事务处理,确保数据完整性;最后要注意释放数据库资源db.close(),这一个步骤在我们整个应用关闭时执行,这个环节容易被忘记,所以朋友们要注意。
我们获取数据库实例时使用了getwritabledatabase()方法,也许朋友们会有疑问,在getwritabledatabase()和getreadabledatabase()中,你为什么选择前者作为整个应用的数据库实例呢?在这里我想和大家着重分析一下这一点。
我们来看一下sqliteopenhelper中的getreadabledatabase()方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
public synchronized sqlitedatabase getreadabledatabase() {
if (mdatabase != null && mdatabase.isopen()) {
// 如果发现mdatabase不为空并且已经打开则直接返回
return mdatabase;
}
if (misinitializing) {
// 如果正在初始化则抛出异常
throw new illegalstateexception( "getreadabledatabase called recursively" );
}
// 开始实例化数据库mdatabase
try {
// 注意这里是调用了getwritabledatabase()方法
return getwritabledatabase();
} catch (sqliteexception e) {
if (mname == null )
throw e; // can't open a temp database read-only!
log.e(tag, "couldn't open " + mname + " for writing (will try read-only):" , e);
}
// 如果无法以可读写模式打开数据库 则以只读方式打开
sqlitedatabase db = null ;
try {
misinitializing = true ;
string path = mcontext.getdatabasepath(mname).getpath(); // 获取数据库路径
// 以只读方式打开数据库
db = sqlitedatabase.opendatabase(path, mfactory, sqlitedatabase.open_readonly);
if (db.getversion() != mnewversion) {
throw new sqliteexception( "can't upgrade read-only database from version " + db.getversion() + " to "
+ mnewversion + ": " + path);
}
onopen(db);
log.w(tag, "opened " + mname + " in read-only mode" );
mdatabase = db; // 为mdatabase指定新打开的数据库
return mdatabase; // 返回打开的数据库
} finally {
misinitializing = false ;
if (db != null && db != mdatabase)
db.close();
}
}
|
在getreadabledatabase()方法中,首先判断是否已存在数据库实例并且是打开状态,如果是,则直接返回该实例,否则试图获取一个可读写模式的数据库实例,如果遇到磁盘空间已满等情况获取失败的话,再以只读模式打开数据库,获取数据库实例并返回,然后为mdatabase赋值为最新打开的数据库实例。既然有可能调用到getwritabledatabase()方法,我们就要看一下了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
|
public synchronized sqlitedatabase getwritabledatabase() {
if (mdatabase != null && mdatabase.isopen() && !mdatabase.isreadonly()) {
// 如果mdatabase不为空已打开并且不是只读模式 则返回该实例
return mdatabase;
}
if (misinitializing) {
throw new illegalstateexception( "getwritabledatabase called recursively" );
}
// if we have a read-only database open, someone could be using it
// (though they shouldn't), which would cause a lock to be held on
// the file, and our attempts to open the database read-write would
// fail waiting for the file lock. to prevent that, we acquire the
// lock on the read-only database, which shuts out other users.
boolean success = false ;
sqlitedatabase db = null ;
// 如果mdatabase不为空则加锁 阻止其他的操作
if (mdatabase != null )
mdatabase.lock();
try {
misinitializing = true ;
if (mname == null ) {
db = sqlitedatabase.create( null );
} else {
// 打开或创建数据库
db = mcontext.openorcreatedatabase(mname, 0 , mfactory);
}
// 获取数据库版本(如果刚创建的数据库,版本为0)
int version = db.getversion();
// 比较版本(我们代码中的版本mnewversion为1)
if (version != mnewversion) {
db.begintransaction(); // 开始事务
try {
if (version == 0 ) {
// 执行我们的oncreate方法
oncreate(db);
} else {
// 如果我们应用升级了mnewversion为2,而原版本为1则执行onupgrade方法
onupgrade(db, version, mnewversion);
}
db.setversion(mnewversion); // 设置最新版本
db.settransactionsuccessful(); // 设置事务成功
} finally {
db.endtransaction(); // 结束事务
}
}
onopen(db);
success = true ;
return db; // 返回可读写模式的数据库实例
} finally {
misinitializing = false ;
if (success) {
// 打开成功
if (mdatabase != null ) {
// 如果mdatabase有值则先关闭
try {
mdatabase.close();
} catch (exception e) {
}
mdatabase.unlock(); // 解锁
}
mdatabase = db; // 赋值给mdatabase
} else {
// 打开失败的情况:解锁、关闭
if (mdatabase != null )
mdatabase.unlock();
if (db != null )
db.close();
}
}
}
|
大家可以看到,几个关键步骤是,首先判断mdatabase如果不为空已打开并不是只读模式则直接返回,否则如果mdatabase不为空则加锁,然后开始打开或创建数据库,比较版本,根据版本号来调用相应的方法,为数据库设置新版本号,最后释放旧的不为空的mdatabase并解锁,把新打开的数据库实例赋予mdatabase,并返回最新实例。
看完上面的过程之后,大家或许就清楚了许多,如果不是在遇到磁盘空间已满等情况,getreadabledatabase()一般都会返回和getwritabledatabase()一样的数据库实例,所以我们在dbmanager构造方法中使用getwritabledatabase()获取整个应用所使用的数据库实例是可行的。当然如果你真的担心这种情况会发生,那么你可以先用getwritabledatabase()获取数据实例,如果遇到异常,再试图用getreadabledatabase()获取实例,当然这个时候你获取的实例只能读不能写了
最后,让我们看一下如何使用这些数据操作方法来显示数据,界面核心逻辑代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
public class sqliteactivity extends activity {
public diarydao diarydao;
//因为getwritabledatabase内部调用了mcontext.openorcreatedatabase(mname, 0, mfactory);
//所以要确保context已初始化,我们可以把实例化dao的步骤放在activity的oncreate里
@override
protected void oncreate(bundle savedinstancestate) {
diarydao = new diarydao(sqliteactivity. this );
initdatabase();
}
class viewocl implements view.onclicklistener {
@override
public void onclick(view v) {
string strsql;
boolean flag;
string message;
switch (v.getid()) {
case r.id.btnadd:
string title = txttitle.gettext().tostring().trim();
string weather = txtweather.gettext().tostring().trim();;
string context = txtcontext.gettext().tostring().trim();;
string publish = new simpledateformat( "yyyy-mm-dd hh:mm:ss" )
.format( new date());
// 动态组件sql语句
strsql = "insert into diary values(null,'" + title + "','"
+ weather + "','" + context + "','" + publish + "')" ;
flag = diarydao.execother(strsql);
//返回信息
message = flag? "添加成功" : "添加失败" ;
toast.maketext(getapplicationcontext(), message, toast.length_long).show();
break ;
case r.id.btndelete:
strsql = "delete from diary where tid = 1" ;
flag = diarydao.execother(strsql);
//返回信息
message = flag? "删除成功" : "删除失败" ;
toast.maketext(getapplicationcontext(), message, toast.length_long).show();
break ;
case r.id.btnquery:
strsql = "select * from diary order by publish desc" ;
string data = diarydao.execquery(strsql);
toast.maketext(getapplicationcontext(), data, toast.length_long).show();
break ;
case r.id.btnupdate:
strsql = "update diary set title = '测试标题1-1' where tid = 1" ;
flag = diarydao.execother(strsql);
//返回信息
message = flag? "更新成功" : "更新失败" ;
toast.maketext(getapplicationcontext(), message, toast.length_long).show();
break ;
}
}
}
private void initdatabase() {
// 创建数据库对象
sqlitedbhelper sqlitedbhelper = new sqlitedbhelper(sqliteactivity. this );
sqlitedbhelper.getwritabledatabase();
system.out.println( "数据库创建成功" );
}
}
|
android sqlite3数据库管理工具
android sdk的tools目录下提供了一个sqlite3.exe工具,这是一个简单的sqlite数据库管理工具。开发者可以方便的使用其对sqlite数据库进行命令行的操作。
程序运行生成的*.db文件一般位于"/data/data/项目名(包括所处包名)/databases/*.db",因此要对数据库文件进行操作需要先找到数据库文件:
1、进入shell 命令
adb shell
2、找到数据库文件
#cd data/data
#ls --列出所有项目
#cd project_name --进入所需项目名
#cd databases
#ls --列出现寸的数据库文件
3、进入数据库
#sqlite3 test_db --进入所需数据库
会出现类似如下字样:
sqlite version 3.6.22
enter ".help" for instructions
enter sql statements terminated with a ";"
sqlite>
至此,可对数据库进行sql操作。
4、sqlite常用命令
>.databases --产看当前数据库
>.tables --查看当前数据库中的表
>.help --sqlite3帮助
>.schema --各个表的生成语句
希望本文所述对大家android程序设计有所帮助。