文件存储

内部存储与外部存储

应用的存储区域在逻辑上分为内部存储和外部存储,机身存储和 SD 卡存储逻辑上都属于外部存储

应用在两个存储区域通过包名来标识,当删除应用时,两个存储区域的对应包名文件夹也会删除

  • 内部存储

    /data 目录,存储 SharedPreference 和 SQLite 数据库,包含 files 和 cache 目录,非 root 手机中不可见

  • 外部存储

    /storage 目录,可在文件管理器中访问,包名文件夹位于 /storage/**/Android/data/ 下,包名文件夹中包含 files 和 cache 目录

    读写外部存储时需要添加权限

    1
    2
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

内部存储获取方法:通过 Context 类获取

  • getDir(name, mode):在 data 目录下获取或创建名称为 name 的目录
  • getFilesDir():返回内部存储的 files 目录
  • getCacheDir():返回内部存储的 cache 目录
  • Environment.getDataDirectory():返回 data 目录(内部存储根目录)

外部存储获取方法:通过 Context 类获取

  • Environment.getExternalStorageDirectory():获取外部存储根目录
  • Environment.getExternalStoragePublicDirectory(""):获取外部存储根目录
  • getExternalFilesDir():获取外部存储的 files 目录及其子目录
  • getExternalCacheDir():获取外部存储的 cache 目录

其他目录

  • Environment.getDownloadCacheDirectory():获取 /cache 目录
  • Environment.getRootDirectory():获取 /system 目录

文件基本操作

文件的操作模式

  • MODE_PRIVATE:默认模式,表示该文件只能被应用本身访问,重新写入会覆盖原内容
  • MODE_APPEND:追加模式
  • MODE_WORLD_READABLE:当前文件可被其他应用读取
  • MODE_WORLD_WRITEABLE:当前文件可被其他应用写入

内部存储文件操作方法:Context 类方法,流操作同 Java

  • openFileOutput(filename, mode):打开文件输出流,文件位于内部存储 files 目录中
  • openFileInput(filename):打开文件输入流,文件位于内部存储 files 目录中

外部存储文件操作:获取外部存储目录后,通过文件名获取 File 对象,操作文件流

SharedPreferences

SharedPreferences 使用键值对进行存储,存储在内部存储中,使用 XML 格式管理文件,通常用于配置信息存储

获取 SharedPreferences 对象

  • context.getSharedPreferences(name, mode)

    mode 参数只选 MODE_PRIVATE,表示只有当前应用程序对该对象进行读写

  • activity.getSharedPreferences(mode)

    文件名默认为当前 Activity 的类名

存储数据

  • 调用 SharedPreferences 对象的 edit(),获取 Editor 对象
  • 调用 Editor 对象的 putString,putBoolean 等方法添加数据,参数为键值对
  • 调用 commit() 提交数据
1
2
3
4
5
SharedPreferences sp = context.getSharedPreferences("my_sp", MODE_PRIVATE);
Editor editor = sp.edit();
editor.putString("username", "value1");
editor.putString("password", "value2");
editor.commit();

读取数据:调用 SharedPreferences 对象的 getStringgetInt 等方法

1
2
3
SharedPreferences sp = context.getSharedPreferences("my_sp", MODE_PRIVATE);
String username = sp.getString("username", "default_value");
String password = sp.getString("password", "default_value");

SQLite 数据库

SQLite 是一个轻量级关系型数据库,数据库文件存放在 /data/data/<packagename>/databases/ 目录下

SQLite 中的三个核心类

  • SQLiteOpenHelper:用于管理数据库,使用时继承该类,重写它的 onCreate()onUpgrade() 方法,定义数据库创建和更新的操作
  • SQLiteDatabase:数据库访问类,通过该类方法操作数据库
  • Cursor:数据库记录指针,可用于遍历结果集

数据库管理

通过 SQLiteOpenHelper 类的实例方法可获取 SQLiteDatabase 数据库对象,首次调用时会调用 onCreate()

  • getReadableDatabase():以只读方式打开数据库
  • getWritableDatabase():以可写方式打开数据库,当数据库不可写入时,调用会抛出异常

继承 SQLiteOpenHelper 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyDBOpenHelper extends SQLiteOpenHelper {
public MyDBOpenHelper(Context context, String name, CursorFactory factory, int version) {
// factory参数用于自定义cursor
// 定义数据库名称和版本号
super(context, "my.db", null, 1);
}

@Override
public void onCreate(SQLiteDatabase db) {
// 通过SQL语句操作数据库
db.execSQL("CREATE TABLE person(personid INTEGER PRIMARY KEY AUTOINCREMENT,name VARCHAR(20))");
}

//软件版本号发生改变时调用
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("ALTER TABLE person ADD phone VARCHAR(12) NULL");
}
}

数据库操作

调用 SQLiteDatabase 对象方法来操作数据库

SQL 语句

  • execSQL:执行 SQL 语句,传入 SQL 语句字符串和占位符参数,SQL 语句中占位符是 ? 问号
  • rawQuery:执行 SQL 语句查询,传入 SQL 语句字符串和占位符参数,返回一个 Cursor 对象,用于遍历结果集

添加数据

调用 insert(),第一个参数为表名,第二个参数为将未指定数据的可空列自动赋值为 null,第三个参数为一个 ContentValues 对象,表示一个键值对

1
2
3
ContentValues values = new ContentValues();
values.put("name", "hello");
db.insert("person", null, values);

更新数据

调用 update(),第一个参数为表名,第二个参数是 ContentValues 对象,表示修改后的数据,第三、四个参数用于 where 约束,不指定默认更新所有行

