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对象的getString
,getInt
等方法
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移动到最后一行,返回truemoveToNext()
:使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()
:添加数据,返回这条新纪录的URIupdate()
:更新数据,返回受影响的行数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后追加一个idparseId(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); // 注销观察者