Ch5 数据存储
5.1 数据存储方式
Android平台提供的数据存储方式:
- 文件存储
读取方式与java中I/O程序是完全一样的 - SharedPrefeences
用来存储一些简单的配置信息的一种机制,它采用了XML格式将数据存储到设备中。通常存储一些应用程序的各种配置信息。 - SQLite数据库
SQLite是Android自带的一个轻量级的数据库,支持基本SQL语法,一般使用它作为复杂数据的存储引擎。 - ContentProvider
Android四大组件之一,用于应用程序之间的数据交换,可以将自己的数据共享给其他应用程序使用。 - 网络储存
需要与Android网络数据包打交道,通过网络提供的存储空间来存储/获取数据信息。
5.2 文件存储
文件存储是Android中最基本的一种数据存储方式。
5.2.1 将数据存入文件中
- 内部存储
将应用程序的数据以文件的形式存储到应用中(默认位于data/data/目录下)。
存储的文件会被其所在的应用程序私有化,其他应用程序想要操作本应用程序中的文件,则需要设置权限。
创建的应用程序被卸载时,其内部存储文件也随之被删除。
内部存储使用的是Context提供的openFileOutput()方法和openFileInput()方法,返回进行读写操作的FileOutputStream对象和FileInputStream对象。
FileOutputStream fos = openFileOutput(String name, int mode);
FileInputStream fis = openFileInput(String name);
- name表示文件名
- mode表示文件的操作模式
- MODE_PRIVATE:该文件只能被当前程序读写
- MODE_APPEND:该文件的内容可以增加
- MODE_WORLD_READABLE:该文件的内容可以被其他程序读
- MODE_WORLD_WRITEABLE:该文件的内容可以被其他程序写
Android默认情况下任何应用创建的文件都是私有的,其他程序无法访问。
如果希望文件能够被其他程序进行读写操作则需要同时指定该文件的MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE权限。
private void WriteInsideFile(){
//打印表示该函数开始执行
Toast.makeText(this,"WriteInsideFile",Toast.LENGTH_SHORT).show();
String fileName="data.txt";
String content="HelloWorld";
FileOutputStream fos=null;
try{
fos=openFileOutput(fileName,MODE_PRIVATE);
fos.write(content.getBytes());
//打印写入文件路径
Toast.makeText(this,fos.getFD().toString(),Toast.LENGTH_SHORT).show();
}catch(Exception e){
e.printStackTrace();
}finally {
try {
if (fos != null) {
fos.close();
//打印表示fos.close()已执行
Toast.makeText(this,"fos.close()",Toast.LENGTH_SHORT).show();
}
}catch(IOException e){
e.printStackTrace();
}
}
}
- 外部存储
将数据以文件的形式存储到一些外部设备上,属于永久性的存储方式(文件通常位于mnt/sdcard目录下)。
外部存储的文件可以被其他应用程序所共享,当外部存储设备连接到计算机时,这些文件可以被浏览、修改和删除,因此使用外部设备之前必须使用Environment.getExternalStorageState()方法确认外部设备是否可用。
向外部设备存储数据示例代码
private void WriteExtFile(){
//打印表示该函数开始执行
Toast.makeText(this,"WriteExtFile",Toast.LENGTH_SHORT).show();
String state = Environment.getExternalStorageState();//获取设备状态
if(state.equals(Environment.MEDIA_MOUNTED)){//判断设备是否可用
//File SDPath = Environment.getExternalStorageDirectory();//获得sd卡根目录,该方法在安卓29后被废弃,且实践可得该方法无法被成功执行(设备版本android29)
File SDPath = getExternalFilesDir(null);//获得sd卡目录
Toast.makeText(this,SDPath.getAbsolutePath(),Toast.LENGTH_SHORT).show();//显示设备状态
File file = new File(SDPath, "data.txt");
String data ="HelloWorld";
FileOutputStream fos=null;
try{
fos = new FileOutputStream(file);
fos.write(data.getBytes());
//打印写入文件绝对路径
Toast.makeText(this,file.getAbsolutePath(),Toast.LENGTH_SHORT).show();
} catch(Exception e){
e.printStackTrace();
}finally {
try{
if(fos!=null){
fos.close();
//打印表示fos.close()已执行
Toast.makeText(this,"fos.close()",Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//显示设备状态
Toast.makeText(this,state,Toast.LENGTH_SHORT).show();
}
5.2.2 从文件中读取数据
- 读取内部存储中的文件数据
private void ReadInsideFile() {
//打印表示该函数开始执行
Toast.makeText(this,"ReadInsideFile()",Toast.LENGTH_SHORT).show();
String content = "";
FileInputStream fis = null;
try {
fis = openFileInput("data.txt");
byte[] buffer = new byte[fis.available()];
fis.read(buffer);
content = new String(buffer);
Toast.makeText(this,fis.getFD().toString(),Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
//打印表示fos.close()已执行
Toast.makeText(this,"fos.close()",Toast.LENGTH_SHORT).show();
}
} catch (IOException e) {
e.printStackTrace();
}
}
//显示文件内容
Toast.makeText(this,content,Toast.LENGTH_SHORT).show();
}
- 读取外部存储中的文件数据
private void ReadExtFile(){
//表示该函数开始执行
Toast.makeText(this,"ReadExtFile()",Toast.LENGTH_SHORT).show();
String state = Environment.getExternalStorageState();
//打印SDCard挂载情况
Toast.makeText(this,state,Toast.LENGTH_SHORT).show();
if(state.equals(Environment.MEDIA_MOUNTED)){
//File SDPath = Environment.getExternalStorageDirectory();//废弃
File SDPath = getExternalFilesDir(null);//获得sd卡目录
File file = new File(SDPath, "data.txt");
FileInputStream fis=null;
BufferedReader br =null;
try{
fis= new FileInputStream(file);
br =new BufferedReader(new InputStreamReader(fis));
String data=br.readLine();
//打印读取内容
Toast.makeText(this,data,Toast.LENGTH_SHORT).show();
}catch(Exception e){
e.printStackTrace();
}finally {
if(br!=null){
try{
br.close();
//打印表示br.close()已执行
Toast.makeText(this,"br.close()",Toast.LENGTH_SHORT).show();
}catch(IOException e){
e.printStackTrace();
}
}
if(fis!=null) {
try {
fis.close();
//打印表示fis.close()已执行
Toast.makeText(this, "fis.close()", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
- 动态申请权限
Android SDK6.0及以上版本改变了权限的管理方式- 正常权限
不会直接给用户隐私权带来风险的权限 - 危险权限
申请了该权限的应用可能涉及了用户隐私信息的数据或资源,也可能对用户存储的数据或其它应用的操作带来影响- 位置(LOCATION)
- 日历(CALENDAR)
- 照相机(CAMERA)
- 联系人(CONTACTS)
- 存储卡(STORAGE)
- 传感器(SENSORS)
- 麦克风(MICROPHONE)
- 电话(PHONE)
- 短信(SMS)
危险权限不仅需要在清单文件的节点中添加权限,还需要在代码中动态申请权限。
- 正常权限
申请SD卡写文件的权限
1.静态申请权限
静态申请权限方式适用于Android SDK6.0 以下版本
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{"android.permission.WRITE_EXTERNAL_STORAGE"},
1);
该方法会在界面上弹出是否允许请求权限的对话框,当用户点击对话框的"ALLOW"按钮时,程序会执行动态申请权限的回调方法onRequestRermissionsResult(),在该方法中可以获取用户授予申请的权限的结果。
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,int[] grantResults){
super.onRequestPermissionsResult(requestCode,permissions,grantResults);
if(requestCode==1){
for(int i=0;i<permissions.length;i++){
if(permissions[i].equals("android.permission.WRITE_EXTERNAL_STORAGE")&&
grantResults[i]== PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "" + "权限" + permissions[i] + "申请成功", Toast.LENGTH_SHORT).show();
}
else{
Toast.makeText(this,""+"权限"+permissions[i]+"申请失败",Toast.LENGTH_SHORT).show();
}
}
}
}
※似乎权限申请代码需要写在onStart()方法中
5.3 SharedPreferences存储
5.3.1 将数据存入SharedPreferences中
首先需要调用getSharedPreferences(String name,int mode)方法获取实例对象。
参数
name:命名
mode:模式,包括
MODE_PRIVATE(只能被自己的应用程序访问)
MODE_WORLD_READABLE(除了自己访问外还可以被其它应该程序读取)
MODE_WORLD_WRITEABLE(除了自己访问外还可以被其它应该程序读取和写入)
若该Activity只需要创建一个SharedPreferences对象的时候,可以使用getPreferences方法,不需要为SharedPreferences对象命名,只要传入参数mode即可
该对象本身只能获取数据,需要调用SharedPreferences类的edit()方法获取可编辑的Editor对象,最后通过该对象putXxx()方法存储数据。
SharedPreferences sp= getSharedPreferences("data",MODE_PRIVATE);
SharedPreferences.Editor editor =sp.edit();
editor.putString("name","姓名");
editor.putInt("age",8);
editor.commit();
Editor对象以key/value形式将文件保存在data/data//shared_prefs文件夹下XML文件中,value只能是float、int、long、boolean、String、Set类型数据
操作数据完成后,一定要调用commit()方法进行数据提交,否则所有操作不生效。
5.3.2 读取与删除SharedPreferences中的数据
- 读取SharedPreferences中的数据
需要获取SharedPreferences对象,通过该对象的getXXX()方法根据相应key值获取到value的值。
SharedPreferences sp = getSharedPreferences("data",MODE_PRIVATE);
String data = sp.getString("name","");
//getXXX()方法的第二个参数为缺省值,如果sp中不存在该key,则返回缺省值。
- 删除SharedPreferences
需要调用Editor对象的remove(String key)方法或者clear()方法。
editor.remove("name");//删除一条数据
editor.clear();//删除全部数据
- 获取数据的key值与存入数据的key值数据类型要一致,否则查找不到数据
- 保存SharedPreferences的key值时建议使用静态变量保存,如private static final String key=“name”;
5.4 SQLite数据库存储
5.4.1 SQLite数据库的创建
创建一个类继承SQLiteOpenHelper类,重写onCreat()方法和onUpgrade()方法。
public class UserSQLHelper extends SQLiteOpenHelper {
public UserSQLHelper(@Nullable Context context) {
super(context,m_dbName+".db",null,1);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(
"CREATE TABLE "+m_dbName+"(" +
"_id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"account VARCHAR(20)," +
"password VARCHAR(20)," +
"superpower BOOLEAN)"
);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
构造方法参数:上下文对象、数据库名称、游标工厂(通常是null)、数据库版本。
onCreat()方法是在数据库第一次创建时调用,该方法通常用于初始化表结构。
onUpgrade()方法在数据库版本号增加时调用,如果版本号不增加,则该方法不调用。
5.4.2 SQLite数据库的基本操作
- 新增数据
public void insert(String name,
String sex,
String phone,
String number,
String state) {
SQLiteDatabase db = m_helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", name);
values.put("sex", sex);
values.put("phone", phone);
values.put("number", number);
values.put("state", state);
long id = db.insert(m_dbName, null, values);
db.close();
}
insert()第二个参数表示如果发现将要插入的行为空行时,会将这个列名的值设为null。
使用完SQLiteDatabase对象后一定要调用close()方法关闭数据库连接,否则数据库连接会一直存在,不断消耗内存。
- 删除数据
//queryvalue为查询匹配值,selection为数据库查询条件语句
public int delete(String deletevalue, String whereClause) {
SQLiteDatabase db = m_helper.getWritableDatabase();
int number = db.delete(m_dbName, whereClause, new String[]{deletevalue});
db.close();
return number;
}
- 修改数据
//queryvalue为查询匹配值,selection为数据库查询条件语句
public int update(String queryvalue, String selection,
String name,
String sex,
String phone,
String number,
String state) {
SQLiteDatabase db = m_helper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", name);
values.put("sex", sex);
values.put("phone", phone);
values.put("number", number);
values.put("state", state);
int Number = db.update(m_dbName, values, selection, new String[]{queryvalue});
db.close();
return Number;
}
update()包含参数:
-
数据库表的名称
-
最新的数据
-
要修改的数据的查找条件
-
查找条件的参数。
-
查询数据
该方法返回一个行数集合Cursor,是一个游标接口,提供了遍历查询结果的方法。使用完Cursor对象后一定要及时关闭,否则会造成内存泄露。
//queryvalue为查询匹配值,selection为数据库查询条件语句
public String[][] query(String queryvalue, String selection) {
SQLiteDatabase db = m_helper.getReadableDatabase();
Cursor cursor = db.query(m_dbName, null, selection, new String[]{queryvalue}, null, null, null);
if (cursor.getCount() == 0) {
//按理说cursor已经获得数据,可以提前关掉db,但事实表明不能在if之前调用db.close(),只能在if else中各调用一次db.close()
db.close();
cursor.close();
return null;
} else {
String[][] result = new String[cursor.getCount()][6];
for (int i = 0; cursor.moveToNext(); i++) {
result[i][0] = cursor.getString(cursor.getColumnIndex("_id"));
result[i][1] = cursor.getString(cursor.getColumnIndex("name"));
result[i][2] = cursor.getString(cursor.getColumnIndex("sex"));
result[i][3] = cursor.getString(cursor.getColumnIndex("phone"));
result[i][4] = cursor.getString(cursor.getColumnIndex("number"));
result[i][5] = cursor.getString(cursor.getColumnIndex("state"));
}
db.close();
cursor.close();
return result;
}
}
query()方法包含参数:
- 表名称
- 查询列名
- 接受查询条件的子句
- 接收查询子句对应的条件值
- 分组方式
- having条件
- 排序方式
使用SQL语句进行数据库操作
//增加一条数据
db.execSQL("insert into information(name,phone)values(?,?)",
new Objext[]{name,phone});
//删除一条数据
db.execSQL("deletefrom information where _id = 1");
//修改一条数据
db.execSQL("update information set name=? where phone =?",
new Object[]{name,price});
//执行查询的SQL语句
Cursor cursr =db.rawQuery("select * from information where name=?",
new String[]{name});
5.4.3 SQLite数据库中的事务
数据库事务是一个对数据库执行工作单元,是针对数据库的一组操作,可以由一条或多条SQL语句组成,必须满足数据库事务正确执行基本要素(ACID)。
PersonSQLiteOpenHelper helper = e ersonSQLiteOpenHelper(getApplication());
//获取可读写SQLiteDataBase对象
SQLiteDataBase db = helper.getWitableDatabase();
//开始事务
db.begnTransaction();
try{
//数据库操作1
//数据库操作2
//数据库操作3
db.setTransctionSuccessful();
}catch(Excepton e){
Log.i("事务处理失败"),e.toString());
}finlly{
db.endTransaction();
db.close();
}