Android 文件存儲

一、前言

最近在項目不是很忙的時候,回顧Android系統(tǒng)的文件存儲功能,結(jié)合自己的經(jīng)驗,也查閱了好多相關(guān)的文檔,發(fā)現(xiàn)了好多好文章,真的非常感謝,寫的挺好的。

一直以來,恐怕都有一大堆的問題困擾著Android初學(xué)者,比如說:

  • 內(nèi)存,內(nèi)部存儲,外部存儲,概念的區(qū)分?
  • 內(nèi)部存儲都包含哪些目錄?外部存儲又包含哪些?
  • 清除APP的緩存,都刪除了哪些文件目錄呢?
  • 清除APP的數(shù)據(jù),又刪除了哪些文件目錄呢?
  • 哪些文件目錄是APP私有的,哪些又是公共的目錄?
  • SharedPreferences、數(shù)據(jù)庫一般都存儲在什么目錄?

今天盡我所能的表述一下這些知識點,如有遺漏或者錯誤,請指正。

二、存儲分類

2.1 內(nèi)部存儲

內(nèi)部存儲空間是 App 私有的存儲數(shù)據(jù)的存儲空間,系統(tǒng)會阻止其他應(yīng)用對這部分數(shù)據(jù)的訪問 (除非有root權(quán)限),并且在 Android 10(API 級別 29)及更高版本中,系統(tǒng)會對這些位置進行加密。 內(nèi)部存儲空間的特性讓它很適合存儲只有 App 本身才能訪問的敏感數(shù)據(jù)。

內(nèi)部存儲空間可以通過 Context.getFileDir()Context.getCacheDir() 獲取到,主要路徑是:

Context.getFileDir() 獲取的路徑為:

  • data/data/packagename/files (部分手機廠商)
  • data/user/0/packagename/files (部分手機廠商)

Context.getCacheDir() 獲取的路徑為:

  • data/data/packagename/cache (部分手機廠商)
  • data/user/0/packagename/cache (部分手機廠商)

ps: 該目錄內(nèi)的文件在設(shè)備內(nèi)存不足時會優(yōu)先被刪除掉,所以存放在這里的文件是沒有任何保障的,可能會隨時丟掉。

最后附上一張截圖,此圖問內(nèi)部存儲目錄

2.2 外部存儲

外部存儲空間包括 App 私有目錄和公共目錄。

  • App 私有目錄: App 的私有目錄雖然名曰私有目錄,但是其他應(yīng)用可以通過絕對路徑訪問當(dāng)前目錄下的數(shù)據(jù),應(yīng)用卸載后也會隨之刪除。
  • 公共目錄:外部可以自由訪問,應(yīng)用刪除后這部分存儲的數(shù)據(jù)不會刪除。

2.2.1 APP私有目錄

// 可以通過以下函數(shù)獲取
Context.externalCacheDir
Context.externalCacheDirs
Context.getExternalFilesDir(String)
Context.getExternalFilesDirs(String)
Context.externalMediaDirs

得到的對應(yīng)目錄是:

externalCacheDir: /storage/emulated/0/Android/data/packagename/cache
externalCacheDirs: /storage/emulated/0/Android/data/packagename/cache
ExternalFilesDir: /storage/emulated/0/Android/data/packagename/files
ExternalFilesDirs: /storage/emulated/0/Android/data/packagename/files
externalMediaDirs: /storage/emulated/0/Android/media/packagename

附上一張外部存儲的私有目錄截圖:

可能會有人問這不是"/mnt/sdcard/Android/datapackagename/"嗎,和上述的 "/storage/emulated/0/Android/data/packagename/"目錄不同啊,后續(xù)會提到的。

2.2.2 外部公共目錄

不要被這里的“外部”這個詞弄糊涂了。最好將此目錄視為媒體/共享的存儲部分。它是一個文件系統(tǒng),可以保存相對大量的數(shù)據(jù),并且在所有應(yīng)用程序之間共享(不強制執(zhí)行權(quán)限)。傳統(tǒng)上這是一張 SD 卡,但它也可以作為設(shè)備中的內(nèi)置存儲實現(xiàn),與受保護的內(nèi)部存儲不同,并且可以作為文件系統(tǒng)安裝在計算機上。

