Android编程规范摘要5 (文件与数据库)

文件与数据库


其它相关文章:
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
  2. [强制] 当使用外部存储时,必须检查外部存储的可用性。

    • 正例:

      // 读/写检查
          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;
          }
  3. [强制] 应用间共享文件时,不要通过放宽文件系统权限的方式去实现,而应使用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);
      }
  4. SharedPreference 中只能存储简单数据类型(int、boolean、String 等),
    复杂数据类型建议使用文件、数据库等其他方式存储。

  5. SharedPreference 提交数据时, 尽量使用Editor#apply() , 而非 Editor#commit()。一般来讲,仅当需要确定提交结果,并据此有后续操作时,才使用Editor#commit()

    • apply 方法: 提交会先写入内存,然后异步写入磁盘。优先考虑使用
    • commit 方法: 直接写入磁盘。
    • 如果频繁进行修改,使用apply 性能更好,因为磁盘写入显然时间开销更大。
    • 如果希望立即获取存储操作的结果,并据此做相应处理,则应使用commit:

      if (!editor.commit()) {
          //提交失败的处理...
      }
  6. [强制] 数据库Cursor 必须确保使用完后关闭,以免内存泄漏。

    • 无论Cursor操作是否正常结束,最后必须确保 Cursor 正常关闭。
    • 一般在finally子句中关闭Cursor。
  7. [强制] 多线程操作写入数据库时,需要使用事务,以免出现同步问题。

    • 说明: 通过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);
      }
  8. 大数据写入数据库时,请使用事务或其他能够提高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();
          }
      }
  9. [强制] 执行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);
      }
  10. [强制] 如果ContentProvider 管理的数据存储在SQL 数据库中,应该避免将不受信任的外部数据直接拼接在原始SQL 语句中。

    • 正例:

      // 使用一个可替换参数
      String mSelectionClause = "var = ?";
      String[] selectionArgs = {""};
      selectionArgs[0] = mUserInput;
    • 反例:

      // 拼接用户输入内容和列名
      String mSelectionClause = "var = " + mUserInput;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值