数据持久化技术:保存在内存中的数据是处于瞬时状态的(可能因为内存回收而丢失),而保存在存储设备中的数据是处于持久化状态的,持久化技术则是提供了一种机制可以让数据在瞬时状态和持久化状态之间进行转化。
Android系统中主要提供了三种数据方式实现数据的持久化:
1.文件存储
2.SharedPreference
3.数据库存储
当然,除了这三种稍微较安全的方式,还可以将数据保存到SD卡、ContentProvider存储(区别于其他的可以在其它进程中访问)、网络存储。
文件存储
文件存储不对存储的内容进行任何的格式化处理,所有数据都是原封不动地保存到文件 当中的,比较适合用于一些简单的文本数据或二进制数据
1 将数据保存到文件中:
Context类中提供了一个openFileOutput()方法,可以将数据存储到指定的文件中,该方法接收两个参数:第一个参数,文件名,注意这里指定的文件名不可以包含路径,因为所有的文件都是默认存储到/data/data/<package name>/files/目录下的;第二个参数,文件的操作模式,有以下两种模式:
1)MODE_PRIVATE:默认,覆盖
2)MODE_APPEND:追加,不存在就创建新的文件
实现示例:
Out = openFileOutput( “data”, Context.MODE_PRIVATE ); //返回一个FileOutputStream对象
//使用Java流的方式将数据写入文件中
writer = new BufferedWriter( new OutputStreamWriter(out) );
writer.write(“这是要保存的数据”);
这里通过openFileOutput得到一个FileOutputStream对象,然后借助它构建出一个OutputStreamWriter对象,接着再使用OutputStreamWriter对象构建出一个BufferedWriter对象,这样就可以将数据写入到文件中了。注意最后要将流关闭:writer.close();
2 从文件中读取数据
Context类中提供了一个openFileInput()方法,用于从文件中读取数据
它只接收一个参数:文件名(系统会自动到/data/data/<package name>/files/目录下去加载这个文件,返回一个FileInputStream对象)
实现示例:
//StringBuilder content = new StringBuilder();
FileInputStream in = openFileInput(“data”) ;
BufferedReader reader = new BufferedReader( new InputStreamReader(in) );
String line=””;
while( ( line=reader.readLine() )!=null ){
Content.append(line);
}
这里通过openFileInput得到一个FileInputStream对象,然后借助它构建出一个InputStreamReader对象,接着再使用InputStreamReader对象构建出一个BufferedReader对象进行一行行地读取,这样就可以将数据从文件中读取出来了。注意最后要将流关闭:reader.close();
SharedPreferences存储数据
要想使用SharedPreferences存储数据要先获取SharedPreferences对象
获取SharedPreferences对象的三种方法:
1.Context类中的getSharedPreferences():
参数1:指定文件的名称(不存在就新建一个,存放在data/data/<package nane>/shared_perfs目录下)
参数2:操作模式,MODE_PRIVATE (只有当前的应用程序才可以对这个SharedPreferences文件进行读写),MODE_MULTI_PROCESS (一般用于多个进程对同一个SharedPreference文件进行读写的情况)
2.Activity类中的getPreferences():
参数:只要操作模式,该方法自动将当前活动的类名作为SharedPreference文件名
3.PreferenceManager类中的getDefaultSharedPreferences():
接收一个Context参数,自动使用当前程序的包名为前缀来命名SharedPreference文件
获取到SharedPreferences后,就可以开始向SharedPreferences文件存储数据了:
1.调用SharedPreferences对象的edit()来获取SharedPreferences.Editor对象
2.向SharedPreferences.Editor对象添加数据
3.调用commit()将添加的数据提交
实现示例:
SharedPreferences.Editor editor = getSharedPreferences(“data”,MODE_PRIVATE).edit();
editor.putString(“name”,”JLChen”);
editor.putInt(“age”,23);
Editor.commit();
从SharedPreferences读取数据:
SharedPreferences对象提供了一系列非get方法,每种get方法对应于SharedPreferences的put方法
实现示例:
SharedPreferences pref = getSharedPreferences(“data”,MODE_PRIVATE);
//第二个参数是默认值
Int age = pref.getInt(“age”,0);
String name = pref.getString(“name”,””);
在使用SharedPreferences存储数据的时候需要使用编辑器Editor,而从SharedPreferences文件中取出数据的时候,直接用SharedPreferences对象来get数据。
SQLite数据库存储
SQLite数据库不仅支持标准的SQL语法,还遵循了数据库的ACID事物。
借助SQLiteOpenHelper可以方便的对数据库进行创建和升级。因为SQLiteOpenHelper是一个抽象类,所以要创建一个类去继承它,然后利用SQLiteOpenHelper中的两个抽象方法onCreate()和onUpgrade()就可以实现数据库的创建、升级数据库的逻辑。
创建SQlite数据库的步骤:
1.新建一个帮助类,继承SQliteOpenHelper()方法
2.重写onCreate()和onUpgrade()方法
3.重写构造方法SQLiteOpenHelper(Context,<数据库名>,null,<数据库版本号>),完成构建SQLiteOpenHelper实例
4.调用getReadableDataBase() 和getWriteableDataBase()方法 (数据库不存在时就创建,数据库文件会保存在/data/data/<package name>/databases/目录下,此时,onCreate方法也会得到执行,所以一般在onCreate()里面添加逻辑处理)
注意:SQLite数据库不像其他的数据库拥有繁杂的数据类型,它的数据类型很简单:
Integer表示整型 real表示浮点型 text表示文本类型 blob表示二进制类型
示例:create table A(
Id integer primary key autoincrement,
name text,
page text,
price real );
在onCreate方法里调用execSQL()传入该sql语句就可以了,等到调用getWriteableDataBase方法时就会执行onCreate方法创建好该表了。
SQLite升级数据库:
问题:当数据库中有一张表A存在的时候,你想再在原来的基础上新增加一张表B的话,如果你直接在onCreate方法里面(执行表A的Sql语句下面)添加表B的Sql语句:
sQliteDB.execSQL(CREATE_A); //CREATE_A是表A的sql语句
sQliteDB.execSQL(CREATE_B);
你会发现,表B没有创建完成!这是因为此时数据库已经存在(只有表A),数据库不会再创建
那怎么办呢?解决方法有两个:
1.卸载程序(或打开目录删除数据库文件XXX.db和XXX.db-journal)再运行
2.利用onUpgrade()方法
1)在数据库MySQLiteOpenHelper帮助类onCreate()里面添加建立表B的sql处理
sQliteDB.execSQL(CREATE_A); //CREATE_A是表A的sql语句
sQliteDB.execSQL(CREATE_B);
2)在onUpgrade()里判断表是否存在
db.execSQl(“drop table if exists A”); //删除表再创建,数据会丢失,太粗暴!
db.execSQl(“drop table if exists B”);
onCreate(db);//调用onCreate方法升级数据库
判断的另一种方式(推荐):原表数据不会丢失
Switch(oldVersion){
case 1: //根据当前版本号建表,这样版本号为2的时候就有AB两张表了
sQliteDB.execSQL(CREATE_B);
default:
}
//建立B表后假如又有需求要改A表
Switch(oldVersion){
case 1: //根据当前版本号建表,这样版本号为2的时候就有AB两张表了
sQliteDB.execSQL(CREATE_B);
case 2:
//告诉系统修改表了
//注意,这里还要修改原来建立A表的sql语句:在后面添加字符串“+address text”,这是为了保证用户直接安装最新版本而不是升级的情况
db.execSQl(“alter table A add column address text”);
}
注意,每一个case后面都没有Break,是为了保证在跨版本升级的时候,每一次数据库的修改都能够被全部执行完。假如版本号从2到3,则case2会执行(onUpgrade执行完后版本号才是3),如果版本号从1升级到3,则case1和case2都会执行
3)在Activity的onCreate调用构造方法实例化数据库MySQLiteOpenHelper帮助类对象的时候,传入的数据库版本号+1(只要传入比原来的版本号大的正整数就可以)
数据库的增删查改
SQLiteOpenHelper的getReadableDatabase()或getWriteableDatabase()方法可以创建和升级数据库,不仅如此,这两个方法还会返回一个SQLiteDataBase对象,借助这一个对象就可以实现不去编写sql语句就可以对数据库的增删查改操作(当然也可以使用SQL语句的方式)
SQLiteDataBase中提供了insert(),delete(),query(),update()方法:
增:db.insert(“A”,null,contentValues) //参数:表名,null,ContentValues对象
ContentValues的用法:
ContentValues contentvalues = new ContentValues();
contentValues.put(“name”,”第一行代码”);
注意调用db.insert(“A”,null,contentValues)要记住调用contentValues.clear();
删:db.delete(“A”,”page>?”,new String[]{“500”}); //参数:表名,约束某一行或几行的数据
查:db.query(“A”,null,null,null,null,null,null) //参数,表名,条件,会返回一个Cursor对象
可以通过调用Cursor的get方法来获取某一列的数据:
cursor.getString( cursor.getColumnIndex(“name”) ); //获取符合条件的某一条数据name这一列的值
cursor.getDouble( cursor.getColumnIndex(“price”) );
Cursor的遍历:
If( cursor.moveToFrist() ){
do{
... ...
}while( cursor.moveToNext() );
}
改:db.update(“A”,values,”name=?”,new String[]{ “JZZheng”});
//参数:表名,新值,约束条件
示例:
contentValues.put(“price”,10.99);
db.update(“A”,values,”name=?”,new String[]{ “JZZheng”});
//将name的值为JZZheng的price值改为10.99
SQL方式实现数据库增加示例:
db.execSQL( “insert into A(id,name,page,price)values(?,?,?,?)” ,
new String[]{ 10,”JLChen”,312,1993});
其他三种方式类似。
使用事务:
1.在调用SQLiteDataBase的insert(),delete(),query(),update()方法之前
db.beginTransaction();//开启事务
2.在调用SQLiteDataBase的insert(),delete(),query(),update()方法之后
db.setTransactionSuccessful();//事务执行成功
3.最后(一般在try-catch中的finally里面)
db.endTransaction()//结束事务
ContentProvider存储
ContentProvider主要在不同的应用程序之间实现数据共享的功能,不同于文件存储和SharedPreferences中的两种全局可读写操作模式,内容提供者可以选择只对那一部分数据进行共享,从而保证程序中隐私数据不会有泄漏的风险。
要使用ContentProvider就需要借助ContentResolve类,可以通过Context中的getContentResolver()方法来获取到该类的实例。ContentResolve提供了一系列方法对数据进行CRUD操作。
不同于SQLiteDataBase,ContentResolve中的增删查改方法都是不接受表名参数的,而是使用一个Uri参数代替。Uri主要由三部分组成:协议、权限(authority)、路径(path),内容Uri最标准的格式写法如下:
content://com.example.app.privider/table(/1)
(加上/1:表示table表中id为1的数据,不加上表示所有数据)
得到Uri字符串之后,就可以将它解析成一个Uri对象:
Uri uri = Uri.prase(“content://com.example.app.privider/table”);
接下来操作ContentProvider就跟SQliteDataBase类似了:
增:ContentValues values = new ContentValues();
Values.put(“column1”,”text”);
Values.put(“column2”,1);
getContentResolver().insert(uri,values);
删:getContentResolver.delete(uri,”column2 = ?”,new String[]{“1”});
查:Cursor cursor = getContentResolver().query(
uri,projection,selectionArgs,sortOrder
);
query()方法参数 | 对应SQL部分 | 描述 |
uri | from table_name | 指定查询表 |
projection | Select column1 | 查询列名 |
selection | Where column = value | Where约束 |
selectionArgs | - | 为where占位符提供value |
orderBy | order by column1 | 查询结果排序方式 |
查询完成返回一个Cursor对象,通过移动游标的位置来遍历Cursor的所有行,然后取出每一行中相应列的数据,代码如下:
If(cursor!=null){
while(cursor.moveToNext()){
String column1 = cursor.getString(Cursor.getColumnIndex(“column1”));
... ...
}
Cursor.close();
}
改:values.put(“column1”,””);//将column1的值清空
getContentResolver().update(uri,values,”column1=?”,new String[]{“text”});
内容URI格式:
内容URI的格式其实有两种,以路径结尾的就表示访问表中所有数据,以id结尾的就表示期望访问该表中拥有相应id的数据。我们使用通配符来分别匹配这两种格式URI:
1. *:表示匹配任意长度的任意字符
2. #:表示匹配任意长度的数字
对应为:content://com.example.app.provider/*
contetn://com.example.app.provider/table/#
创建内容提供者:
1.新建一个类,继承ContentProvider
2.重写onCreate(),query(),insert(),update(),delete(),getType()
注意,onCreate()只有在ContentResolver尝试访问我们的内容提供者暴露的数据的时候才会被初始化,完成数据库的创建和升级等操作。
3.先自定义好匹配规则:利用UriMatcher类提供的addURI()方法
static{
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//权限,路径,匹配返回值
uriMatcher.addURI(“com.example.app.provider”,”table”,1); //所有数据
uriMatcher.addURI(“com.example.app.provider”,”table/#”,1);//单条数据
}
4.在各方法里添加相应的各种返回值操作(根据UriMatcher.match(uri)的返回值来确定具体操作,(调用SQLiteOpenHelper)操作数据库)
5.清单声明:
<privider android:name=”...” //内容提供者名字
android:authorities=”...”> //必须指定是哪一个自定义Privider
</privider>