Android存儲目錄結構體系梳理

Android存儲

所有Android設備都有兩個存儲區域:“內部”存儲和“外部”存儲,這些名詞是Android早期產生。
一些設備會把內置存儲介質(通常爲Flash)劃分爲系統分區和擴展分區,即使沒有擴展sd卡,也會有“內部”存儲和“外部”存儲之分。
因此,內部存儲通常是指系統分區(/data所在分區),外部存儲通常指擴展分區,可以在內置Flash上,也可以是移動存儲介質;
image.png

本文名詞解釋

1、內部存儲:指系統分區(/data),以區分內置卡;
2、外部存儲:指系統分區之外的擴展分區;
3、內置卡:指內部存儲介質(通常爲Flash),不可移除;
4、外置卡:指擴展sd卡,可以隨意拔插;
5、主存儲(Primary存儲)分區:Google定義的默認外部存儲分區,即Environment.getExternalStorageDirectory()返回路徑所在分區;
而這個API的返回結果是由OEM廠商決定,可以隨意更改,不一定在內置卡上。
主存儲分區主要是針對支持多個外部存儲的設備而言;

Android應用可用存儲目錄

1、內部存儲應用私有目錄

通常爲/data/data/{package_name}/路徑,App私有,普通應用通常沒有權限訪問;sharepreference和database等數據也存儲在該目錄下。
###Android SDK提供的API
Context.getFilesDir(): 返回/data/data/{package_name}/files目錄,存儲app相關私有數據
Context.getCacheDir(): 返回/data/data/{package_name}/cache目錄,存儲app相關緩存數據,在存儲空間不足時,可能會被系統自動清除
讀寫權限相關:所有Android版本都不需要申請讀寫權限,app可以直接訪問。
image.png

2、外部存儲應用私有目錄

通常是指外部存儲分區上Android/data/{package_name}/路徑下的files或cache目錄;
Android SDK提供的API
Context.getExternalFilesDir(String): 返回外部Primary存儲Android/data/{package_name}/files目錄,存儲app相關數據;
Context.getExternalCacheDir(): 返回外部Primary存儲Android/data/{package_name}/cache目錄,存儲app相關緩存數據,在存儲空間不足時,可能會被系統自動清除

Android 4.4新增
Context.getExternalFilesDirs(String): 返回所有外部存儲Android/data/{package_name}/files目錄,數組形式

Context.getExternalCacheDirs(): 返回所有外部存儲Android/data/{package_name}/cache目錄,數組形式
Android 5.0新增
Context.getExternalMediaDirs(): 返回所有外部存儲上的媒體文件存儲目錄,這些文件可以被MediaStore掃描到
讀寫權限相關:Android 4.4以下需要申請權限,4.4以上不再需要申請權限
image.png

Note:應用被卸載時,該目錄下的所有數據都會被清除。

其他應用申請了WRITE_EXTERNAL_STORAGE權限,也能夠讀寫該目錄。

特例華爲某些機型(榮耀X2、榮耀7)修改默認存儲爲外置卡時,如果應用沒有申請WRITE_EXTERNAL_STORAGE權限,此時外置卡的該目錄將不可寫

3、外部存儲公用路徑

指外部存儲上非應用私有目錄的普通目錄,需要申請權限;
Android SDK提供的API
Environment.getExternalStorageDirectory(): 返回外部Primary存儲的根目錄
Environment.getExternalStoragePublicDirectory(String): 返回外部Primary存儲上的Picture、Music、DCIM等公用路徑
讀寫權限相關:所有Android版本都要申請權限
image.png
Note: Android 4.4權限WRITE_EXTERNAL_STORAGE變更
在Android 4.4以下,WRITE_EXTERNAL_STORAGE權限對所有的外部存儲分區(內置卡和外置卡)都有效;
Android 4.4之後,WRITE_EXTERNAL_STORAGE權限只對內置卡有效,對外置sd卡無效,應用將沒有權限寫外置卡的普通目錄;
但是某些廠商可以通過定製ROM或修改默認存儲開放外置卡的寫權限。

Android外部存儲權限

