好好管理你應用的文件夾,別再亂用了

安卓碎片化的問題,由來已久,這次來看一下文件儲存碎片化的問題。到底要怎麼去正確選擇和管理文件存儲呢?

爲什麼要管理文件?

Android手機一直以來被人詬病越用越卡,越用存儲空間越少,經常有要靠各種清理app清理垃圾,到最後不得對手機進行雙清,原因除了硬件老化和Android的底層實現問題之外,開發者對文件管理的忽視製造出大量無法清理的“垃圾”也是造成手機卡慢的原因之一。

Android的開放性給了開發者巨大的自由度,但自由不是讓我們濫用權限和隨意開發的藉口,每一個開發者都應該注重細節,連曾經一片混亂的第三方推送都開始統一整合規範化了,如果你還在隨意開發,不如現在開始,注重細節,提高用戶的Android手機體驗?

Android閃存

總所周知,Android手機存儲分爲兩個部分:內部存儲外部存儲,內部存儲一般是手機自帶的存儲空間,外部存儲指外插SD卡提供的存儲空間;隨着手機發展,這兩個存儲的定義又有了一些些變化,新的手機不再有外插SD卡的概念,採取了內置閃存(eMMC、UFS等)的方式,所以內部存儲和外部存儲在新的Android手機上已經在同一個硬件上了。但爲了兼容舊設備和讓用戶得到更好的體驗,我們仍然需要管理好手機上內外存儲的使用。

關於文件存儲位置的api

做過文件相關管理的同學應該都曾經被android衆多的文件api搞得一片混亂過,現在來理一理.

我把應用操作的文件存儲位置分爲三個部分

  1. 應用內部存儲私有文件目錄
  2. 應用外部存儲私有文件目錄
  3. 公有目錄

我們有兩種api去獲取這三個部分的存儲位置,它們分別歸屬於Context和Environment。

Context

Context是應用的上下文,它用來獲取與應用相關的文件目錄,可以獲取應用私有和應用公有目錄,常用的api有(後面是所對應的路徑):

1\. Context#getCacheDir()                    /data/user/0/cn.appname.xxx/cache
2\. Context#getDir("spanner",MODE_PRIVATE)   /data/user/0/cn.appname.xxx/app_spanner
3\. Context#getFileDir()                     /data/user/0/cn.appname.xxx/files
3\. Context#getExternalCacheDir()            /storage/emulated/0/Android/data/cn.appname.xxx/cache
4\. Context#getExternalFilesDir(Environment.DIRECTORY_PICTURES)  /storage/emulated/0/Android/data/cn.appname.xxx/files/Pictures
   Context#getExternalFilesDir(null)        /storage/emulated/0/Android/data/cn.appname.xxx/files
5\. Context#getExternalMediaDirs()           /storage/emulated/0/Android/media/cn.appname.xxx

前兩個是應用內部存儲私有目錄,後面4個都是應用外部存儲私有文件目錄。 注意:/data/user/0/ 等同於 /data/data/

Environment

Environment和應用無關,它用於獲取公有存儲位置的文件目錄,常用的api有:

1\. Environment#getExternalStorageDirectory()                /storage/emulated/0
2\. Environment#getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM)        /storage/emulated/0/DCIM
   Environment#getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)    /storage/emulated/0/Pictures
3\. Environment#getDataDirectory()                           /data
4\. Environment#getDownloadCacheDirectory()                  /data/cache
5\. Environment#getRootDirectory()                           /system

API的選用

到底什麼時候要用什麼api呢?

應用私有文件目錄

應用私有目錄由Context獲取控制,分爲內部存儲外部存儲,內部存儲不需要申請文件讀寫權限也能夠使用,外部存儲需要權限(getetExternalCacheDir() 和 getExternalFilesDir() 這兩個方法從4.4之後不再需要讀寫權限)。用戶對app進行數據清理或卸載可以清理外部存儲和內部存儲下的所有文件目錄。

內部存儲

內部存儲的文件夾其他應用和用戶無法直接訪問,可以用於存放敏感數據。

  • getCacheDir()

    • 專門用於存放緩存數據。
    • 用戶對app進行緩存清理的時候會清理緩存目錄cache的數據,手機空間不足的時候系統也會對緩存目錄內的數據進行清理。但儘管如此,開發者仍要管理好緩存數據特別是內部存儲的緩存,避免緩存數據過大。
  • getFileDir()

    • 可用於用於存放私有持久文件。
    • 非常適合用於存放app各種伴隨app運行週期所需要的文件數據,它既不會因爲手機存儲空間不足而被清理,也不會因卸載app而遺留數據垃圾,並且它是私有的。
  • getDir(String name,int mode)

    • 歸類存放私有文件。
    • 在內部私有目錄下會創建一個名爲app_name的文件夾,mode以前是可以設置文件夾私有(MODE_PRIVATE)和公有的(MODE_WORLD_READABLE、MODE_WORLD_WRITEABLE),但目前公有的mode都已經廢棄,意味着這個api創建的文件夾已經完全私有,不能再共享出去了。

