Android 數據存儲

1、數據存儲方式介紹

Android 使用的文件系統相似於其餘平臺上基於磁盤的文件系統。該系統爲您提供瞭如下幾種保存應用數據的選項:java

  • 應用專屬存儲空間:存儲僅供應用使用的文件,能夠存儲到內部存儲卷中的專屬目錄或外部存儲空間中的其餘專屬目錄。使用內部存儲空間中的目錄保存其餘應用不該訪問的敏感信息。
  • 共享存儲:存儲您的應用打算與其餘應用共享的文件,包括媒體、文檔和其餘文件。
  • 偏好設置:以鍵值對形式存儲私有原始數據。
  • 數據庫:使用 Room 持久性庫將結構化數據存儲在專用數據庫中。

下表彙總了這些選項的特色:android

內容類型 訪問方法 所需權限 其餘應用是否能夠訪問 卸載應用是否移除
僅供您的應用使用的文件 從內部存儲空間訪問,能夠使用 getFilesDir() 或 getCacheDir() 方法;

從外部存儲空間訪問,能夠使用 getExternalFilesDir() 或 getExternalCacheDir() 方法
從內部存儲空間訪問不須要任何權限

若是應用在搭載 Android 4.4(API 級別 19)或更高版本的設備上運行,從外部存儲空間訪問不須要任何權限
若是文件存儲在內部存儲空間中的目錄內,則不能訪問
若是文件存儲在外部存儲空間中的目錄內,則能夠訪問
可共享的媒體文件(圖片、音頻文件、視頻) MediaStore API 在 Android 10(API 級別 29)或更高版本中,訪問其餘應用的文件須要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 權限

在 Android 9(API 級別 28)或更低版本中,訪問全部文件均須要相關權限
是,但其餘應用須要 READ_EXTERNAL_STORAGE 權限
鍵值對 SharedPreferences
結構化數據 數據庫 Room
外部共享存儲空間文件 經過路徑或者Uri訪問 須要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 權限

android 11增長MANAGE_EXTERNAL_STORAGE權限

2、不一樣存儲方式使用

2.1 應用專屬存儲空間

內部存儲:/data/data/packagename/ 外部存儲:/sdcard/Android/data/packagename/數據庫

這兩個目錄本App無需申請訪問權限便可使用。使用方式也很簡單,直接經過路徑訪問便可。markdown

public static String readFile(Context context, String fileName) {
    File file = new File(context.getCacheDir(), fileName);
    try {
      FileInputStream fis = new FileInputStream(file);
      BufferedReader br = new BufferedReader(new InputStreamReader(fis));
      Log.d(TAG, "readFile: " + br.readLine());
      return br.readLine();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return "";

  }

  public static void writeFile(Context context, String fileName, String content) {
    File file = new File(context.getCacheDir(), fileName);
    try {
      FileOutputStream fos = new FileOutputStream(file);
      fos.write(content.getBytes());
      fos.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
複製代碼

2.2 共享媒體文件

申請權限

Android 6.0 以前是無需申請動態權限的,在AndroidManifest.xml 裏聲明存儲權限:框架

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
複製代碼

Android 6.0 以後除了在AndroidManifest.xml 裏聲明存儲權限,還須要動態申請權限。ide

//檢查權限,並返回須要申請的權限列表
  private List<String> checkPermission(Context context, String[] checkList) {
    List<String> list = new ArrayList<>();
    for (int i = 0; i < checkList.length; i++) {
      if (PackageManager.PERMISSION_GRANTED != ActivityCompat
          .checkSelfPermission(context, checkList[i])) {
        list.add(checkList[i]);
      }
    }
    return list;
  }

  //申請權限
  private void requestPermission(Activity activity, String requestPermissionList[]) {
    ActivityCompat.requestPermissions(activity, requestPermissionList, 100);
  }

  //用戶做出選擇後,返回申請的結果
  @Override
  public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == 100) {
      for (int i = 0; i < permissions.length; i++) {
        if (permissions[i].equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
          if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
            Toast.makeText(MainActivity.this, "存儲權限申請成功", Toast.LENGTH_SHORT).show();
          } else {
            Toast.makeText(MainActivity.this, "存儲權限申請失敗", Toast.LENGTH_SHORT).show();
          }
        }
      }
    }
  }

  //測試申請存儲權限
  private void testPermission(Activity activity) {
    String[] checkList = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE};
    List<String> needRequestList = checkPermission(activity, checkList);
    if (needRequestList.isEmpty()) {
      Toast.makeText(MainActivity.this, "無需申請權限", Toast.LENGTH_SHORT).show();
    } else {
      requestPermission(activity, needRequestList.toArray(new String[needRequestList.size()]));
    }
  }
複製代碼

訪問方式

1. 經過路徑訪問

以圖片爲例,假設圖片存儲在/sdcard/Pictures/test.jpgpost

private Bitmap getBitmap(String fileName) {
    //獲取目錄:/storage/emulated/0/
    File rootFile = Environment.getExternalStorageDirectory();
    String imagePath = rootFile.getAbsolutePath() + File.separator + Environment.DIRECTORY_PICTURES + File.separator + fileName;
    return BitmapFactory.decodeFile(imagePath);
  }
複製代碼
2. 經過MediaStore獲取路徑
private Bitmap getBitmap(Context context, String fileName) {
    ContentResolver contentResolver = context.getContentResolver();
    //查詢條件
    String selection = MediaStore.Images.Media.DISPLAY_NAME + " = ?";
    String[] selectionArgs = new String[]{fileName};

    Cursor cursor = contentResolver
        .query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, selection, selectionArgs, null);
    while (cursor.moveToNext()) {
      //獲取圖片路徑
      String imagePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA));
      return BitmapFactory.decodeFile(imagePath);
    }
    return null;
  }