在具有多個用戶的設(shè)備上(如 UserManager 所述),每個用戶都有自己的隔離共享存儲。應(yīng)用程序只能訪問它們正在運行的用戶的共享存儲。

獲取方式:

Environment.getExternalStorageState() // SD 卡狀態(tài)
Environment.getExternalStorageDirectory()
Environment.getExternalStoragePublicDirectory(String)

輸出內(nèi)容:

getExternalStorageState: mounted // 已掛載
getExternalStorageDirectory: /storage/emulated/0 
getExternalStoragePublicDirectory: /storage/emulated/0

根據(jù)google官方文檔提示,getExternalStorageDirectorygetExternalStoragePublicDirectory已經(jīng)被標記為棄用,可以使用 Context.getExternalFilesDir(String)MediaStoreIntent.ACTION_OPEN_DOCUMENT等替代方案,它們性能更好。

在上述的需要傳遞 String 參數(shù)的方法中,例如Context.getExternalFilesDir(String)getExternalStoragePublicDirectory(String),String 有以下幾個常量值:

  • DIRECTORY_MUSIC // 音樂
  • DIRECTORY_PODCASTS // 博客
  • DIRECTORY_RINGTONES // 鈴聲
  • DIRECTORY_ALARMS // 鬧鐘
  • DIRECTORY_NOTIFICATIONS // 通知
  • DIRECTORY_PICTURES // 圖片
  • DIRECTORY_MOVIES // 電影
  • DIRECTORY_DOWNLOADS // 下載
  • DIRECTORY_DCIM // 照片
  • DIRECTORY_DOCUMENTS // 文檔

附上一張外部公共目錄的截圖:

39.png

2.3 系統(tǒng)目錄

Environment 還提供了對一些系統(tǒng)目錄的訪問方法:
Environment.getRootDirectory()  // 系統(tǒng)分區(qū)的 root 路徑
Environment.getDataDirectory()  // 獲取用戶數(shù)據(jù)目錄的路徑
Environment.getDownloadCacheDirectory() // 獲取用戶緩存目錄的路徑

// 輸出為

getRootDirectory: /system
getDataDirectory: /data
getDownloadCacheDirectory: /data/cache

三、問題解答

3.1 內(nèi)存,內(nèi)部存儲,外部存儲,概念的區(qū)分?內(nèi)部存儲都包含哪些目錄?外部存儲又包含哪些?

1、內(nèi)存(Memory/RAM):

  • 內(nèi)存(Memory)是計算機的重要部件,也稱內(nèi)存儲器和主存儲器,擁有極高的讀寫速度,用于暫時存放CPU中的運算數(shù)據(jù),以及與硬盤等外部存儲器交換的數(shù)據(jù)。

  • 它是外存與CPU進行溝通的橋梁,計算機中所有程序的運行都在內(nèi)存中進行,內(nèi)存性能的強弱影響計算機整體發(fā)揮的水平。

  • 只要計算機開始運行,操作系統(tǒng)就會把需要運算的數(shù)據(jù)從內(nèi)存調(diào)到CPU中進行運算,當(dāng)運算完成,CPU將結(jié)果傳送出來。

2、內(nèi)部存儲

  • 注意內(nèi)部存儲不是內(nèi)存。

  • 內(nèi)部存儲位于系統(tǒng)中很特殊的一個位置,如果你想將文件存儲于內(nèi)部存儲中,那么文件默認只能被你的應(yīng)用訪問到,且一個應(yīng)用所創(chuàng)建的所有文件都在和應(yīng)用包名相同的目錄下。也就是說應(yīng)用創(chuàng)建于內(nèi)部存儲的文件,與這個應(yīng)用是關(guān)聯(lián)起來的。

  • 當(dāng)一個應(yīng)用卸載之后,內(nèi)部存儲中的這些文件也被刪除。

  • 從技術(shù)上來講,如果你在創(chuàng)建內(nèi)部存儲文件的時候?qū)⑽募傩栽O(shè)置成可讀,其他app能夠訪問自己應(yīng)用的數(shù)據(jù),前提是他知道你這個應(yīng)用的包名,如果一個文件的屬性是私有(private),那么即使知道包名其他應(yīng)用也無法訪問。

  • 內(nèi)部存儲空間十分有限,因而顯得可貴,另外,它也是系統(tǒng)本身和系統(tǒng)應(yīng)用程序主要的數(shù)據(jù)存儲所在地,一旦內(nèi)部存儲空間耗盡,手機也就無法使用了。所以對于內(nèi)部存儲空間,我們要盡量避免使用。Shared Preferences和SQLite數(shù)據(jù)庫都是存儲在內(nèi)部存儲空間上的。內(nèi)部存儲一般用Context來獲取和操作。

