下面会对Android平台下的数据存储方式做简单的介绍,主要包括基于文件的流读取,轻量级数据库SQLite,Content Provider 已经Preference
1 私有文件夹文件的写入和读取
介绍Android下进行文件读取前,必须先了解Android下的数据存储规则。在其他操作系统如Windows,应用程序可以自由的或者在特定的访问权限基础上访问或修改其他应用程序名下的文件等资源,在Android平台下,应用程序中所有的数据都是私有的,只是对自己可见的
当应用程序被安装到系统之后,其所在的包会有一个文件夹用于存放自己的数据,只有这个应用程序才有对这个文件夹的写入权限,这个私有文件夹位于Android系统下的/data/data/<应用程序包名>目录下,其他的应用程序都无法在这个文件夹中写入数据。除了存放私有数据文件夹外,应用程序也具有SD卡的写入权限。
使用文件件I/O方法可以直接往手机存储中存入数据,默认情况下这些文件不可以被其他的应用程序访问。Android平台支持Java平台下的文件I/O操作,主要使用FIleInputStream和FileOutputStream 这两个类来实现文件的存储和读取。获取这两个类对象的方式有两种。
(1)第一种方式就像Java平台下的实现方式一样通过构造器直接创建,如果需要向打开的文件末尾写入数据,可以通过使用构造器FileOutputStream(File file, boolean append)将append设置为true来实现,不过需要注意的是采用这种方式获得FileOutputStream对象时如果文件不存在或不可写入时,会抛出FileNotFoundException异常。
(2)第二种获取FileInputStream和FileOutputStream对象的方法是调用Context.openFileInput好Context.openFileOut两个方法来创建。初除了这两个方法外Context对象还提供了其他几个用于文件操作的方法,如下表:
Context对象中文件操作的API及说明:
方法名 说明
openFileInput(String filename) 打开应用程序私有目录下的指定私有文件以读入数据,返回一个FileInputStream对象
openFileOutput(String name,int mode) 打开应用程序私有目录下的指定私有文件以写入数据,返回一个FileOutputStream对象
如果文件不存在就创建这个文件
fileList() 搜索应用程序私有文件夹下的私有文件,返回所有文件名的String数组
deleteFile(String fileName) 删除指定文件名的文件,成功返回true,否则返回false
在使用openFileOutput 方法打开文件以写入数据,需要指定打开模式,默认为0,即MODE_PRIVATE.不同的模式对应的含义如下表:
常量 含义
MODE_PRIVATE 默认模式,文件只可以被调用该方法的应用程序访问
MODE_APPEND 如果文件已经存在就向该文件末尾继续写入数据,而不是覆盖原来的数据
MODE_WORLD_READABLE 赋予所有的应用程序对该文件读的权限
MODE_WORLD_WRITEABLE 赋予所有的应用程序对该文件写的权限
下面通过一个小例子来说明Android平台下的文件I/O操作方式,本例主要功能是在应用程序私有的数据文件夹下创建一个文件并读取其中的数据显示到屏幕的TextView
首先修改布局文件main.xml改后代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/textView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Activity类中主要进行工作是调用自己开发的文件写入读取方法来获得数据信息,并将其内容显示到TextView中。代码如下:
package karant.zhn;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import org.apache.http.util.EncodingUtils;
import android.R.integer;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class IOdroid extends Activity {
public static final String ENCODING = "UTF-8"; //常量,编码格式
String fileName = "karant.txt"; //文件名称
String message = "这是一个关于文件I/O的实例"; //写入和读出的数据信息
TextView textView; //TextView对象引用
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
wirteFileData(fileName, message); //创建文件并写入数据
String result = readFileData(fileName); //获得从文件中读入的数据
textView = (TextView)findViewById(R.id.textView);
textView.setText(result);
}
//向指定文件中写入指定的数据
public void wirteFileData(String Filename, String message) {
try {
FileOutputStream fileOutputStream = openFileOutput(fileName, MODE_PRIVATE); //获得FileOutputStream
byte[] bytes = message.getBytes(); //将要写入的字符串转换为byte数组
fileOutputStream.write(bytes);
fileOutputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
//打开指定的文件,读取其数据,返回字符串对象
public String readFileData(String fileName){
String result = "";
try {
FileInputStream fileInputStream = openFileInput(fileName); //获得FileInputStream对象
int length = fileInputStream.available(); //获取文件长度
byte[] buffer = new byte[length]; //创建和文件长度一样的数组
fileInputStream.read(buffer); //将内容存入数组
result = EncodingUtils.getString(buffer, ENCODING); //将byte数组转换为指定格式的字符串
fileInputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
2.读取Resources和Assets中的文件
在Android平台下,除了对应应用程序的私有文件夹中的文件进行操作之外,还可以从资源文件和Assets中获得输入流读取数据,这些文件分别存放在应用程序的res/raw目录和assets目录下,这些文件将会在编译的时候和其他文件一起被打包。
需要注意的是,来自Resource和Assets中的文件只可以读取而不能够进行写的操作,下面通过一个例子来说明如何从Resources和Assets中读取信息,首先分别在res/raw和assets目录下新建两个文本文件,test1.txt和test2.txt来进行读取。
为避免字符串转码带来的麻烦,可以将这两个文件的编码格式设置为UTF-8。设置编码格式的方法有多种。比较简单的一种是用Windows记事本代开文本文件,在另存为对话框中的编码格式中选择“UTF-8”
首先修改布局文件:main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/textView1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/textView2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
Activity源代码如下:
package karant.zhan;
import java.io.InputStream;
import org.apache.http.util.EncodingUtils;
import android.R.integer;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class ReadFRA extends Activity {
public static final String ENCODING = "UTF-8"; //常量,代表编码格式
TextView textView1;
TextView textView2;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
textView1 = (TextView)findViewById(R.id.textView1);
textView2 = (TextView)findViewById(R.id.textView2);
textView1.setText(getFromRaw("test1.txt"));
textView2.setText(getFromAssets("test2.txt"));
}
private String getFromAssets(String filename) {
String result = "";
try {
InputStream in = getResources().getAssets().open(filename); //从raw中的文件获取输入流
int length = in.available(); //获取文件的字节数
byte[] buffer = new byte[length]; //创建byte数组
in.read(buffer); //将文件中的数组读取到byte数组中
result = EncodingUtils.getString(buffer, ENCODING); //将byte数组转化成指定格式的字符
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
private String getFromRaw(String filename) {
String result = "";
try {
InputStream in = getResources().openRawResource(R.raw.test1); //从raw中的文件获取输入流
int length = in.available(); //获取文件的字节数
byte[] buffer = new byte[length]; //创建byte数组
in.read(buffer); //将文件中的数组读取到byte数组中
result = EncodingUtils.getString(buffer, ENCODING); //将byte数组转化成指定格式的字符
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
}
3.轻量级数据库SQLite
SQLite是一款开源的嵌入式数据库引擎,其多数的SQL92标准都提供了支持。相比于同样开源的MySQL和PostgreSQL来说,SQLite具有一下独特的地方:
(1)处理速度快。传统的数据库引擎采用的是客户端-服务端的访问方式,SQLite的数据库引擎嵌入到程序中作为其一部分,所以运行的速度要快的很多。
(2)占用资源少。SQLite源代码总共不到3万行,运行时所占内存不超过250KB,而且不需要安装部署,并支持多线程访问。
(3)SQLite中所有的数据库信息如表,索引等全部集中存放在一个文件中,SQLite支持事务,在开始一个新事务时会将整个数据库文件枷锁。
(4)支持Windows,Linux等主流操作系统,可以采用多种语言进行操作,如Java,PHP等
总之,SQLite的这些特性都非常适合作为移动设备的数据存储。Android平台提供了对创建和使用SQLite数据库的支持,其装载了sqlite3组件,任何一个SQLite数据库对于创建该数据库的应用程序来说都是私有的,SQLite的数据库文件位于/data/data/package-name/databases目录下。
创建数据库对象
要想对数据进行操作,首先要获得SQLiteDatabase对象,SQLiteDatabase类提供了一些静态方法用于创建或打开一个数据库。这些方法及说明如面:
SQLiteDatabase类提供的创建或打开数据库的方法
方法 说明
openDatabase(String path,SQLiteDatabase.CusorFactory 以指定的模式打开指定路径名的数据库文件,使用指定的Cursor
factory ,int flags) 对象用于在查询中返回,如果传入null则使用默认
openOrCreateDatabase(File file,SQLiteDatabase.CursorFactory 相当于openDatabase(file.getPath(),factory,CREATE_IF
factory) _NECESSARY)
openOrCreateDatabase(String path,SQLiteDatabase. 相当于openDatabase(path,factory,CREATE_IF_NECESSA
CusorFactory factory) RY)
其中openDatabase方法中可以通过参数flags指定打开的模式,不同的模式极其说明如下表:
openDatabase可指定的打开模式极其说明
模式名称 说明
OPEN_READONLY 打开数据库的方式为只读
OPEN_READWRITE 打开数据库的方式为可读/可写
CREATE_IF_NECESSARY 如果指定的数据库文件不存在,就创建新的数据库
NO_LOCALIZED_COLLAORS 打开数据库时,不对数据进行基于本地化语言的排序
除了基于文件系统的数据库外,在Android中还可以创建内存数据库。调用SQLiteDatabase的静态方法create(SQLiteDatabase.CursorFactory factroy)创建内存数据库,如果对数据库操作的要求高,可以考虑采用内存数据库来实现。
除了上面几种创建或打开数据库的方式外,在实际开发中,为方便起见,还可以开发一个数据库辅助类来创建或打开数据库,这个辅助类继承自SQLiteOpenHelper类,在该类的构造器中,调用Context方法创建并打开一个指定名称的数据库对象。继承和扩展SQLiteOpenHelper类主要做的工作就是重写以下两个方法。
onCreate(SQLiteDatabase db):当数据库被首次创建时执行该方法,一般将创建表等初始化操作放在该方法中执行。
onUpgrade(SQLiteDatabase dv,int oldVersion,int newVersion):当打开数据库时传入的版本号与当前版本号不同时会调用该方法。
SQLiteOpenHelper类的基本用法是:当需要创建或打开一个数据库并获得数据库对象时,首先根据指定的文件名创建一个辅助对象,然后调用该对象的getWritableDatabase或getReadableDatabase方法获得SQLiteDatabase对象。(调用getReadableDatabase方法返回的并不总是只读的数据库对象,一般来说该方法和getWritableDatabase方法的返回情况相同,只有在数据库仅开放只读权限或磁盘已满时才会返回一个只读的数据库对象)
操作数据
对数据库的操作一般来将就是增,删,改,查,SQLiteDatabase类提供了很多方法用来对数据库进行操作,其中既含有直接接收SQL语句作为执行参数的方法,也含有专门用于增删改查的方法。常用的数据库操作方法如下:
常用数据库操作方法及参数说明
方法 方法说明 参数说明
(1)void execSQL(String sql) 执行指定的SQL语句,不可以为查询 sql:要执行的SQL语句;
(2)void execSQL(String sql, 语句,不可以传入以“;”隔开的多条 bindArgs:将来替换SQL表达式中“?”的参数列表,目
Object[] bindArgs) SQL语句 前只支持byte数组,String,long和double类型
(1)long insert(String table, 像表中插入一行数据,返回插入行的id table:要操作的表名;
String nullColumnHack, nullColumnHack:如果传入的values参数为null或空
ContentValues values) 时,该参数所指定的列将会被赋值为NULL插入到表中
(2)long insertOrThrow(String 方法insertOrThrow会抛出SQLException values:要插入的数据以“列名--列值”的键值对方式
table,String nullColumnHack, 异常 封装到ContentValues对象中
ContentValues values)
int update(String table, table:更操作的表名;
ContentValues values, 像表中更新数据,返回更新的行的个数 values:更新后的“列名--列值”的键值对whereCla
String whereClause, use:指明更新位置的where子句,如果为null则更新表
String[] whereArgs) 中所有行;
whereArgs:将用来替换whereClause中“?”参数
int delete(String table,String table:要操作的表名:
whereClause,String[] 删除表中满足指定条件的行,返回删除 whereClause:指明删除位置的where子句,如果传入
whereArgs) 的个数 “1”则会删除表中所有的行;
whereArgs:将来用于替换whereClause中的“?”参数列表
检索数据
对数据的检索涉及的内容稍微多一些,所有在此单独介绍。SQLiteDatabase中提供了直接解析SQL语句的查询方法和专门用于查询的方法,这些方法及说明如下:
用于查询数据库的方法及说明
方法 方法说明 参数说明
(1)Cusor rawQuery(String sql, sql:要执行的SQL查询语句;
String[] args) 执行指定的SQL查询语句,返回 args:用于替换SQL查询语句中“?”的参数列表
(2)Cursor rawQueryWithFactory( Cursor对象作为查询结果 factory:构造Cursor对象时锁使用的CursorFacto
SQLiteDatabase.CursorFactory factory, ry对象,为null则使用默认;
String sql,String[] args,String editable) editTable:首个可编的表的名称
(1)Cursor query(boolean distinct, distinct:是否要求每一行具有唯一性,true表示要
String[] columns,String selection, 求; table:要操作的表名;
String[] selectionArgs,String groupBy, columns:指明查询结果中需要返回的列,如果传入
String having,String orderBy,String limit) null则返回所有列;
(2)Cursor query(String table,String[] 查询指定的表 selection:对应SQL语句中where子句,指明查询
columns,String selection,String[] selectionArgs, 返回Cursor 结果需要返回的行数,传入null返回所有行;
String groupBy,String having,String orderBy) 对象作为查询 selectionArgs:用于替换selection参数中的“?
(3)Cursor query(Stirng table,String[] columns, 结果 ”的参数列表
String selection,String[] selectionArgs,String groupBy, groupBy:对应SQL语句中的group by 子句,对
String having,String orderBy,String limit) 查询结果分组,传入null表示不分类
(4)Cursor query WithFactory(SQLiteDatabese.CurFactory having:对应SQL语句中的having子句,需要与
cursorFactory,boolean distinct,String table,String[] columns, groupBy参数配合使用,传入null表示不对分组
String selection, String[] selectionArgs,String groupBy, 结果附加条件
String having,String orderBy,String limit) orderBy:对应SQL语句中的order by语句,传入
null为默认排序
limit:对应SQL语句中的limit语句,对查询返回的
的行数进行限制,传入null表示不作限制
(上面列出一系列的query方法其实是将SQL语句进行分解,让整个SQL语句的每个部分和子句都独立出来作为供调用者选择的参数,需要注意如果这些参数对应SQL中的某个子句,在传入参数的时候不应该包含这些子句的关键字,如selection参数中不应该包含“where关键字”)
先修改布局文件添加一个TextView控件,修改后的main.xml文件源码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:id="@+id/textView"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
创建数据库的辅助类SQLiteHelper。继承自SQLiteOpenHelper类,并重写其onCreate等方法,代码如下:
package karant.zhan; //声明包语句
import android.content.Context; //引入相关类
import android.database.sqlite.SQLiteDatabase; //引入相关类
import android.database.sqlite.SQLiteOpenHelper; //引入相关类
import android.database.sqlite.SQLiteDatabase.CursorFactory; //引入相关类
//继承自SQLiteOpenHelper的子类
public class SQLiteHelper extends SQLiteOpenHelper{
public SQLiteHelper(Context context, String name, CursorFactory factory,
int version) {
super(context, name, factory, version); //调用父类的构造器
}
@Override
public void onCreate(SQLiteDatabase db) { //重写onCreate方法
db.execSQL("create table if not exists hero_info(" //调用execSQL方法创建表
+ "id integer primary key,"
+ "name varchar,"
+ "level integer)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Activity类代码如下:
package karant.zhan; //声明包语句
import android.app.Activity; //引入相关类
import android.content.ContentValues; //引入相关类
import android.database.Cursor; //引入相关类
import android.database.sqlite.SQLiteDatabase; //引入相关类
import android.os.Bundle; //引入相关类
import android.widget.TextView; //引入相关类
//继承自Activity的子类
public class SQLite extends Activity {
SQLiteHelper myHelper; //数据库辅助类对象的引用
TextView tv; //TextView对象的引用
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);//设置显示的屏幕
tv = (TextView)findViewById(R.id.textView); //获得TextView对象的引用
myHelper = new SQLiteHelper(this, "my.db", null, 1); //创建MySQLiteOpenHelper辅助类对象
insertAndUpdateData(myHelper); //向数据库中插入和更新数据
String result = queryData(myHelper); //向数据库中查询数据
tv.setText("名字/t等级/n"+result); //将查询到的数据显示到屏幕上
}
//方法:向数据库中的表中插入和更新数据
public void insertAndUpdateData(SQLiteHelper myHelper){
SQLiteDatabase db = myHelper.getWritableDatabase(); //获取数据库对象
//使用execSQL方法向表中插入数据
db.execSQL("insert into hero_info(name,level) values('Hero1',1)");
//使用insert方法向表中插入数据
ContentValues values = new ContentValues(); //创建ContentValues对象存储“列名-列值”映射
values.put("name", "hero2");
values.put("level", 2);
db.insert("hero_info", "id", values); //调用方法插入数据
//使用update方法更新表中的数据
values.clear(); //清空ContentValues对象
values.put("name", "hero2");
values.put("level", 3);
db.update("hero_info", values, "level = 2", null); //更新表中level为2的那行数据
db.close(); //关闭SQLiteDatabase对象
}
//方法:从数据库中查询数据
public String queryData(SQLiteHelper myHelper){
String result="";
SQLiteDatabase db = myHelper.getReadableDatabase(); //获得数据库对象
Cursor cursor = db.query("hero_info", null, null, null, null, null, "id asc"); //查询表中数据
int nameIndex = cursor.getColumnIndex("name"); //获取name列的索引
int levelIndex = cursor.getColumnIndex("level"); //获取level列的索引
for(cursor.moveToFirst();!(cursor.isAfterLast());cursor.moveToNext()){ //遍历查询结果集,将数据提取出来
result = result + cursor.getString(nameIndex)+" ";
result = result + cursor.getInt(levelIndex)+" /n";
}
cursor.close(); //关闭结果集
db.close(); //关闭数据库对象
return result;
}
@Override
protected void onDestroy() {
SQLiteDatabase db = myHelper.getWritableDatabase(); //获取数据库对象
db.delete("hero_info", "1", null); //删除hero_info表中的所有数据
super.onDestroy();
}
}