外部存儲

在Android Q之前其他應用是可以訪問修改外部存儲的應用私有目錄的,這個要注意。

使用外部存儲之前一定要檢查外部存儲是否可用,因爲舊設備不一定會有外部存儲,新手機也不一定會給你讀寫權限,就算用戶不給你權限,你的app也要運行啊,不然就不用你的了。

    public static boolean isSDCardEnable() {
        return Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState());
    }
  • getExternalCacheDir()

    • 專門用於存放緩存數據。和內部存儲的getCacheDir()相似。
  • getExternalFilesDir(String type)

    • 歸類存放公有文件。
    • 如果type不爲null的話在外部私有目錄下創建返回一個名爲type的文件夾,爲null直接返回外部私有根目錄。如無特別需要,個人的做法是傳入Environment的DIRECTORY常量進行文件夾創建。
    • 如果看完這篇你還不會選用api,那就把你應用雜七雜八的東西都放進去吧,文件至少不用東一件西一件的,卸載之後也能夠被正確清理掉,不過也要控制好佔用的存儲空間。
  • getExternalMediaDirs()

    • 可存放共享媒體文件。
    • 這個是在Android 5.0加入的api,創建和獲取位於/sdcard/Android/media目錄下的應用目錄,該目錄下的文件能夠被其他應用訪問和被MediaStore查詢和獲取。但目前較少開發者在使用這個api。

公有目錄

獲取公有目錄要使用Environment的Api,它返回的目錄全都是共享的公有目錄。造成Android手機文件存儲混亂的罪魁禍首!爲數衆多的無責任開發者在這裏胡亂創建文件夾,亂起名、亂放文件,普通用戶根本無法判斷哪些文件夾、文件是有用的,卸載app之後留下龐大的無法清理的垃圾文件,導致手機空間不足。於是它們在Android Q被廢棄了,但是Q之前還是能好好使用的,我認爲要開始減少使用它們,更多地使用Context下的私有目錄API。

  • getExternalStorageDirectory()

    • 獲取外部存儲(SD卡)的根目錄。使用getExternalStoragePublicDirectory(String)進行替代即可。
  • getExternalStoragePublicDirectory(String type)

    • 使用頻率極高的api,返回在根目錄下的名爲type的文件夾,我把它分爲兩種用法:一種是傳入Environment的DIRECTORY常量再創建子目錄使用;一種是傳入appPackageName或者易被識別歸屬的名稱創建子目錄使用。前者會比較通用,內容可以被各種工具app搜索發現(包括微信);後者算是私用,可以存放不跟隨app生命的文件,即卸載後也可以保留。

    • Environment.DIRECTORY_DCIM是手機的相冊,這個文件夾都是系統相關的app在用,存放相機拍攝的圖片,手機截圖之類的,不推薦開發者使用這個文件夾,避免混亂。值得一提的是淘寶有在使用這個文件夾,用於保存它的商品分享截圖,這個位置的確可以避免被微信封殺~哈哈

    • Environment.DIRECTORY_PICTURES用於存放各種“正式的”圖片,強烈建議在這裏創建文件夾存放你想要被用戶發現的圖片,並且微信會掃描這個文件夾,讓你的圖片更容易分享。

    • Environment.DIRECTORY_DOWNLOADS可以用於存放app更新的apk等下載資源。

    • 其他幾個比較少用就不介紹了。

適配Android Q

上面提到Environment的兩個公有目錄常用API在Android Q被廢棄了,應用存儲功能沙箱化,文件存放到沙箱外面要使用 DocumentFile,共享媒體文件要使用MediaStore進行。

結尾

最後說一下幾個重要的事:

  • 獲取文件路徑這件事永遠不能寫死某個路徑,不存在SD卡怎麼辦呢?某個路徑無法使用了怎麼辦呢?所以管理文件的時候必須要有存儲策略。比如一個文件的保存地址獲取方法裏不能只有一個api,要保有兜底措施,如果我不能存在外部儲存,那我就存在內部,保證app的功能正常運行。

這次的分享到這裏,希望看完這篇文章之後能夠讓你更瞭解如何管理手機文件夾。

學習分享,共勉

題外話,我從事Android開發已經五年了,此前我指導過不少同行。但很少跟大家一起探討,正好最近我花了一個多月的時間整理出來一份包括不限於高級UI、性能優化、移動架構師、NDK、混合式開發(ReactNative+Weex)微信小程序、Flutter等全方面的Android進階實踐技術,今天暫且開放給有需要的人,若有關於此方面可以關注+點贊後領取,或者評論與我一起交流探討。

加羣:騰訊@Android高級架構(818520403)
免費獲取往期Android高級架構資料、源碼、筆記、視頻。高級UI、性能優化、架構師課程、NDK、混合式開發(ReactNative+Weex)微信小程序、Flutter全方面的Android進階實踐技術,羣內還有技術大牛一起討論交流解決問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章