Android编程中的5种数据存储方式

时间:2021-09-04 19:40:42

本文介绍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编辑完成后,使用该方法提交修改

实际案例:运行界面如下

Android编程中的5种数据存储方式

这里只提供了两个按钮和一个输入文本框,布局简单,故在此不给出界面布局文件了,程序核心代码如下:        

?
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的界面

Android编程中的5种数据存储方式

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