1
2
3
ContentValues values = new ContentValues();
values.put("name", "world");
db.update("person", values, "name = ?", new String[]{"hello"});

删除数据

调用 delete(),第一个参数为表名,第二、三个参数指定约束,不指定默认删除所有行

1
db.delete("person", "personid = ?", new String[]{"3"});

查询操作

调用 query(),传入参数指定子句或约束,声明如下

1
2
3
4
5
6
7
8
9
10
public Cursor query(
String table,
String[] columns,
String selection,
String[] selectionArgs,
String groupBy,
String having,
String orderBy,
String limit
);
  • table:指定表名
  • columns:指定列名
  • selection:指定 where 条件
  • selectionArgs:为 where 条件的占位符提供值
  • groupBy:指定需要 groupby 的列
  • having:having 子句
  • orderby:指定排序的列
  • limit:指定 limit 子句

Cursor 指针

query 方法和 rawQuery 方法返回一个 Cursor 对象,通过该对象获取数据

  • move(offset):向前或向后移动指定行数,负数表示向前移动
  • moveToFirst():使 cursor 移动到第一行,返回 true 表示有数据
  • moveToLast():使 cursor 移动到最后一行,返回 true
  • moveToNext():使 cursor 移动到下一行,返回 true 表示下一行有数据
  • moveToPrevious():使 cursor 移动到上一行
  • getCount():返回总行数
  • isFirst():是否是第一行
  • isLast():是否是最后一行
  • moveToPosition(int):移动到指定行
  • getColumnIndex(String):获取指定列的列索引
  • getXXX(int):返回当前行的指定列索引的数据

使用事务

事务可以使一组操作变为原子操作

  • beginTransaction():开启事务
  • endTransaction():关闭事务
1
2
3
4
5
6
7
8
9
SQliteDatabase db = helper.getWritableDatabase();
db.beginTransaction();
try {
// ...
} catch (Exception e) {
// ...
} finally {
db.endTransaction(); // 最终关闭事务
}

ContentProvider 数据共享

Android 应用通过 ContentProvider 来实现应用间共享数据

URI

URI 为统一资源标识符,主要由 scheme、authority 和 path 组成,使用 ContentProvider 时,authority 通常为 packagename.provider

Android 常用的 scheme

  • content:本地资源
  • file:本地文件
  • http:网络资源
  • tel:打电话
  • smsto:发送短信

URI 通配符

  • * 表示匹配任意长度的任意字符
  • #表示匹配任意长度的数字

Uri.parse() 方法可以将 URI 字符串解析为 Uri 对象

访问 ContentProvider

通过 ContentResolver 类访问其他程序的 ContentProvider,调用 Context 的 getContentResolver() 获取实例

基本操作方法

  • 添加数据

    1
    Uri insert(Uri uri, ContentValues values)
  • 删除数据:通过 where 子句添加约束

    1
    int delete(Uri uri, String where, String[] args)
  • 查询数据

    1
    2
    3
    4
    5
    6
    7
    Cursor query(
    Uri uri,
    String[] projection,
    String selection,
    String[] selectionArgs,
    String sortOrder
    )
  • 更新数据

    1
    int update(Uri uri, ContentValues values, String where, String[] selectionArgs)

Android11 后,在访问其他程序的 ContentProvider 需要在 AndroidManifest.xml 中声明 queries 标签

1
2
3
<queries>
<package android:name="com.example.databasetest"/>
</queries>

自定义 ContentProvider

自定义 ContentProvider 用于给其他程序提供数据访问接口

实现 ContentProvider

自定义继承 ContentProvider 的类,重写六个方法,也可默认空实现

  • onCreate():在初始化时调用,用于创建数据库或升级数据库,返回初始化是否成功

  • query():查询,结果放入 Cursor 对象返回

  • insert():添加数据,返回这条新纪录的 URI

  • update():更新数据,返回受影响的行数

  • delete():删除数据,返回被删除的行数

  • getType():返回传入的 URI 对应的 MIME 类型

自定义 ContentProvider 需要在 AndroidManifest.xml 中注册

1
2
3
4
5
<provider
android:name="com.example.bean.MyContentProvider"
android:authorities="com.example.providers.myprovider"
android:exported="true">
</provider>

URI 注册

在 ContentProvider 中需要借助 UriMatcher 类注册特定的 URI

调用 addURI(authority, path, code),该方法将 URI 与一个自定义编码 code 绑定起来,调用 match(uri) 时返回 code,即 URI 为 content://authority/path 时,match 方法返回注册的 code

1
2
3
4
5
6
7
8
9
10
11
12
13
UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI("com.example.myprovider", "user1", 1);
matcher.addURI("com.example.myprovider", "user2", 2);
switch (matcher.match(uri)) {
case 1:
// ...
break;
case 2:
// ...
break;
default:
break;
}

其他工具类

ContentUris 类:URI 相关处理

  • withAppendedId(uri, int):在 URI 后追加一个 id
  • parseId(uri):解析 URI 中的 id

ContentObserver 类:用于将 ContentProvider 内的数据变化通知给 ContentResolver

1
2
3
4
5
6
7
8
9
10
11
resolver.registerContentObserver(uri);  // resolver注册观察者,观察URI的数据变化

public class MyProvider extends ContentProvider {

public Uri insert(Uri uri, ContentValues values) {
// ...
getContext().getContentResolver().notifyChange(uri, null); // 通知访问者
}
}

resolver.unregisterContentObserver(uri); // 注销观察者