数据存储

Android 提供多种应用数据保存选项。您所选择的解决方案取决于您的特定需求,例如数据需要多少存储空间、需储存哪种类型的数据,以及数据应该是应用的私有数据,还是可供其他应用和用户访问的数据。

数据存储方式

image-20200512120105399

文件存储

image-20200512120148822

内部存储

输出流:

方法openFileOutput()

说明:打开与此Context的应用程序包关联的私有文件进行写入。创建文件(如果尚不存在)。调用应用程序不需要任何其他权限即可读取或写入返回的文件。

public abstract FileOutputStream openFileOutput(String name, @FileMode int mode)

参数:

name:要打开的文件名;不能包含路径分隔符。

mode:操作模式。

  • MODE_PRIVATE:该文件只能被当前程序读写
  • MODE_APPEND:该文件的内容可以追加;
  • MODE_WORLD_READABLE:该文件的内容可以被其他程序读;
  • DE_WORLD_WRITEABLE:该文件的内容可以被其他程序写;

演示:

String fileName = "data.txt";
String content = "内容";

FileOutputStream fos = null;

try {
    fos = openFileOutput(fileName, MODE_PRIVATE);
    fos.write(content.getBytes());
} catch (IOException e) {
    e.printStackTrace();
}

注意:Android系统有一套自己的安全模型,默认情况下任何应用创建的文件都是私有的,其他程序无法访问。

输入流:

方法openFileInput()

说明:打开与此Context的应用程序包相关联的私有文件以供阅读。

public abstract FileInputStream openFileInput(String name)

参数:

name:要打开的文件名;不能包含路径分隔符。

演示:

String fileName = "data.txt";

FileInputStream fis = null;

try {
    fis = openFileInput(fileName);
    // 创建缓存区,并获取文件长度。
    byte[] buffer = new byte[fis.available()];
    // 将文件内容读取到buffer缓冲区
    fis.read(buffer);
    // 把获取到数据转换成字符串
    Toast.makeText(this, new String(buffer), Toast.LENGTH_SHORT).show();
    // 关闭输入流
    fis.close();
} catch (IOException e) {
    e.printStackTrace();
}

外部存储

输出流:

String fileName = "data.txt";
String content = "内容";


// 获取外部设备的状态
String state = Environment.getExternalStorageState();
// 判断外部设备是否可用
if (state.equals(Environment.MEDIA_MOUNTED)) {
    // 获取SD卡目录
    File sdpath = getExternalFilesDir(null);
    File file = new File(sdpath, fileName);
    FileOutputStream fos = null;
    try {
        fos = new FileOutputStream(file);
        fos.write(content.getBytes());
    } catch (IOException e) {
        e.printStackTrace();
    }
}

输入流:

