本文介绍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程序设计有所帮助。