文件与数据库


其它相关文章:
Android编程规范摘要1 (资源文件命名与使用)
Android编程规范摘要2 (基本组件)
Android编程规范摘要3 (UI与布局)
Android编程规范摘要4 (进程、线程与消息通信)
Android编程规范摘要5 (文件与数据库)
Android编程规范摘要6 (Bitmap、Drawable 与动画)
Android编程规范摘要7 (安全)


  1. [强制] 任何时候不要硬编码文件路径,请使用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

  1. [强制] 当使用外部存储时,必须检查外部存储的可用性。
  • 正例:
// 读/写检查
    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;
    }
  1. [强制] 应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用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);
}
  1. SharedPreference 中只能存储简单数据类型(int、boolean、String 等),
    复杂数据类型建议使用文件、数据库等其他方式存储。
  2. SharedPreference 提交数据时, 尽量使用Editor#apply() , 而非 Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用Editor#commit()
  • apply 方法: 提交会先写入内存,然后异步写入磁盘。优先考虑使用
  • commit 方法: 直接写入磁盘。
  • 如果频繁进行修改,使用apply 性能更好,因为磁盘写入显然时间开销更大。
  • 如果希望立即获取存储操作的结果,并据此做相应处理,则应使用commit:
if (!editor.commit()) {
    //提交失败的处理...
}
  1. [强制] 数据库Cursor 必须确保使用完后关闭,以免内存泄漏。
  • 无论Cursor操作是否正常结束,最后必须确保 Cursor 正常关闭。
  • 一般在finally子句中关闭Cursor。
  1. [强制] 多线程操作写入数据库时,需要使用事务,以免出现同步问题。
  • 说明: 通过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);
}
  1. 大数据写入数据库时,请使用事务或其他能够提高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();
    }
}
  1. [强制] 执行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);
}
  1. [强制] 如果ContentProvider 管理的数据存储在SQL 数据库中,应该避免将不受信任的外部数据直接拼接在原始SQL 语句中。
  • 正例:
// 使用一个可替换参数
String mSelectionClause = "var = ?";
String[] selectionArgs = {""};
selectionArgs[0] = mUserInput;
  • 反例:
// 拼接用户输入内容和列名
String mSelectionClause = "var = " + mUserInput;