3、外部存儲

  • 最容易混淆的是外部存儲,因為老的Android系統(tǒng)的跟新的Android系統(tǒng)是有差別的,很多人去網(wǎng)上查找資料,看了一下以前的資料,又看了一下現(xiàn)在的資料,但是發(fā)現(xiàn)它們說法不一樣然后就困惑了。

  • 首先說一個大家普遍的概念:如果在pc機上是區(qū)分外部存儲和內(nèi)部存儲的話,那么電腦自帶的硬盤算是內(nèi)部存儲,U盤或者移動硬盤就是外部存儲了。 因此很多人帶著這樣的理解去看待安卓手機,把內(nèi)置存儲(機身存儲)當(dāng)做內(nèi)部存儲,而把擴展的SD卡當(dāng)做是外部存儲。

  • 這么認為確實沒錯,因為在4.4(API19)以前的手機上確實是這樣的,手機自身帶的存儲卡就是內(nèi)部存儲,而擴展的SD卡就是外部存儲。但是從4.4的系統(tǒng)開始,很多的中高端機器都將自己的機身存儲擴展到了8G以上,比如有的人的手機是16G的,有的人的手機是32G的,但是這個16G,32G是內(nèi)部存儲嗎,不是的?。?!,它們依然是外部存儲,也就是說4.4系統(tǒng)及以上的手機將機身存儲存儲(手機自身帶的存儲叫做機身存儲)在概念上分成了 內(nèi)部存儲internal外部存儲external兩部分。

  • 既然16G,32G是外部存儲,那有人又有疑惑了,那4.4系統(tǒng)及以上的手機要是插了SD卡呢,SD卡又是什么呢,如果SD卡也是外部存儲的話,那怎么區(qū)分機身存儲的外部存儲跟SD卡的外部存儲呢?對,SD卡也是外部存儲,那怎么區(qū)分呢,在4.4以后的系統(tǒng)中,API提供了這樣一個方法來遍歷手機的外部存儲路徑:

File[] files;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    files = getExternalFilesDirs(Environment.MEDIA_MOUNTED);
    for(File file:files){
        Log.e("main",file);
    }
}

如果你的手機插了SD卡的話,那么它打印的路徑就有兩條了,例如華為榮耀7插了SD卡,它的結(jié)果如下:

/storage/emulated/0/Android/data/packname/files/mounted
/storage/B3E4-1711/Android/data/packname/files/mounted

其中 /storage/emulated/0 目錄就是機身存儲的外部存儲路徑,而 /storage/B3E4-1711/ 就是SD卡的路徑,他們統(tǒng)稱為外部存儲,那么,訪問外部存儲的API方法有:

 (1)Environment.getExternalStorageDirectory().getAbsolutePath()
 (2)Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath()
 (3)getExternalFilesDir(“”).getAbsolutePath()
 (4)getExternalCacheDir().getAbsolutePath()

