文件与数据库
其它相关文章:
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;