<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          帶你徹底搞懂 Android 存儲!(建議收藏)

          共 18167字,需瀏覽 37分鐘

           ·

          2021-08-28 00:18

          微信改了推動機(jī)制,真愛請星標(biāo)本公號
          公眾號回復(fù)加入BATcoder技術(shù)群BAT

          作者:小魚人愛編程  
          鏈接:https://www.jianshu.com/p/93c9f5e2d2a7

          在持久化數(shù)據(jù)的時候,一般都是選擇存入到文件里,本篇將著重分析Android 存儲相關(guān)的知識, 通過本篇文章,你將了解到:

          1. 存儲劃分
          2. 內(nèi)部存儲
          3. 外部存儲
          4. 易混淆點(diǎn)說明

          1. 存儲劃分

          1.1 Android 4.4 之前

          在Android 4.4 之前,由于硬件發(fā)展受限,手機(jī)自身的存儲空間有限,需要通過外置SD卡來擴(kuò)展存儲空間。

          如上圖,手機(jī)自身的存儲空間,稱之為機(jī)身存儲,在Android 4.4 之前作為內(nèi)部存儲使用。當(dāng)然內(nèi)部存儲空間一般是不夠用的,所以需要通過插入外置SD卡來擴(kuò)充存儲空間,這當(dāng)做外部存儲。

          1.2 Android 4.4之后

          在Android 4.4 之后(含),手機(jī)機(jī)身存儲擴(kuò)大了:

          如上圖,機(jī)身存儲劃分為兩部分:內(nèi)部存儲外部存儲

          當(dāng)然,依然可以插入SD卡來擴(kuò)充存儲空間,這部分的存儲空間稱為擴(kuò)展的外部存儲空間。只是現(xiàn)在機(jī)身存儲都比較大,很少插入SD卡了。

          接下來將以Android 4.4 之后的存儲劃分來分析具體的存儲方案。

          2. 內(nèi)部存儲

          2.1 存儲位置

          回想一下平時使用的持久化方案:

          • SharedPreferences:適用于存儲小文件
          • 數(shù)據(jù)庫:存儲結(jié)構(gòu)比較復(fù)雜的大文件

          以上這些文件都是默認(rèn)放在內(nèi)部存儲里。"/" 表示根目錄,內(nèi)部存儲里給每個應(yīng)用按照其包名各自劃分了目錄,假設(shè)App的包名為:com.fish.myapplication那么該文件在內(nèi)部存儲里的目錄為:/data/user/0/com.fish.myapplication/

          第一個"/"表示根目錄,其后每個"/"表示目錄分割符。"0" 表示是第一個用戶,后續(xù)添加了多用戶則生成相應(yīng)的用戶目錄:

          如上圖,新增了兩個用戶,生成的目錄分別是:"11"、"12"。目前來說,很少開啟多用戶的。一般來說,adb shell 里是沒有權(quán)限查看/data目錄的。若要查看內(nèi)部存儲,通常是通過 Android Studio側(cè)邊欄Device File Explorer選擇對應(yīng)的目標(biāo)設(shè)備查看。

          同樣的,如果包名為:com.fish.myapplication,則對應(yīng)的內(nèi)部存儲目錄為:/data/data/com.fish.myapplication//data/user/0/com.fish.myapplication/ 會將值轉(zhuǎn)換到/data/data/com.fish.myapplication/路徑下。每個App的內(nèi)部存儲空間僅允許自己訪問(除非有更高的權(quán)限,如root),程序卸載后,該目錄也會被刪除。

          2.2 存儲內(nèi)容

          除了SharedPreferences、數(shù)據(jù)庫文件,內(nèi)部存儲還存放了哪些文件呢?為方便起見,只查看/data/data/目錄下的。

          剛開始有只有兩個空目錄。當(dāng)進(jìn)行寫入SharedPreferences,創(chuàng)建數(shù)據(jù)庫、寫入文件等操作后新增了幾個目錄:

          大致介紹一下以上目錄作用:

          目錄 用途
          cache 存放緩存文件
          code_cache 存放運(yùn)行時代碼優(yōu)化等產(chǎn)生的緩存
          databases 存放數(shù)據(jù)庫文件
          files 存放一般文件
          shared_prefs 存放 SharedPreferences 文件
          lib 存放App依賴的so庫 是軟鏈接,指向/data/app/ 某個子目錄下

          2.3 訪問方式

          既然知道了各類文件存儲的目錄,那么如何讀寫這些文件呢?我們知道在Java 的世界里,操作文件有兩種方式:字符流和字節(jié)流

          以字節(jié)流為為例,一個簡單的讀取寫入文件Demo:

              //寫入文件
              private void writeFile(String filePath) {
                  if (TextUtils.isEmpty(filePath))
                      return;

                  try {
                      File file = new File(filePath);
                      FileOutputStream fileOutputStream = new FileOutputStream(file);
                      BufferedOutputStream bos = new BufferedOutputStream(fileOutputStream);
                      String writeContent = "hello world\n";
                      bos.write(writeContent.getBytes());
                      bos.flush();
                      bos.close();

                  } catch (Exception e) {

                  }
              }

              //從文件讀取
              private void readFile(String filePath) {
                  if (TextUtils.isEmpty(filePath))
                      return;

                  try {
                      File file = new File(filePath);
                      FileInputStream fileInputStream = new FileInputStream(file);
                      BufferedInputStream bis = new BufferedInputStream(fileInputStream);
                      byte[] readContent = new byte[1024];
                      int readLen = 0;
                      while (readLen != -1) {
                          readLen = bis.read(readContent, 0, readContent.length);
                          if (readLen > 0) {
                              String content = new String(readContent);
                              Log.d("test""read content:" + content.substring(0, readLen));
                          }
                      }
                      fileInputStream.close();
                  } catch (Exception e) {

                  }
              }

          可以看出,通過 FileInputStream/FileOutputStream 構(gòu)造函數(shù)傳入 File 對象即可實(shí)現(xiàn)文件讀寫,而 File 對象的構(gòu)造依賴于文件的存放路徑,因此重點(diǎn)在于如何獲取文件的路徑。分別說明各個目錄下文件的讀寫:

          2.3.1 讀寫files目錄下文件

          #Context.java
          public abstract File getFilesDir()
          ;

          使用方式:

              private String getFilePath(Context context) {
                  //獲取files根目錄
                  File fileDir = context.getFilesDir();
                  //獲取文件
                  File myFile = new File(fileDir, "myFile");
                  return myFile.getAbsolutePath();
              }

          context.getFilesDir()的結(jié)果是返回files目錄:

          /data/user/0/com.fish.myapplication/files/

          拿到對應(yīng)文件的File對象后,構(gòu)造相應(yīng)的輸入輸出流即可實(shí)現(xiàn)對該文件的讀寫。可以看出,過程雖然簡單但是有點(diǎn)枯燥,因此Google將這些步驟封裝好了,直接返回對應(yīng)文件的 FileOutputStream/FileInputStream

          #Context.java
              public abstract FileInputStream openFileInput(String name)
                  throws FileNotFoundException
          ;

              public abstract FileOutputStream openFileOutput(String name, @FileMode int mode)
                  throws FileNotFoundException
          ;

          其中name 表示文件名,mode表示訪問權(quán)限。

          2.3.2 讀寫cache目錄下文件

          與讀取files目錄相似:

          #Context.java
          public abstract File getCacheDir()
          ;

          context.getCacheDir()的結(jié)果是返回cache目錄:

          /data/user/0/com.fish.myapplication/cache/

          2.3.3 讀寫shared_prefs目錄下文件

          SharedPreferences 提供了簡易的快速持久化數(shù)據(jù)的方案。

              private void testSP(String fileName, String key, String value) {
                  if (TextUtils.isEmpty(fileName) || TextUtils.isEmpty(key) || TextUtils.isEmpty(value))
                      return;

                  //構(gòu)造SP文件
                  SharedPreferences sp = getSharedPreferences(fileName, MODE_PRIVATE);

                  //寫入SP
                  sp.edit().putString(key, value).commit();

                  //讀取SP
                  String myValue = sp.getString(key, "");
              }

          其內(nèi)部也是使用了輸入輸出流,以寫入SP文件為例:

          #SharedPreferencesImpl.java
              private void writeToFile(MemoryCommitResult mcr, boolean isFromSyncCommit) 
          {
                  ...
                  //構(gòu)造輸出流
                  FileOutputStream str = createFileOutputStream(mFile);
                  XmlUtils.writeMapXml(mcr.mapToWriteToDisk, str);
                  FileUtils.sync(str);
                  str.close();
                  ...
              }
              

          2.3.4 讀寫數(shù)據(jù)庫目錄下文件

          創(chuàng)建數(shù)據(jù)庫:

          MyDatabaseHelper myDatabaseHelper = new MyDatabaseHelper(v.getContext(), "myDB"null10);

          myDB是數(shù)據(jù)庫文件名。打開數(shù)據(jù)庫的相應(yīng)表,即可讀寫數(shù)據(jù)。

          獲取數(shù)據(jù)庫文件路徑:

          #Context.java
          Context.public abstract File getDatabasePath(String name);

          獲取結(jié)果如下:

          /data/user/0/com.fish.myapplication/databases/myDB

          2.3.5 讀寫code_cache目錄下文件

          #Context.java API>=21
          public abstract File getCodeCacheDir();

          獲取結(jié)果如下:

          /data/user/0/com.fish.myapplication/code_cache/

          以上是分別列舉了各個子目錄/文件的獲取方式,如果想獲取:/data/user/0/com.fish.myapplication/,可通過:

          #Context.java
          public abstract File getDataDir()
          ;

          該方法需要 API>=24。

          3. 外部存儲

          外部存儲分為兩部分:自帶外部存儲和擴(kuò)展外部存儲(外置SD卡)

          3.1 自帶外部存儲存儲

          3.1.1 存儲位置

          自帶外部存儲的根目錄是:"/"。

          根目錄下幾個需要關(guān)注的目錄:

          • /data/
          • /sdcard/
          • /storage/

          其中/data/目錄前面已經(jīng)分析過。

          /sdcard/是軟鏈接,指向/storage/self/primary

          /storage/下有幾個目錄:

          /storage/self/primary/ 是軟鏈接,指向/storage/emulated/0/

          也就是說/sdcard/、/storage/self/primary/ 真正指向的是/storage/emulated/0/

          3.1.2 存儲內(nèi)容

          自帶外部存儲主要有以下內(nèi)容:

          如上圖所示,/sdcard/目錄下的子目錄看起來都比較眼熟。這些子目錄分為分為三部分:

          第一部分:共享存儲空間

          也就是所有App共享的部分,比如相冊、音樂、鈴聲、文檔等。共享存儲空間按文件類型又分為兩部分:

          1. 媒體文件
          目錄 用途
          DCIM/ 和 Pictures/ 存儲圖片
          DCIM/、Movies/ 和 Pictures 存儲視頻
          Alarms/、Audiobooks/、Music/、Notifications/、Podcasts/ 和 Ringtones/ 存儲音頻文件
          Download/ 下載的文件
          1. 文檔和其它文件
          目錄 用途
          Documents 存儲如.pdf類型等文件

          第二部分:App外部私有目錄

          目錄 用途
          Android/data/ 存儲各個App的外部私有目錄,與內(nèi)部存儲類似,命名方式是:Android/data/xx(xx指應(yīng)用的包名)。如:/sdcard/Android/data/com.fish.myapplication

          Android/data/--->存儲各個App的外部私有目錄 與內(nèi)部存儲類似,命名方式是:Android/data/xx------>xx指應(yīng)用的包名。如:/sdcard/Android/data/com.fish.myapplication

          第三部分:其它目錄

          比如各個App在/sdcard/目錄下創(chuàng)建的目錄,如支付寶創(chuàng)建的目錄:alipy/,微博創(chuàng)建的目錄:com.sina.weibo/,qq創(chuàng)建的目錄:com.tencent.mobileqq/等。

          3.1.3 訪問方式

          與訪問內(nèi)部存儲文件類似,外部存儲也可以通過構(gòu)造輸入輸出流訪問文件。

          讀寫共享存儲空間

          視頻、圖片等可能分散存儲在各個不同的目錄里,如果想要獲取所有的圖片地址,那么得需要遍歷不同的目錄尋找,效率顯而易見的低。Android 將視頻、圖片等信息存儲在數(shù)據(jù)庫里,每當(dāng)某個App想要訪問這些共享的媒體文件時只需要查找數(shù)據(jù)庫對應(yīng)的表,讀取符合條件的行,找出每個媒體的文件路徑等信息。

          App查詢共享存儲空間的媒體方式是:通過ContentProvider訪問。

          • 訪問媒體文件

          以查詢圖片為例:

              private void getImagePath(Context context) {
                  ContentResolver contentResolver = context.getContentResolver();
                  Cursor cursor = contentResolver.query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, nullnullnullnull);
                  while(cursor.moveToNext()) {
                      String imagePath = cursor.getString(cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA));
                  }
              }

          查詢到圖片的地址,當(dāng)然就可以展示圖片了。

          • 訪問文檔和其它文件Storage Access Framework 簡稱SAF:存儲訪問框架

          以查看.pdf文件為例:

              private void startSAF() {
                  Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
                  intent.addCategory(Intent.CATEGORY_OPENABLE);
                  intent.setType("application/pdf");
                  startActivityForResult(intent, 100);
              }

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

                  if (requestCode == 100) {
                      Uri uri = data.getData();
                  }
              }

          SAF實(shí)際上就是調(diào)用系統(tǒng)提供的選擇器,選中后在 onActivityResult(xx) 里接收結(jié)果,拿到Uri 后當(dāng)然就可以讀寫對應(yīng)的文件了。

          讀寫App外部私有目錄

          剛開始并沒有自己App的包名。

          調(diào)用如下方法后:

              private void testAppDir(Context context) {
                  //4個基本方法
                  File fileDir = context.getExternalFilesDir(null);
                  //API>=19
                  File[] fileList = context.getExternalFilesDirs(null);

                  File cacheDir = context.getExternalCacheDir();
                  //API>=19
                  File[] cacheList = context.getExternalCacheDirs();

                  //指定目錄,自動生成對應(yīng)的子目錄
                  File fileDir2 = context.getExternalFilesDir(Environment.DIRECTORY_DCIM);
              }

          再查看目錄樹:

          可以看出再/sdcard/Android/data/目錄下生成了com.fish.myapplication/目錄,該目錄下有兩個子目錄分別是:files/cache/。當(dāng)然也可以選擇創(chuàng)建其它目錄。

          App卸載的時候,兩者都會被清除。

          讀寫其它目錄

          只要拿到根目錄,就可以遍歷尋找其它子目錄/文件。

              private void testOtherDir(Context context) {
                  File rootDir = Environment.getExternalStorageDirectory();
              }

          返回的rootDir路徑:/storage/emulated/0/

          3.2 擴(kuò)展外部存儲(外置SD卡)

          3.2.1 存儲位置

          當(dāng)給設(shè)備插入SD卡后,查看其目錄:

          /sdcard/ 依然指向 /storage/self/primary,繼續(xù)來看/storage/:

          可以看出,多了sdcard1,軟鏈接指向了 /storage/77E4-07E7/

          3.2.2 存儲內(nèi)容

          取決于SD卡上裝了什么東西。

          3.2.3 訪問方式

          還記得上面獲取外部存儲-App私有目錄方式嗎?

          File[] fileList = context.getExternalFilesDirs(null);

          返回File對象數(shù)組,當(dāng)有多個外部存儲時候,存儲在數(shù)組里。

          返回的數(shù)組有兩個元素,一個是自帶外部存儲存儲,另一個是剛插入的SD卡。拿到路徑后,當(dāng)然就可以訪問相應(yīng)的文件了。

          4. 易混淆點(diǎn)說明

          以上分別闡述了內(nèi)部存儲、自帶外部存儲、擴(kuò)展外部存儲等,這幾者關(guān)系如下:

          其中比較容易混淆的是:內(nèi)部存儲與外部存儲里的App私有目錄,兩者命名風(fēng)格很像。

          4.1 不同點(diǎn)

          /data/data/com.fish.myapplication/ 位于內(nèi)部存儲,一般用于存儲容量較小的,私密性較強(qiáng)的文件。而/sdcard/Android/data/com.fish.myapplication/ 位于外部存儲,作為App私有目錄,一般用于存儲容量較大的文件,即使刪除了也不影響App正常功能。

          4.2 相同點(diǎn)

          1. 屬于App專屬,App自身訪問兩者無需任何權(quán)限。
          2. App卸載后,兩者皆被刪除。
          3. 兩者目錄下增加的文件最終會被統(tǒng)計到"設(shè)置->存儲和緩存"里。

          另外,常見的在設(shè)置里的"存儲與緩存"項:

          當(dāng)點(diǎn)擊"Clear cache" 時:

          • 內(nèi)部存儲/data/data/com.fish.myapplication/cache//data/data/com.fish.myapplication/code_cache/目錄會被清空
          • 外部存儲/sdcard/Android/data/com.fish.myapplication/cache/ 會被清空

          當(dāng)點(diǎn)擊"Clear storage" 時:

          • 內(nèi)部存儲/data/data/com.fish.myapplication/下除了lib/,其余子目錄皆被刪除
          • 外部存儲/sdcard/Android/data/com.fish.myapplication/被清空

          注:該功能慎用,因?yàn)闀h除用戶數(shù)據(jù)庫,SP文件等,相當(dāng)于重置了App




          ·················END·················

          推薦閱讀

          ? 耗時2年,Android進(jìn)階三部曲第三部《Android進(jìn)階指北》出版!

          ? 『BATcoder』做了多年安卓還沒編譯過源碼?一個視頻帶你玩轉(zhuǎn)!

          ? 『BATcoder』我去!安裝Ubuntu還有坑?

          ? 重生!進(jìn)階三部曲第一部《Android進(jìn)階之光》第2版 出版!

          BATcoder技術(shù)群,讓一部分人先進(jìn)大廠

          大家,我是劉望舒,騰訊TVP,著有三本業(yè)內(nèi)知名暢銷書,連續(xù)四年蟬聯(lián)電子工業(yè)出版社年度優(yōu)秀作者,谷歌開發(fā)者社區(qū)特邀講師,百度百科收錄的高級技術(shù)專家。

          前華為技術(shù)專家,現(xiàn)大廠技術(shù)負(fù)責(zé)人。

          想要加入 BATcoder技術(shù)群,公號回復(fù)BAT 即可。

          為了防止失聯(lián),歡迎關(guān)注我的小號

                    
            微信改了推送機(jī)制,真愛請星標(biāo)本公號
          瀏覽 67
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  一道免费大香蕉 | 国产精品久久毛片 | 一区在线播放 | 天天干天天爽天天玩 | 午夜福利视频性爱 |