文章

Android基础——数据存储与共享

文件存储

内部存储与外部存储

应用的存储区域在逻辑上分为内部存储和外部存储,机身存储和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来实现应用间共享数据

image-20220316161907659

URI

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

img

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);  // 注销观察者
本文由作者按照 CC BY 4.0 进行授权