複製代碼
3. 經過MediaStore獲取Uri
private Bitmap getBitmap(Context context, String fileName) throws FileNotFoundException {
    ContentResolver contentResolver = context.getContentResolver();
    String selection = MediaStore.Images.Media.DISPLAY_NAME + " = ?";

    String[] selectionArgs = new String[]{fileName};

    Cursor cursor = contentResolver
        .query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, null, selection, selectionArgs, null);
    while (cursor.moveToNext()) {
      long id = cursor.getLong(cursor.getColumnIndex(MediaStore.Images.Media._ID));
	  //獲取Uri
      Uri contentUri = ContentUris.withAppendedId(
          MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
          id
      );
      //經過Uri構造InputStream
      InputStream inputStream = contentResolver.openInputStream(contentUri);
      //經過InputStream獲取Bitmap
      return BitmapFactory.decodeStream(inputStream);
    }
    return null;
  }
複製代碼

適配Android 10

Android 10及以上版本沒法直接經過路徑獲取到文件,即 BitmapFactory.decodeFile(imagePath) 沒法正常使用,會提示沒有權限。只能經過 Uri 方式獲取到圖片資源。測試

2.3 SharedPreferences

SharedPreferences 主要用來保存相對較小鍵值對集合,使用也十分簡單。ui

Context context = getActivity();
//建立SharedPreferences
SharedPreferences sharedPref = context.getSharedPreferences(
            name, Context.MODE_PRIVATE);
    //經過editor 寫入數據
    SharedPreferences.Editor editor = sharedPref.edit();
    editor.putInt(key, value);
    editor.commit();
//讀取數據
int data = sharedPref.getInt(key, defaultValue);

複製代碼

2.4 數據庫

關於 Room 的基本使用能夠參考個人另一篇文章:Android Room 使用this

2.5 外部非媒體的共享存儲空間

除了應用專屬空間以外的存儲空間。

申請權限

與共享媒體文件同樣,都須要申請 WRITE_EXTERNAL_STORAGEREAD_EXTERNAL_STORAGE

訪問方式

1. 經過路徑訪問

與媒體文件同樣,直接構造路徑進行訪問。 經過 Environment.getExternalStorageDirectory() 獲取外部存儲路徑,而後經過這個路徑進行操做便可。

private void testPublicFile() {
        File rootFile = Environment.getExternalStorageDirectory();
        String imagePath = rootFile.getAbsolutePath() + File.separator + "myDir";
        File myDir = new File(imagePath);
        if (!myDir.exists()) {
            myDir.mkdir();
        }
    }
複製代碼

在/sdcard/目錄下建立 myDir 的文件夾。

2. 經過SAF訪問

Storage Access Framework 簡稱SAF:存儲訪問框架。至關於系統內置了文件選擇器,經過它能夠拿到想要訪問的文件信息。

private void startSAF() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    //選擇圖片
    intent.setType("image/jpeg");
    //會跳轉到一個文件選擇器中
    startActivityForResult(intent, 100);
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == 100 && data != null) {
      //選中返回的圖片封裝在uri裏
      Uri uri = data.getData();
      Bitmap bitmap = openUri(uri);
      if (bitmap != null) {
        binding.iv.setImageBitmap(bitmap);
      }
    }
  }
  private Bitmap openUri(Uri uri) {
    try {
      //從uri構造輸入流
      InputStream fis = getContentResolver().openInputStream(uri);
      return BitmapFactory.decodeStream(fis);
    } catch (Exception e) {
      Log.e(TAG, "openUri: ", e);
    }
    return null;
  }
複製代碼

跳轉到系統內置的文件選擇器:

在這裏插入圖片描述

該方式不須要申請讀寫權限,也能夠訪問外部文件,可是沒法直接獲取到外部文件的路徑,須要經過Uri進行轉換。

適配Android 10

Android 10 開始增長了分區存儲功能,限制APP只能訪問應用專屬存儲空間,沒法直接經過路徑訪問sdcard中的文件,只能使用SAF的方式。 能夠避免各個APP無節操的一直往sdcard卡中寫入數據。而申請文件讀寫權限的提示語也作了修改。

Android 6 - Android 9:

在這裏插入圖片描述

Android 10及以上版本:

在這裏插入圖片描述

對比低版本,只容許訪問照片和媒體的內容。

適配Android 11

Android 11 增長了一個全部文件訪問權限 MANAGE_EXTERNAL_STORAGE ,該權限比文件讀寫權限更爲嚴格,不是彈出框提示,而是須要跳轉到設置界面進行受權。若是受權該權限,就容許開發者使用路徑方式訪問外部存儲的全部文件。 AndroidManifest.xml 中新增如下權限:

<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
複製代碼

代碼中動態申請權限:

private void requestPermission() {
  	//須要判斷版本
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      // 先判斷有沒有權限
      if (Environment.isExternalStorageManager()) {
        //已經受權
      } else {
        Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
        intent.setData(Uri.parse("package:" + getPackageName()));
        startActivityForResult(intent, 101);
      }
    }
  }
  
  @Override
  protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == 101 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      if (Environment.isExternalStorageManager()) {
        Toast.makeText(this, "全部文件訪問權限受權成功", Toast.LENGTH_SHORT).show();
      } else {
        Toast.makeText(this, "全部文件訪問權限受權失敗", Toast.LENGTH_SHORT).show();
      }
    }
  }
複製代碼

受權方式以下圖:

在這裏插入圖片描述

相關文章
相關標籤/搜索