// 获取外部设备的状态
String state = Environment.getExternalStorageState();
// 判断外部设备是否可用
if (state.equals(Environment.MEDIA_MOUNTED)) {
    // 获取SD卡目录
    File sdpath = getExternalFilesDir(null);
    // 创建文件对象
    File file = new File(sdpath, fileName);
    FileInputStream fis = null;
    BufferedReader br = null;
    try {
        // 创建文件输入流对象
        fis = new FileInputStream(file);
        // 创建字符输入缓冲流对象
        br = new BufferedReader(new InputStreamReader(fis));
        // 读取数据并提示出来
        Toast.makeText(this, br.readLine(), Toast.LENGTH_SHORT).show();
        // 关闭字符输入缓存流
        br.close();
        // 关闭输入流
        fis.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

申请SD卡写文件的权限

  • Android系统规定,程序访问系统的一些关键信息时,必须申请权限,否则程序运行时会因为没有访问系统信息的权限而直接崩溃。
  • 申请权限方式:
    • 静态申请权限。
    • 动态申请权限。

静态申请权限:

  • 适用系统版本:Android 6.0以下。
  • 在清单文件(AndroidManifest.xml)的<manifest>节点中声明需要申请的权限。
  • 申请SD卡的写权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  

动态申请权限:

  • 适用系统版本:Android 6.0及以上。
  • 权限:
    • 正常权限:不会直接给用户隐私权带来风险的权限。
    • 危险权限:涉及到用户隐私的权限,申请了该权限的应用,可能涉及了用户隐私信息的数据或资源,也可能对用户存储的数据或其他应用的操作产生影响。
    • 九组危险权限:位置(LOCATION)、日历(CALENDAR)、照相机(CAMERA)、联系人(CONTACTS)、存储卡(STORAGE)、传感器(SENSORS)、麦克风(MICROPHONE)、电话(PHONE)和短信(SMS)的相关权限。

方法:requestPermissions()

说明: 请求授予此应用程序的权限。这些权限必须在您的清单中请求,不应将其授予您的应用,并且它们应具有android.content.pm.PermissionInfo#PROTECTION_DANGEROUS dangerous,的保护级别,无论是否由平台或第三方应用。

参数:

activity:上下文。

permissions:申请权限回调方法。

requestCode:请求码。

public static void requestPermissions(final @NonNull Activity activity, final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {}

方法:onRequestPermissionsResult()

说明:请求权限的结果的回调。每次调用时都会调用此方法。

参数:

requestCode:请求码。

permissions:请求权限。

grantResults:用户授予权限的结果,当用户授予权限时,该数组中对应的值为PackageManager.PERMISSION_GRANTED

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {}

演示:

ActivityCompat.requestPermissions(MainActivity.this, new String[]{"android.permission.READ_EXTERNAL_STORAGE"}, 1);

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    Toast.makeText(this, "requestCode:" + requestCode, Toast.LENGTH_SHORT).show();
}

SharedPrefernces存储

SharedPreferences:是Android平台上一个轻量级的存储类,用于程序中一些少量数据持久化存储。

数据存储:

// 获取SharedPreferences实例对象
SharedPreferences sp = getSharedPreferences("data", MODE_PRIVATE);
// 获取编译器
SharedPreferences.Editor editor = sp.edit();
// 存入String的数据
editor.putString("name", "Webb");
// 存入Int的数据
editor.putInt("age", 18);
// 提交数据
editor.apply();

存储数据:以key/value(键值对)的形式保存数据,value值只能是float、int、long、boolean、String、Set类型数据。

注意:Android系统有一套自己的安全模型,默认情况下任何应用创建的文件都是私有的,其他程序无法访问。

读取数据:

SharedPreferences sp = getSharedPreferences("data", MODE_PRIVATE);
// 第一个参数:Key值。
// 第二个参数:缺省值(在找不到Key值的数据时就会输出缺省值)。
String name = sp.getString("name", "");

删除数据和清空数据:

// 根据key删除数据
editor.remove("name");
// 提交删除请求
editor.apply();
// 删除所有数据
editor.clear();
// 提交删除所有数据请求
editor.apply();

注意:执行删除或者清除所有数据都要提交请求。

注意事项:

  • 获取数据的key值与存入数据的key值,数据类型要一致,否则查找不到指定数据。
  • 保存SharedPreferences的key值时,使用静态变量保存,以免操作时写错,如private final String key = “data”。

SQLite 数据库存储

SQLite特点:

  • SQLite是Android自带的一个轻量级的数据库,他运算速度快,占用资源少,支持基本SQL语法。
  • SQLite数据库可以存储应用程序中的大量数据,并对数据进行管理和维护。

数据基础操作

创建数据库:

public class MyHelper extends SQLiteOpenHelper {
    // 第一次创建时调用,用于初始化表结构。
    public MyHelper(@Nullable Context context) {
        super(context, "database.db", null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        // 创建数据表
        db.execSQL("CREATE TABLE information(_id INTEGER PRIMARY KEY AUTOINCREMENT, name VARCHAR(20), price INTEGER)");
    }

    // 数据库的版本增加时调用
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

添加数据:

// 调用添加数据方法
insert("Webb", "10");

public void insert(String name, String price) {
    MyHelper helper = new MyHelper(MainActivity.this);
    // 获取可读写SQLiteDatabse对象。
    SQLiteDatabase db = helper.getWritableDatabase();
    // 创建ContentValues对象并就数据添加到ContentValues对象中。
    ContentValues values = new ContentValues();
    values.put("name", name);
    values.put("price", price);
    // 调用inset()方法将数据添加到数据库中,添加成功后返回id。
    long id = db.insert("information", null, values);
    db.close();
}

删数数据:

// 调用删除数据方法
delete(1);

public int delete(long id) {
    MyHelper helper = new MyHelper(MainActivity.this);
    SQLiteDatabase db = helper.getWritableDatabase();
    // 调用delete()方法删除数据库指定数据。
    int number = db.delete("information", "_id=?", new String[]{id + ""});
    db.close();
    return number;
}

更新数据:

// 调用更新数据方法
update("Webb-L","30");	

public int update(String name, String price) {
    MyHelper helper = new MyHelper(MainActivity.this);
    SQLiteDatabase db = helper.getWritableDatabase();
    // 创建ContentValues对象将修改的数据添加到ContentValues对象中
    ContentValues values = new ContentValues();
    values.put("price", price);
    // 调用update ()方法修改数据
    int number = db.update("information", values, " name =?", new String[]{name});
    db.close();
    return number;
}

查询数据:

// 调用查询数据方法
find(1);

public void find(int id) {
    String _id = null;
    String name = null;
    String price = null;
    MyHelper helper = new MyHelper(MainActivity.this);
    SQLiteDatabase db = helper.getReadableDatabase();
    // 调用query ()方法查询数据库中的数据,返回一个行数集合Cursor
    Cursor cursor = db.query("information", null, "_id=?", new String[]{id + ""}, null, null, null);
    // 数据总条数
    if (cursor.getCount() != 0) {
        // 移动游标指向下一行数据
        while (cursor.moveToNext()) {
            // 获取数据
            _id = cursor.getString(cursor.getColumnIndex("_id"));
            name = cursor.getString(cursor.getColumnIndex("name"));
            price = cursor.getString(cursor.getColumnIndex("price"));
        }
        // 输出数据
        Toast.makeText(this, "id:" + _id + " name:" + name + " price" + price, Toast.LENGTH_SHORT).show();
    }
    cursor.close();
    db.close();
}

使用SQL语句进行数据库操作

//增加一条数据
db.execSQL("insert into information (name, price) values (?,?)", new Object[]{name, price });
//删除一条数据
db.execSQL("delete from information where _id  = 1");
//修改一条数据
db.execSQL("update information set name=? where price =?", new Object[]{name, price });
//执行查询的SQL语句
Cursor cursor = db.rawQuery("select * from information where name=?", new String[]{name});

数据库事务

  • 数据库事务是一个对数据库执行工作单元,是针对数据库的一组操作,他可以由一条或多条SQL语句组成。
  • 事务是以逻辑顺序完成的工作单位或序列,可以是由用户手动操作完成,也可以是由某种数据库程序自动完成。
  • SQLite是遵守ACID的关系型数据库管理系统,ACID是指数据库事务正确执行的四个基本要素。
image-20200512204556317

使用:

MyHelper helper = new MyHelper(getApplication());
SQLiteDatabase db = helper.getWritableDatabase();
// 开启事务
db.beginTransaction();
try {
    db.execSQL("update information set price = 1000 where name =?", new Object[]{"Webb-L"});
    // 设置事务标志为成功,当事务结束时,提交事务
    db.setTransactionSuccessful();
} catch (Exception e) {
    Log.i("事务处理失败", e.toString());
} finally {
    // 关闭数据库事务
    db.endTransaction();
    //关闭数据库
    db.close();
}

本章小结

本章主要讲解了Android中的数据存储,首先介绍了Android中常见的数据存储方式,然后详细地讲解了文件存储、SharedPreferences存储以及SQLite数据库存储,数据存储是Android开发中非常重要的内容,一般在应用程序中会经常涉及到数据存储的知识,因此要求初学者必须熟练掌握本章知识。