Android 數(shù)據(jù)存儲

一、數(shù)據(jù)存儲方式介紹

Android 使用的文件系統(tǒng)類似于其他平臺上基于磁盤的文件系統(tǒng)。該系統(tǒng)為您提供了以下幾種保存應(yīng)用數(shù)據(jù)的選項:

  • 應(yīng)用專屬存儲空間:存儲僅供應(yīng)用使用的文件歪玲,可以存儲到內(nèi)部存儲卷中的專屬目錄或外部存儲空間中的其他專屬目錄载矿。使用內(nèi)部存儲空間中的目錄保存其他應(yīng)用不應(yīng)訪問的敏感信息漆羔。
  • 共享存儲:存儲您的應(yīng)用打算與其他應(yīng)用共享的文件剿干,包括媒體、文檔和其他文件抓谴。
  • 偏好設(shè)置:以鍵值對形式存儲私有原始數(shù)據(jù)暮蹂。
  • 數(shù)據(jù)庫:使用 Room 持久性庫將結(jié)構(gòu)化數(shù)據(jù)存儲在專用數(shù)據(jù)庫中。

下表匯總了這些選項的特點:

內(nèi)容類型 訪問方法 所需權(quán)限 其他應(yīng)用是否可以訪問 卸載應(yīng)用是否移除
僅供您的應(yīng)用使用的文件 從內(nèi)部存儲空間訪問癌压,可以使用 getFilesDir() 或 getCacheDir() 方法仰泻;

從外部存儲空間訪問,可以使用 getExternalFilesDir() 或 getExternalCacheDir() 方法
從內(nèi)部存儲空間訪問不需要任何權(quán)限

如果應(yīng)用在搭載 Android 4.4(API 級別 19)或更高版本的設(shè)備上運行滩届,從外部存儲空間訪問不需要任何權(quán)限
如果文件存儲在內(nèi)部存儲空間中的目錄內(nèi)集侯,則不能訪問
如果文件存儲在外部存儲空間中的目錄內(nèi)被啼,則可以訪問
可共享的媒體文件(圖片、音頻文件棠枉、視頻) MediaStore API 在 Android 10(API 級別 29)或更高版本中浓体,訪問其他應(yīng)用的文件需要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 權(quán)限

在 Android 9(API 級別 28)或更低版本中,訪問所有文件均需要相關(guān)權(quán)限
是辈讶,但其他應(yīng)用需要 READ_EXTERNAL_STORAGE 權(quán)限
鍵值對 SharedPreferences
結(jié)構(gòu)化數(shù)據(jù) 數(shù)據(jù)庫 Room
外部共享存儲空間文件 通過路徑或者Uri訪問 需要 READ_EXTERNAL_STORAGE 或 WRITE_EXTERNAL_STORAGE 權(quán)限

android 11增加MANAGE_EXTERNAL_STORAGE權(quán)限

二命浴、不同存儲方式使用

2.1 應(yīng)用專屬存儲空間

內(nèi)部存儲:/data/data/packagename/
外部存儲:/sdcard/Android/data/packagename/

這兩個目錄本App無需申請訪問權(quán)限即可使用。使用方式也很簡單贱除,直接通過路徑訪問即可生闲。

  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 共享媒體文件

申請權(quán)限

Android 6.0 之前是無需申請動態(tài)權(quán)限的,在AndroidManifest.xml 里聲明存儲權(quán)限:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

Android 6.0 之后除了在AndroidManifest.xml 里聲明存儲權(quán)限月幌,還需要動態(tài)申請權(quán)限碍讯。

  //檢查權(quán)限,并返回需要申請的權(quán)限列表
  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;
  }

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

  //用戶作出選擇后扯躺,返回申請的結(jié)果
  @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, "存儲權(quán)限申請成功", Toast.LENGTH_SHORT).show();
          } else {
            Toast.makeText(MainActivity.this, "存儲權(quán)限申請失敗", Toast.LENGTH_SHORT).show();
          }
        }
      }
    }
  }

  //測試申請存儲權(quán)限
  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, "無需申請權(quán)限", Toast.LENGTH_SHORT).show();
    } else {
      requestPermission(activity, needRequestList.toArray(new String[needRequestList.size()]));
    }
  }

訪問方式

1. 通過路徑訪問

以圖片為例捉兴,假設(shè)圖片存儲在/sdcard/Pictures/test.jpg

  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構(gòu)造InputStream
      InputStream inputStream = contentResolver.openInputStream(contentUri);
      //通過InputStream獲取Bitmap
      return BitmapFactory.decodeStream(inputStream);
    }
    return null;
  }

適配Android 10

Android 10及以上版本無法直接通過路徑獲取到文件,即 BitmapFactory.decodeFile(imagePath) 無法正常使用录语,會提示沒有權(quán)限倍啥。只能通過 Uri 方式獲取到圖片資源。

2.3 SharedPreferences

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

Context context = getActivity();
//創(chuàng)建SharedPreferences
SharedPreferences sharedPref = context.getSharedPreferences(
            name, Context.MODE_PRIVATE);
    //通過editor 寫入數(shù)據(jù)
    SharedPreferences.Editor editor = sharedPref.edit();
    editor.putInt(key, value);
    editor.commit();