大家對Android的外部存儲會產(chǎn)生疑問,主要是現(xiàn)在很多的手機已經(jīng)從物理上看不到外部存儲了,以前的手機都有,就是那種黑色的內(nèi)存卡,8G,16G,32G的,可以像U盤一樣插拔,以前很流行,存儲空間不夠了,就去買個內(nèi)存卡(準確說是SD卡,說成內(nèi)存卡又會引起誤解)回來,后來的手機比如現(xiàn)在我用的華為榮耀7,廠家已經(jīng)把機身存儲擴展到了16G了,只是在存儲概念上了分為了內(nèi)部存儲(內(nèi)部internal)外部存儲(外部external),其實它們都集成在一起了。
當(dāng)然如果你覺得16G不夠用,那他支持通過插SD卡來擴充容量嗎?支持的,榮耀7為例,它是三合二卡槽。卡槽1:Nano SIM卡;卡槽2:Nano SIM卡或Micro SD卡。默認卡槽1為4G主卡,可以在設(shè)置中更改4G主卡卡槽;不支持熱插拔,插拔卡托后需重啟手機。這樣插入的SD卡也屬于外部存儲。

  • 所以手機的外部存儲可能包含兩部分,一是機身存儲的外部存儲部分,還有一個是SD卡部分
  • 那么外部存儲的目錄在哪呢,在storage文件夾中有一個 sdcard文件夾,這個文件夾中的文件又分為兩類,一類是公有目錄,還有一類是私有目錄,其中的公有目錄有九大類,比如DCIMDOWNLOAD等這種系統(tǒng)為我們創(chuàng)建的文件夾,私有目錄就是Android這個文件夾,這個文件夾打開之后里邊有一個data文件夾,打開這個data文件夾,里邊有許多包名組成的文件夾。
3.2 清除APP的緩存,都刪除了哪些文件目錄呢?
  • 緩存是程序運行時的臨時存儲空間,它可以存放從網(wǎng)絡(luò)下載的臨時圖片,從用戶的角度出發(fā)清除緩存對用戶并沒有太大的影響,但是清除緩存后用戶再次使用該APP時,由于本地緩存已經(jīng)被清理,所有的數(shù)據(jù)需要重新從網(wǎng)絡(luò)上獲取。

  • 很多人簡單的認為,清除緩存數(shù)據(jù)時清除的肯定就是cache下的數(shù)據(jù),但是事實卻不是這樣的。我們知道應(yīng)用程序在運行過程中需要經(jīng)過很多過程,比如讀入程序,計算,輸入輸出等等,這些過程中肯定會產(chǎn)生很多的數(shù)據(jù),它們在內(nèi)存中,以供程序運行時調(diào)用。所以清除緩存清除的是APP運行過程中所產(chǎn)生的臨時數(shù)據(jù)。

  • 不過為了保證在清除緩存的時候,能夠正常清除與應(yīng)用相關(guān)的緩存,我們一般將緩存文件存放在getCacheDir()或者 getExternalCacheDir()路徑下。

3.3 清除APP的數(shù)據(jù),又刪除了哪些文件目錄呢?

清除數(shù)據(jù) 清除的是保存在app中所有數(shù)據(jù),就是上面提到的位于packagename下面的所有文件,內(nèi)部存儲(/data/data/packagename/)和外部存儲(/storage/emulated/0/Android/data/packagename/), 包括cache,files,lib,shared_prefs等等。當(dāng)然除了SD卡上面的數(shù)據(jù),SD卡上面的數(shù)據(jù)當(dāng)app卸載之后還會存在的。

3.4 哪些文件目錄是APP私有的,哪些又是公共的目錄?
  • 內(nèi)部存儲空間, 通過 Context.getFileDir()或 Context.getCacheDir() 獲取到的目錄,絕對是APP私有的,其他進程是無法訪問的;

  • 外部存儲控件的私有目錄,比方說通過Context.externalCacheDirs獲取的緩存目錄,也是私有的,但是,其他進程可以通過路徑進行訪問,只不過無法通過Context進行訪問了;

  • 外部存儲控件的公共目錄,是純粹的共有的資源了,任意進程都可以進行訪問,比如Environment.getDataDirectory() 獲取用戶數(shù)據(jù)目錄的路徑。

3.5 SharedPreferences、數(shù)據(jù)庫一般都存儲在什么目錄?
  • 既然是涉及到APP的私有數(shù)據(jù),而且是數(shù)據(jù)庫這么重要的東西,那肯定是存儲在內(nèi)部存儲中比較安全了,所以,SharedPreferences存儲在 "/data/data/Android/packagename/shared_prefs/xxx.xml"文件中,數(shù)據(jù)庫存儲在 "/data/data/Android/packagename/databases/xxx.xml"文件中。
  • 如果真有向其他進程共享數(shù)據(jù)的情況,可以通過ContentProvider進行提供。
