文件与数据库
其它相关文章:
Android编程规范摘要1 (资源文件命名与使用)
Android编程规范摘要2 (基本组件)
Android编程规范摘要3 (UI与布局)
Android编程规范摘要4 (进程、线程与消息通信)
Android编程规范摘要5 (文件与数据库)
Android编程规范摘要6 (Bitmap、Drawable 与动画)
Android编程规范摘要7 (安全)
- [强制] 任何时候不要硬编码文件路径,请使用Android 文件系统API 访问。
- 风险: 不仅存在安全隐患,也让app 更容易出现适配问题
- 外部存储APIs:
APIs | 参考目录 |
Environment.getDataDirectory() | /data |
Environment.getDownloadCacheDirectory() | /cache |
Environment.getExternalStorageDirectory() | /storage/emulated/0 |
Environment.getExternalStoragePublicDirectory(String type) | /storage/emulated/0/[@type] |
Environment.getRootDirectory() | /system |
Environment.getExternalStoragePublicDirectory(String type) 参数参考:
Type | 参考目录 |
Environment.DIRECTORY_DOWNLOADS | /storage/emulated/0/Download |
Environment.DIRECTORY_ALARMS | /storage/emulated/0/Alarms |
Environment.DIRECTORY_DCIM | /storage/emulated/0/DCIM |
Environment.DIRECTORY_DOCUMENTS | /storage/emulated/0/Documents |
Environment.DIRECTORY_MOVIES | /storage/emulated/0/Movies |
Environment.DIRECTORY_MUSIC | /storage/emulated/0/Music |
Environment.DIRECTORY_NOTIFICATIONS | /storage/emulated/0/Notifications |
Environment.DIRECTORY_PICTURES | /storage/emulated/0/Pictures |
Environment.DIRECTORY_PODCASTS | /storage/emulated/0/Podcasts |
Environment.DIRECTORY_RINGTONES | /storage/emulated/0/Ringtones |
- 内部存储APIs:
APIs | 参考目录 |
Context#getDataDir() | /data/user/0/[PackageName] |
Context#getFilesDir() | /data/user/0/[PackageName]/files |
Context#getCacheDir() | /data/user/0/[PackageName]/cache |
- [强制] 当使用外部存储时,必须检查外部存储的可用性。
- 正例:
// 读/写检查
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
// 只读检查
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
- [强制] 应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用FileProvider。
- 正例:
manifest:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" />
</provider>
void getAlbumImage(String imagePath) {
File image = new File(imagePath);
Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
Uri imageUri = FileProvider.getUriForFile(
this, "com.example.provider", image);
getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE);
}
- 反例:
void getAlbumImage(String imagePath) {
File image = new File(imagePath);
Intent getAlbumImageIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//不要使用file://的URI 分享文件给别的应用,包括但不限于Intent
getAlbumImageIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(image));
startActivityForResult(takePhotoIntent, REQUEST_GET_ALBUMIMAGE);
}
SharedPreference
中只能存储简单数据类型(int、boolean、String 等),
复杂数据类型建议使用文件、数据库等其他方式存储。SharedPreference
提交数据时, 尽量使用Editor#apply()
, 而非Editor#commit()
。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用Editor#commit()
。
- apply 方法: 提交会先写入内存,然后异步写入磁盘。优先考虑使用
- commit 方法: 直接写入磁盘。
- 如果频繁进行修改,使用apply 性能更好,因为磁盘写入显然时间开销更大。
- 如果希望立即获取存储操作的结果,并据此做相应处理,则应使用commit:
if (!editor.commit()) {
//提交失败的处理...
}
- [强制] 数据库Cursor 必须确保使用完后关闭,以免内存泄漏。
- 无论Cursor操作是否正常结束,最后必须确保 Cursor 正常关闭。
- 一般在finally子句中关闭Cursor。
- [强制] 多线程操作写入数据库时,需要使用事务,以免出现同步问题。
- 说明: 通过
SQLiteOpenHelper
获取数据库SQLiteDatabase
实例,Helper 中会自动缓存已经打开的SQLiteDatabase
实例,单个App 中应使用SQLiteOpenHelper
的单例模式确保数据库连接唯一。由于SQLite 自身是数据库级锁,单个数据库操作是保证线程安全的(不能同时写入),transaction 是一次原子操作,因此处于事务中的操作是线程安全的。 - 若同时打开多个数据库连接,并通过多线程写入数据库,会导致数据库异常,提示数据库已被锁住。
- 正例:
public void insertUserPhoto(SQLiteDatabase db, String userId, String content) {
ContentValues cv = new ContentValues();
cv.put("userId", userId);
cv.put("content", content);
db.beginTransaction();
try {
db.insert(TUserPhoto, null, cv);
// 其他操作
db.setTransactionSuccessful();
} catch (Exception e) {
// TODO
} finally {
db.endTransaction();
}
}
- 反例:
public void insertUserPhoto(SQLiteDatabase db, String userId, String content) {
ContentValues cv = new ContentValues();
cv.put("userId", userId);
cv.put("content", content);
db.insert(TUserPhoto, null, cv);
}
- 大数据写入数据库时,请使用事务或其他能够提高I/O 效率的机制,保证执行速度。
- 正例:
public void insertBulk(SQLiteDatabase db, ArrayList<UserInfo> users) {
db.beginTransaction();
try {
for (int i = 0; i < users.size; i++) {
ContentValues cv = new ContentValues();
cv.put("userId", users[i].userId);
cv.put("content", users[i].content);
db.insert(TUserPhoto, null, cv);
}
// 其他操作
db.setTransactionSuccessful();
} catch (Exception e) {
// TODO
} finally {
db.endTransaction();
}
}
- [强制] 执行SQL 语句时,应使用
SQLiteDatabase#insert()
、update()
、delete()
,不要使用SQLiteDatabase#execSQL()
,以免SQL 注入风险。
- 反例:
public void updateUserPhoto(SQLiteDatabase db, String userId, String content) {
String sqlStmt = String.format("UPDATE %s SET content=%s WHERE userId=%s",
TUserPhoto, userId, content);
//请提高安全意识,不要直接执行字符串作为SQL 语句
db.execSQL(sqlStmt);
}
- [强制] 如果ContentProvider 管理的数据存储在SQL 数据库中,应该避免将不受信任的外部数据直接拼接在原始SQL 语句中。
- 正例:
// 使用一个可替换参数
String mSelectionClause = "var = ?";
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;
- 反例:
// 拼接用户输入内容和列名
String mSelectionClause = "var = " + mUserInput;