1、READ_EXTERNAL_STORAGE 讀外部存儲權限,Android 4.1新增
2、WRITE_EXTERNAL_STORAGE 寫外部存儲權限,一直都有;
3、WRITE_MEDIA_STORAGE 寫外置sd卡權限,4.0-4.3存在,4.4該權限被回收,只授予系統級app,需要系統簽名。
Note: WRITE_EXTERNAL_STORAGE權限隱式包含了READ_EXTERNAL_STORAGE權限。

存儲權限的版本演變

1、Android 4.1以下版本沒有READ_EXTERNAL_STORAGE權限,外部存儲是全局可讀的;4.1-4.3版本,用戶可以通過開發者選項開啓對SD卡的讀保護,最好聲明讀權限;
從Android 4.4版本開始,系統強制對外部存儲的讀權限進行校驗;

2、WRITE_EXTERNAL_STORAGE權限一直都有,Android 4.4以下版本對外部存儲所有目錄都有效,Android 4.4以後,該權限只對內置卡有效,對外置sd卡無效;
3、Android 6.0的動態權限,READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE都隸屬於STORAGE權限組;
當動態申請權限時,用戶授權之後,在Manifest中聲明過的同組的另一個權限自動獲取。

(1) Manifest中只聲明READ_EXTERNAL_STORAGE權限,動態申請權限用戶授權之後,只能獲取READ_EXTERNAL_STORAGE權限;(因爲WRITE_EXTERNAL_STORAGE權限沒有在Manifest中聲明)

(2) Manifest中只聲明WRITE_EXTERNAL_STORAGE權限,動態申請權限用戶授權之後,同時獲取READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE權限;(因爲WRITE_EXTERNAL_STORAGE隱式包含READ_EXTERNAL_STORAGE權限的聲明)

(3) Manifest中同時聲明READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE權限,結果同(2)。
因此,在適配Android 6.0時,需要存儲數據到外部存儲根目錄時,一定記得檢查權限和申請權限;
Android 4.4(Kitkat)之後,外置sd卡根目錄的寫權限被收緊,應用只能寫自己的私有目錄。
同時Google引入了存儲訪問框架SAF,讓用戶能夠在首選文檔存儲程序中方便地瀏覽並打開文件、圖像以及其他文件。
Android 5.0(Lollipop),google引入了Intent.ACTION_CREATE_DOCMENT_TREE可以向SAF框架申請目錄的臨時讀寫權限。
SAF框架的使用和集成請參考鏈接:https://developer.android.com/guide/topics/providers/document-provider.html

Android SDK API的侷限性

1、使用Context.getExternalFilesDir()或Environment.getExternalStorageDiretory()通常只能獲取一張卡的存儲路徑,兩者返回的是同一個存儲分區;

2、在某些機型上,這兩個系統API可能拿不到路徑,返回值爲null,出現空指針異常現象,如ZTEU817、ZTEU950,需要做空指針判斷或保護;

3、這兩個API在不同的機型上返回的路徑可能是內置卡,也可能是外置卡;

4、這兩個API在某些機型上插拔卡或修改默認存儲時返回的路徑會發生動態變化,比如未插擴展sd卡時,返回內置卡的路徑,插上擴展sd卡後,返回外置卡的路徑,如紅米1,、華爲X2。

5、Android 4.4上新增的Context.getExternalFilesDirs()方法可以獲取多張卡的路徑,但是在某些機型上會出現獲取路徑不全的現象,如華爲暢享5。

6、Environment.getExternalStorageDiretory()和Environment.geteExternalStoragePublicDirectory(String)返回SD卡根路徑下相關目錄,需要申請讀寫權限,特別是Android 6.0以上需要動態申請權限;
如果直接讀寫SD卡根目錄,而沒有檢查並確保獲取相應權限,應用會因權限問題拋出異常。
對SD卡進行文件IO操作時,應確保獲取了相應的權限,特別注意Android 6.0上的動態權限申請。

SD卡掃描方式

1、反射StorageManager和StorageVolume的隱藏API
2、讀取/proc/mounts或執行linux mount命令

反射方式原理