3.6 getFilesDir().getAbsolutePath()和getCacheDir().getAbsolutePath()有什么區(qū)別呢?

我們知道 getFilesDir()getCacheDir()是都獲取內(nèi)部存儲的相關(guān)目錄,只不過 getFilesDir() 獲取的是files目錄,getCacheDir() 獲取的是cache目錄,它們位于同一級目錄,只是為了用來存放不同類型的數(shù)據(jù)的,由文件名不難看出:cache 下存放緩存數(shù)據(jù),databases 下存放使用SQLite存儲的數(shù)據(jù),files 下存放普通數(shù)據(jù)(log數(shù)據(jù),json型數(shù)據(jù)等),shared_prefs下存放使用SharedPreference存放的數(shù)據(jù)。這些文件夾都是由系統(tǒng)創(chuàng)建的。APP被卸載或者刪除數(shù)據(jù)時,這些目錄下的文件都會被清除。

3.7 getFilesDir().getAbsolutePath()和getExternalFilesDir(“”).getAbsolutePath()有什么區(qū)別呢?

我們先看它們的路徑:

/data/user/0/packname/files

/storage/emulated/0/Android/data/packname/files

很顯然這兩個的區(qū)別是一個在內(nèi)部存儲里面,一個在外部存儲里面,這是它們的區(qū)別。它們的共同點呢,就是它們的路徑都帶有包名,表明是這個APP的專屬文件,這類文件應(yīng)該是隨著app卸載而一起被刪除的,并且我們在設(shè)置里面清除該應(yīng)用的數(shù)據(jù)時,這兩個文件夾下的數(shù)據(jù)都會被清除。

3.8 什么是APP專屬文件?

所謂專屬文件就是它是屬于某個具體的應(yīng)用的,他的文件路徑都帶有相應(yīng)的包名,當(dāng)APP卸載時,它們會隨應(yīng)用一起刪除,當(dāng)我們在設(shè)置里面手動清除某個應(yīng)用數(shù)據(jù)時(不是清除緩存),它們也會一起被清掉。Android使用這種專屬文件的目的就是為了方便文件管理,避免文件隨意存儲,顯得很亂,另一個目的就是為了當(dāng)應(yīng)用被卸載時不會留下很多垃圾文件。

3.9 既然內(nèi)部存儲與外部存儲都有APP專屬文件,那么我們該優(yōu)先使用哪個呢?
  • 內(nèi)部存儲與外部存儲都有APP專屬文件,我們該用哪個呢,很顯然應(yīng)該用外部存儲的,因為內(nèi)部存儲本身就比較小,而且已經(jīng)存儲了一些系統(tǒng)的文件,因此內(nèi)部存儲我們盡量不要去使用。

  • 但是當(dāng)手機沒有外部存儲時,我們還是得使用內(nèi)部存儲,一般程序員會做判斷是否有外部存儲,沒有再使用內(nèi)部存儲,代碼如下:

public static String getFilePath(Context context,String dir) {
    String directoryPath="";
    if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ) {//判斷外部存儲是否可用 
        directoryPath =context.getExternalFilesDir(dir).getAbsolutePath();
        }else{//沒外部存儲就使用內(nèi)部存儲  
        directoryPath=context.getFilesDir()+File.separator+dir;
        }
        File file = new File(directoryPath);
        if(!file.exists()){//判斷文件目錄是否存在
        file.mkdirs();
        }
    return directoryPath;
}
  • 還有種情況,我們要保存數(shù)據(jù)庫這種重要數(shù)據(jù),那么應(yīng)該也會選擇內(nèi)部存儲了。
3.10 /storage/sdcard,/sdcard,/mnt/sdcard,/storage/emulated/0之間的關(guān)系

簡單的說,是Android系統(tǒng)開發(fā)者為了兼容不同的版本,做出的結(jié)果。這些路徑都是同一個路徑的不同 ”指針“,指向的是同一個地方,只是不同Android版本的叫法不一樣。具體分析參考:https://blog.csdn.net/u010937230/article/details/73303034

最后附上參考的文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容