//讀取數(shù)據(jù)
int data = sharedPref.getInt(key, defaultValue);

2.4 數(shù)據(jù)庫

關(guān)于 Room 的基本使用可以參考我的另外一篇文章:Android Room 使用

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

除了應(yīng)用專屬空間之外的存儲空間。

申請權(quán)限

與共享媒體文件一樣失暂,都需要申請 WRITE_EXTERNAL_STORAGEREAD_EXTERNAL_STORAGE

訪問方式

1. 通過路徑訪問

與媒體文件一樣,直接構(gòu)造路徑進行訪問鳄虱。
通過 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/目錄下創(chuàng)建 myDir 的文件夾拙已。

2. 通過SAF訪問

Storage Access Framework 簡稱SAF:存儲訪問框架决记。相當于系統(tǒng)內(nèi)置了文件選擇器,通過它可以拿到想要訪問的文件信息倍踪。

 private void startSAF() {
    Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    //選擇圖片
    intent.setType("image/jpeg");
    //會跳轉(zhuǎn)到一個文件選擇器中
    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構(gòu)造輸入流
      InputStream fis = getContentResolver().openInputStream(uri);
      return BitmapFactory.decodeStream(fis);
    } catch (Exception e) {
      Log.e(TAG, "openUri: ", e);
    }
    return null;
  }

跳轉(zhuǎn)到系統(tǒng)內(nèi)置的文件選擇器:


在這里插入圖片描述

該方式不需要申請讀寫權(quán)限系宫,也可以訪問外部文件,但是無法直接獲取到外部文件的路徑建车,需要通過Uri進行轉(zhuǎn)換扩借。

適配Android 10

Android 10 開始增加了分區(qū)存儲功能,限制APP只能訪問應(yīng)用專屬存儲空間缤至,無法直接通過路徑訪問sdcard中的文件潮罪,只能使用SAF的方式。
可以避免各個APP無節(jié)操的一直往sdcard卡中寫入數(shù)據(jù)。而申請文件讀寫權(quán)限的提示語也做了修改嫉到。

Android 6 - Android 9:

在這里插入圖片描述

Android 10及以上版本:

在這里插入圖片描述

對比低版本沃暗,只允許訪問照片和媒體的內(nèi)容。

適配Android 11

Android 11 增加了一個所有文件訪問權(quán)限 MANAGE_EXTERNAL_STORAGE 何恶,該權(quán)限比文件讀寫權(quán)限更為嚴格孽锥,不是彈出框提示,而是需要跳轉(zhuǎn)到設(shè)置界面進行授權(quán)细层。如果授權(quán)該權(quán)限惜辑,就允許開發(fā)者使用路徑方式訪問外部存儲的所有文件。
AndroidManifest.xml 中新增以下權(quán)限:

    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>

代碼中動態(tài)申請權(quán)限:

  private void requestPermission() {
    //需要判斷版本
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
      // 先判斷有沒有權(quán)限
      if (Environment.isExternalStorageManager()) {
        //已經(jīng)授權(quán)
      } 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, "所有文件訪問權(quán)限授權(quán)成功", Toast.LENGTH_SHORT).show();
      } else {
        Toast.makeText(this, "所有文件訪問權(quán)限授權(quán)失敗", Toast.LENGTH_SHORT).show();
      }
    }
  }

授權(quán)方式如下圖:


在這里插入圖片描述
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末今艺,一起剝皮案震驚了整個濱河市韵丑,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌虚缎,老刑警劉巖撵彻,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異实牡,居然都是意外死亡陌僵,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門创坞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碗短,“玉大人,你說我怎么就攤上這事题涨≠怂” “怎么了?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵纲堵,是天一觀的道長巡雨。 經(jīng)常有香客問我,道長席函,這世上最難降的妖魔是什么铐望? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮茂附,結(jié)果婚禮上正蛙,老公的妹妹穿的比我還像新娘。我一直安慰自己营曼,他們只是感情好乒验,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著溶推,像睡著了一般徊件。 火紅的嫁衣襯著肌膚如雪奸攻。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天虱痕,我揣著相機與錄音睹耐,去河邊找鬼。 笑死部翘,一個胖子當著我的面吹牛硝训,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播新思,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼窖梁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了夹囚?” 一聲冷哼從身側(cè)響起纵刘,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎荸哟,沒想到半個月后假哎,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡鞍历,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年舵抹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片劣砍。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡惧蛹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出刑枝,到底是詐尸還是另有隱情香嗓,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布装畅,位于F島的核電站陶缺,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏洁灵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一掺出、第九天 我趴在偏房一處隱蔽的房頂上張望徽千。 院中可真熱鬧,春花似錦汤锨、人聲如沸双抽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽牍汹。三九已至铐维,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間慎菲,已是汗流浹背嫁蛇。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留露该,地道東北人睬棚。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像解幼,于是被迫代替她去往敵國和親抑党。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351

推薦閱讀更多精彩內(nèi)容