StorageManager類是一項系統服務,有3個隱藏API可以獲取存儲分區的路徑和狀態, 以下3個API在4.0上可用

  • StorageVolume[] getVolumeList(): 獲取所有的存儲分區

  • String[] getVolumePaths(): 獲取所有存儲分區的路徑

  • String getVolumeState(String mountPoint): 獲取存儲分區的狀態
    Note:getVolumePaths()和getVolumeState(String)在高版本中標記爲deprecated
    StorageVolume類是系統的一個隱藏類,標識了一個存儲分區,它提供了一系列相關API獲取該分區的信息,該類於4.0引入

  • long getMaxFileSize(): 獲取文件大小

  • String getPath(): 獲取分區路徑

  • File getPathFile(): 4.2+, 返回File類型路徑

  • String getState(): 4.4+, 返回分區的狀態(掛載還是卸載等)

  • boolean isEmulated(): 是否模擬出來的分區

  • boolean isRemovable(): 該分區是否可移除

  • boolean isPrimary(): 4.2+, 該分區是否是主分區

通過反射這兩個類的相關方法,基本上可以正常獲取到所有sd卡的存儲路徑。
Note:目前通過StorageVolume類的**isPrimary()**區分內置卡還是外置卡;機型測試結果表示,準確性較高,暫時發現只在機型ZTEV987上出現兼容性問題,該機型將外置SD卡分區的isPrimary()方法標記爲true。而isPrimary()方法由於是隱藏的,因此與系統API Environment.getExternalStorageDiretory()結果並不保持一致;

機型測試結果表示,isRemovable()能夠區分內置和外置卡,由於沒有線上大面積測試驗證過,因此沒有以此作爲區分內置卡和外置卡的依據。

讀取mount表原理

mount是linux的一個shell命令,可以返回所有掛載分區的信息,需要從一堆的掛載點中篩選出可能的sd卡存儲路徑。

mount表格式:
<設備名> <掛載點> <文件系統> <可讀寫,所屬用戶,所屬組等信息> <整數1> <整數2>

Android外部存儲掛載點特點:
第一張sd卡(通常稱爲主卡)的掛載點演變:

  • Android 2.3, 掛載到/mnt/sdcard,/sdcard軟連接指向它
  • Android 4.1, 掛載到/storage/sdcard0, /sdcard, /mnt/sdcard軟連接指向它
  • Android 4.2, 開始支持多用戶,掛載到/storage/emulated/0,同時掛載到/storage/emulated/legacy向下兼容,並建立三個軟連接/storage/sdcard0, /sdcard, /mnt/sdcard指向/storage/emulated/legacy
  • Android 4.4, 掛載到/mnt/shell/emulated/0, 建立軟連接/storage/emualted/legacy指向它

第二張sd卡的掛載點沒有標準,各廠商自行定義
可能掛載點:/mnt/external, /mnt/extSdCard, /mnt/sdcard/ext_sd等

問題:

  1. 返回的掛載信息很多,需要進行過濾篩選出有效的sd卡路徑;
  2. 很多掛載路徑可能是軟連接,需要標準化,如/mnt/sdcard—>/storage/emulated/0;
  3. 同一個設備可能會掛載到多個目錄下,需要過濾。

解決方案:

  1. 通過關鍵詞和可讀寫性過濾,多機型測試整理常見的關鍵詞;
  2. 通過File.geteCanonicalPath可以對路徑進行標準化,將軟連接轉換爲真實的路徑;
  3. 通過創建隱藏文件(指紋)對同一個設備的多個目錄進行過濾;

常見關鍵詞(逆向分析和測試經驗值)
設備名:/dev/block/vold, /dev/block/sd, /dev/sd, /dev/fuse, /dev/lfuse等
掛載點:emmc, storage, sdcard, external, ext_sd, extSdCard等
缺點:內置卡和外置卡的掛載順序是不確定的,無法準確區分內置卡和外置卡。

總結

  1. 反射StorageManager和StorageVolume的方法最爲靠譜,兼容性較好;
  2. 讀取mount表經過過濾和篩選也能正確的獲取所有存儲路徑,但無法區分內置卡和外置卡;
  3. 目前SD卡掃描方式採取兩種方式相互補充的形式,在反射調用出現異常的時候,再選用讀取mount表的方式。

android Q 以上新幺蛾子

image.png
Android 採用了沙箱模型,應用只能使用自己獨立的存儲空間,不能直接像過去一樣通過公共目錄方式,存儲文件。

開發者需要做相應的適配動作。

發佈了15 篇原創文章 · 獲贊 6 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章