徹底搞懂Android文件存儲---內部存儲,外部存儲以及各種存儲路徑解惑

前言:

對於任何一個應用來說,無論是PC端應用還是Android應用,存儲肯定是必不可少的。對於很多做Android開發的同學來說,可能認爲文件存儲很簡單,調用一些諸如getFilesDir,getExternalStorageDirectory方法行了,但是雖然說它們會調用相應的方法來實現簡單的數據存儲。但是他們未必就搞懂了他的數據到底存在了哪裏,以及他的數據是否存對了地方,或者是否做好了版本兼容。下面我將從這幾個地方來解答大家常見的困惑:
1、Android中內部存儲,外部存儲的概念
2、不同安卓版本下getDataDirectory,getFilesDir,getCacheDir,getDir,getExternalStorageDirectory,getExternalStoragePublicDirectory,getExternalFilesDir,getExternalCacheDir,getExternalCacheDir,getRootDirectory等方法的區別和聯繫
3、清除數據和清除緩存到底清除了什麼數據
4、/storage/sdcard,/sdcard,/mnt/sdcard,/storage/emulated/0之間的關係
5、一張圖看懂Ram,Rom,以及擴展存儲(TF卡)的區別;內部存儲,外部存儲的區別。

一、Android中內部存儲,外部存儲的概念

內部存儲
概念:注意內部存儲不是內存。內部存儲位於系統中很特殊的一個位置,如果你想將文件存儲於內部存儲中,那麼文件默認只能被你的應用訪問到,且一個應用所創建的所有文件都在和應用包名相同的目錄下。也就是說應用創建於內部存儲的文件,與這個應用是關聯起來的。當一個應用卸載之後,內部存儲中的這些文件也被刪除。從技術上來講如果你在創建內部存儲文件的時候將文件屬性設置成可讀,其他app能夠訪問自己應用的數據,前提是他知道你這個應用的包名,如果一個文件的屬性是私有(private),那麼即使知道包名其他應用也無法訪問。 內部存儲空間十分有限,因而顯得可貴,另外,它也是系統本身和系統應用程序主要的數據存儲所在地,一旦內部存儲空間耗盡,手機也就無法使用了。所以對於內部存儲空間,我們要儘量避免使用。Shared Preferences和SQLite數據庫都是存儲在內部存儲空間上的。內部存儲一般用Context來獲取和操作。
訪問內部存儲的API方法:
1、Environment.getDataDirectory()
2、getFilesDir().getAbsolutePath()
3、getCacheDir().getAbsolutePath()
4、getDir(“myFile”, MODE_PRIVATE).getAbsolutePath()
外部存儲
概念:最容易混淆的是外部存儲,因爲老的Android系統的跟新的Android系統是有差別的,很多人去網上查找資料,看了一下以前的資料,又看了一下現在的資料,但是發現它們說法不一樣然後就困惑了。首先說一個大家普遍的概念“如果在pc機上是區分外部存儲和內部存儲的話,那麼電腦自帶的硬盤算是內部存儲,U盤或者移動硬盤就是外部存儲了。”因此很多人帶着這樣的理解去看待安卓手機,把內置存儲(機身存儲)當做內部存儲,而把擴展的SD卡當做是外部存儲。這麼認爲確實沒錯,因爲在4.4(API19)以前的手機上確實是這樣的,手機自身帶的存儲卡就是內部存儲,而擴展的SD卡就是外部存儲。但是從4.4的系統開始,很多的中高端機器都將自己的機身存儲擴展到了8G以上,比如有的人的手機是16G的,有的人的手機是32G的,但是這個16G,32G是內部存儲嗎,不是的!!!,它們依然是外部存儲,也就是說4.4系統及以上的手機將機身存儲存儲(手機自身帶的存儲叫做機身存儲)在概念上分成了”內部存儲internal” 和”外部存儲external” 兩部分。既然16G,32G是外部存儲,那有人又有疑惑了,那4.4系統及以上的手機要是插了SD卡呢,SD卡又是什麼呢,如果SD卡也是外部存儲的話,那怎麼區分機身存儲的外部存儲跟SD卡的外部存儲呢?對,SD卡也是外部存儲,那怎麼區分呢,在4.4以後的系統中,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卡,它的結果如下:
/storage/emulated/0/Android/data/packname/files/mounted
/storage/B3E4-1711/Android/data/packname/files/mounted
其中/storage/emulated/0目錄就是機身存儲的外部存儲路徑
而/storage/B3E4-1711/就是SD卡的路徑
他們統稱爲外部存儲
訪問外部存儲的API方法:
1、Environment.getExternalStorageDirectory().getAbsolutePath()
2、Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath()
3、getExternalFilesDir(“”).getAbsolutePath()
4、getExternalCacheDir().getAbsolutePath()
大家對Android的外部存儲會產生疑問,主要是現在很多的手機已經從物理上看不到外部存儲了,以前的手機都有,就是那種黑色的內存卡,8G,16G,32G的,可以像U盤一樣插拔,以前很流行,存儲空間不夠了,就去買個內存卡(準確說是SD卡,說成內存卡又會引起誤解)回來,後來的手機比如現在我用的華爲榮耀7,廠家已經把機身存儲擴展到了16G了,只是在存儲概念上了分爲了內部存儲(內部internal)和外部存儲(外部external),其實它們都集成在一起了。當然如果你覺得16G不夠用,那他支持通過插SD卡來擴充容量嗎?支持的,榮耀7爲例,它是三合二卡槽。卡槽1:Nano SIM卡;卡槽2:Nano SIM卡或Micro SD卡。默認卡槽1爲4G主卡,可以在設置中更改4G主卡卡槽;不支持熱插拔,插拔卡託後需重啓手機。這樣插入的SD卡也屬於外部存儲。所以手機的外部存儲可能包含兩部分,一是機身存儲的外部存儲部分,還有一個是SD卡部分

二、不同Android版本下getDataDirectory,getFilesDir,getCacheDir,getDir,getExternalStorageDirectory,getExternalStoragePublicDirectory,getExternalFilesDir,getExternalCacheDir,getExternalCacheDir,getRootDirectory的區別和聯繫

上面這些方法,我們可能似曾相識,但是對於有些同學來說卻又很難分清出,主要還是不同的Android版本的問題。爲了方便大家理解,我先簡要介紹以上各個方法,爲方便大家理解我把這些方法的結果打印出來(以下的打印結果是基於榮耀7的(系統版本6.0):
1、Environment.getDataDirectory() = /data
這個方法是獲取內部存儲的根路徑
2、getFilesDir().getAbsolutePath() = /data/user/0/packname/files
這個方法是獲取某個應用在內部存儲中的files路徑
3、getCacheDir().getAbsolutePath() = /data/user/0/packname/cache
這個方法是獲取某個應用在內部存儲中的cache路徑
4、getDir(“myFile”, MODE_PRIVATE).getAbsolutePath() = /data/user/0/packname/app_myFile
這個方法是獲取某個應用在內部存儲中的自定義路徑
方法2,3,4的路徑中都帶有包名,說明他們是屬於某個應用
…………………………………………………………………………………………
5、Environment.getExternalStorageDirectory().getAbsolutePath() = /storage/emulated/0
這個方法是獲取外部存儲的根路徑
6、Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath() = /storage/emulated/0
這個方法是獲取外部存儲的根路徑
7、getExternalFilesDir(“”).getAbsolutePath() = /storage/emulated/0/Android/data/packname/files
這個方法是獲取某個應用在外部存儲中的files路徑
8、getExternalCacheDir().getAbsolutePath() = /storage/emulated/0/Android/data/packname/cache
這個方法是獲取某個應用在外部存儲中的cache路徑
注意:其中方法7和方法8如果在4.4以前的系統中getExternalFilesDir(“”)和getExternalCacheDir()將返回null,如果是4.4及以上的系統纔會返回上面的結果,也即4.4以前的系統沒插SD卡的話,就沒有外部存儲,它的SD卡就等於外部存儲;而4.4及以後的系統外部存儲包括兩部分,getExternalFilesDir(“”)和getExternalCacheDir()獲取的是機身存儲的外部存儲部分,也即4.4及以後的系統你不插SD卡,它也有外部存儲,既然getExternalFilesDir(“”)和getExternalCacheDir()獲取的是機身存儲的外部存儲部分,那麼怎麼獲取SD卡的存儲路徑呢,還是通過上面提到的getExternalFilesDirs(Environment.MEDIA_MOUNTED)方法來獲取了,不知道Android有沒有提供相關的API接口來獲取SD卡的存儲路徑,大家可以去查資料。又重複了上面的話,主要是提醒大家要注意不同的Android版本是有差別的,這個最坑了。
…………………………………………………………………………………………
Environment.getDownloadCacheDirectory() = /cache
Environment.getRootDirectory() = /system
這兩個方法沒什麼說的了,每個版本的android系統都一樣
…………………………………………………………………………………………
從上面我們很清楚的可以看到上面的方法可以分爲三類,我用橫線隔開了。第一類是位於根目錄/data下;還有一類是位於根目錄/storage下,可以看到調用它們的API方法都帶了一個External;另外一類不在/data下也不再/storage下,比如系統文件/system,或者緩存文件/cache。
/data目錄下的文件物理上存放在我們通常所說的內部存儲裏面
/storage目錄下的文件物理上存放在我們通常所說的外部存儲裏面
/system用於存放系統文件,/cache用於存放一些緩存文件,物理上它們也是存放在內部存儲裏面的
下面來看一下大家常見的疑問
疑問1、那getFilesDir().getAbsolutePath()和getCacheDir().getAbsolutePath()有什麼區別呢?
其實是沒有什麼區別的,我們可以看下面一張圖:
這裏寫圖片描述
getFilesDir獲取的是files目錄,getCacheDir獲取的是cache目錄,它們位於同一級目錄,只是爲了用來存放不同類型的數據的,由文件名不難看出:cache下存放緩存數據,databases下存放使用SQLite存儲的數據,files下存放普通數據(log數據,json型數據等),shared_prefs下存放使用SharedPreference存放的數據。這些文件夾都是由系統創建的。
疑問2、getFilesDir().getAbsolutePath()和getExternalFilesDir(“”).getAbsolutePath()有什麼區別呢?
我們先看它們的路徑:
/data/user/0/packname/files
/storage/emulated/0/Android/data/packname/files
很顯然這兩個的區別是一個在內部存儲裏面,一個在外部存儲裏面,這是它們的區別。它們的共同點呢,就是它們的路徑都帶有包名,表明是這個APP的專屬文件,這類文件應該是隨着app卸載而一起被刪除的,並且我們在設置裏面清除該應用的數據時,這兩個文件夾下的數據都會被清除。
疑問3、什麼是APP專屬文件?
上面疑問2我們提到了專屬文件,所謂專屬文件就是它是屬於某個具體的應用的,他的文件路徑都帶有相應的包名,當APP卸載時,它們會隨應用一起刪除,當我們在設置裏面手動清除某個應用數據時(不是清除緩存),它們也會一起被清掉。Android使用這種專屬文件的目的就是爲了方便文件管理,避免文件隨意存儲,顯得很亂,另一個目的就是爲了當應用被卸載時不會留下很多垃圾文件。
疑問4、既然內部存儲與外部存儲都有APP專屬文件,那麼我們該使用哪個呢?
內部存儲與外部存儲都有APP專屬文件,我們該用哪個呢,很顯然應該用外部存儲的,因爲內部存儲本身就比較小,而且已經存儲了一些系統的文件,因此內部存儲我們儘量不要去使用。但是當手機沒有外部存儲時,我們還是得使用內部存儲,一般程序員會做判斷是否有外部存儲,沒有再使用內部存儲,代碼如下:

public static String getFilePath(Context context,String dir) {
    String directoryPath="";
    if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ) {//判斷外部存儲是否可用 
        directoryPath =context.getExternalFilesDir(dir).getAbsolutePath();
        }else{//沒外部存儲就使用內部存儲  
        directoryPath=context.getFilesDir()+File.separator+dir;
        }
        File file = new File(directoryPath);
        if(!file.exists()){//判斷文件目錄是否存在
        file.mkdirs();
        }
    return directoryPath;
}

爲了讓大家更好地理解不同版本的Android系統的存儲差異我做了下列一份說明表:
比較的是我向getFilesDir().getAbsolutePath()路徑下和getExternalFilesDir(“”).getAbsolutePath()路徑各寫入19.48M數據前後的差別
表一、4.1.1系統,帶有SD卡 (真機)

存儲位置 獲取路徑的方法 容量(寫入前) 容量(寫入後) 備註
/data/data/packname/files getFilesDir() 1.59GB 1.57GB 內部存儲
/storage/sdcard0/Android
/data/packname/files
getExternal
StorageDirectory()
1.47GB 1.45GB 外部存儲(SD卡)

表二、4.1.1系統,不帶有SD卡 (真機)

存儲位置 獲取路徑的方法 容量(寫入前) 容量(寫入後) 備註
/data/data/packname/files getFilesDir() 1.59GB 1.47GB 內部存儲
路徑不存在 getExternal
StorageDirectory()
~~~ ~~~ 沒插SD卡

表三、4.2.1系統,帶有SD卡 (模擬器)

存儲位置 獲取路徑的方法 容量(寫入前) 容量(寫入後) 備註
/data/data/packname/files getFilesDir() 1.85GB 1.83GB 內部存儲
/mnt/sdcard/Android
/data/packname/files
getExternal
StorageDirectory()
98.42MB 78.93MB 外部存儲(SD卡)

表四、4.4.2系統,帶有SD卡 (真機)

存儲位置 獲取路徑的方法 容量(寫入前) 容量(寫入後) 備註
/data/data/packname/files getFilesDir() 2.22GB 2.18GB 內部存儲
/storage/emulated/0/Android
/data/packname/files
getExternal
StorageDirectory()
2.20GB 2.16GB 機身外部存儲
/storage/sdcard1 getExternalFilesDirs 1.47GB 1.47GB 外部存儲(SD卡
沒有向其寫數據,只是讀取)

表五、4.4.2系統,不帶有SD卡 (真機)

存儲位置 獲取路徑的方法 容量(寫入前) 容量(寫入後) 備註
/data/data/packname/files getFilesDir() 2.22GB 2.18GB 內部存儲
/storage/emulated/0/Android
/data/packname/files
getExternal
StorageDirectory()
2.20GB 2.16GB 機身外部存儲

表六、6.0.0系統,帶有SD卡 (真機)

存儲位置 獲取路徑的方法 容量(寫入前) 容量(寫入後) 備註
/data/user/0/packname/files getFilesDir() 11.94GB 11.90GB 內部存儲
/storage/emulated/0/Android
/data/packname/files
getExternal
StorageDirectory()
11.92GB 11.88GB 機身外部存儲
/storage/B3E4-1711 getExternalFilesDirs 1.47GB 1.47GB 外部存儲(SD卡)
沒有向其寫數據,只是讀取

表七、6.0.0系統,不帶有SD卡 (真機)

存儲位置 獲取路徑的方法 容量(寫入前) /storage/容量(寫入後) 備註
/data/user/0/packname/files getFilesDir() 11.93GB 11.89GB 內部存儲
/storage/emulated/0/Android
/data/packname/files
getExternal
StorageDirectory()
11.91GB 11.87GB 機身外部存儲

注:上述容量指的是該路徑所在根路徑的可用容量,比如/data/data/packname/files的容量是指/data的可用容量,/storage/sdcard0/Android/data/packname/files指的是/storage/sdcard0的可用容量,而一般在4.4及以上的系統中,我們很少操作SD

三、清除數據和清除緩存到底清除了什麼數據

這個很容易搞混,爲什麼呢?通過上面我們知道:
/data/user/0/packname/files它是用來存儲普通數據的
/data/user/0/packname/cache它是用來存儲緩存數據的
所以很多人就以爲我清除數據時清除的肯定就是files下的數據,而我清除緩存數據時清除的肯定就是cache下的數據,但是事實卻不是這樣的。正確應該是:
清除緩存:我們知道應用程序在運行過程中需要經過很多過程,比如讀入程序,計算,輸入輸出等等,這些過程中肯定會產生很多的數據,它們在內存中,以供程序運行時調用。所以清除緩存清除的是APP運行過程中所產生的臨時數據。
清除數據:清除數據纔是真正的刪除了我們保存在文件中的數據(永久性數據,如果不人爲刪除的話會一直保存在文件中)例如當我們在設置裏面清除了某個應用的數據,那麼/data/user/0/packname/和/storage/emulated/0/Android/data/packname/下的文件裏面的數據會全部刪除,包括cache,files,lib,shared_prefs等等。

四、/storage/sdcard,/sdcard,/mnt/sdcard,/storage/emulated/0之間的關係

從上面的表中我們可以發現,在4.1系統中,getExternalStorageDirectory方法獲取到的路徑爲/storage/sdcard0;4.2系統中getExternalStorageDirectory方法獲取到的路徑爲/mnt/sdcard,因爲4.2是模擬器打印的結果,如果是真機的話也是/storage/sdcard0;4.4的getExternalStorageDirectory方法獲取到的路徑爲/storage/emulated/0,它的SD卡存儲路徑爲/storage/sdcard1;6.0的getExternalStorageDirectory方法獲取到的路徑爲/storage/emulated/0,它的SD卡存儲路徑爲/storage/B3E4-1711;另外根據測試在4.0上getExternalStorageDirectory方法獲取到的路徑爲/mnt/sdcard。所以在真機上,getExternalStorageDirectory獲取到的路徑如下表所示:

系統版本 結果
4.0 /mnt/sdcard
4.1 /storage/sdcard0
4.2 /storage/sdcard0
4.4 /storage/emulated/0
6.0 /storage/emulated/0

要理解/storage/sdcard,/sdcard,/mnt/sdcard,/storage/emulated/0之間的關係,我們需要先要了解一下linux文件掛載的概念,關於掛載大家可以自行去百度。還有我們不明白爲什麼會有有/storage/sdcard,/sdcard,/mnt/sdcard,/storage/emulated/0這麼多目錄,讓人看起來眼花繚亂,要詳細瞭解請仔細看下面的文章,下面的文章是我摘自關於android的4.2的0文件夾的詳解
—- android 4.0 —-
在galaxy nexus(GN)手機上userdata分區很大,被掛在/data目錄,用戶的數據通常是放在sd卡上,然而gn是沒有sd卡的,所以google想了一個辦法,就是虛擬一個。
所以,在userdata分區下有個目錄叫media,是內置sd卡的數據存儲位置,使用fuse技術將/data/media虛擬成爲一個叫做/dev/fuse的設備,爲了讓程序能認出來,被同時掛載在 /mnt/sdcard 目錄,又爲了兼容以前的程序,做了一個快捷方式(linux系統裏叫軟連接) /sdcard 指向的是 /mnt/sdcard .
當然,這些都是4.0的做法。
—- android 4.1 —-
在4.1裏,同樣也會使用fuse技術,/dev/fuse 會被同時掛載到/storage/sdcard0 目錄,這個sdcard0表示第一個sd卡(如果有外置sd卡,那會多一個 /storage/sdcard1,比如我的xoom), /sdcard 軟連接會指向 /storage/sdcard0 ,此時/mnt/sdcard 也是個軟連接,會指向/storage/sdcard0。
如果你通過otg線接U盤,會被掛載到 /storage/usb0目錄,stickmount這個軟件爲了讓圖庫、快圖、mx player等軟件,能看到u盤裏的數據,又同時掛載到 /storage/sdcard0/usStorage/sda1.
也許你會問,爲什麼不是usb0,而是sda1,這是linux的對硬盤的命名方式,如果你的u盤有多個分區,就分別是sda1,sda2這樣一直排下去了。
—- android 4.2 —-
好了,我們開始說4.2系統。
谷歌是不是沒事幹啊,非要給android搞個多用戶,你想想啊,在中國,可能因爲經濟問題,家裏不是每人一個電腦,在美國,幾乎需要用電腦的人,都會自己有一臺或多臺,一臺電腦多人用的情況少之又少,這就是爲什麼叫PC了,顧名思義,個人電腦。像手機和平板這些東西,更加私人化了,很少公用了吧,我想在中國也是如此吧。
當然,谷歌也不完全是抽風,因爲他有更大的戰略部署,而且平板也的確有多人用的可能。
所以谷歌搞出來一個多用戶,那每個人的應用、數據、個性配置都要分開吧。 應用和個性配置好弄,想想啊,通過權限控制,每人只能看自己的應用就行了,桌面也可以用自己的。
那數據怎麼辦????
好吧,調整用戶數據的掛載結構。android 4.2,同樣也會使用fuse技術/dev/fuse 會被掛載到/storage/emulated/0 目錄,爲什麼是0呢,你還記得上邊的sdcard0吧,第一個的意思。(如果有第二個,應該就是/storage/emulated/1,我們的三兒子沒有外置sd卡,所以沒法驗證)
爲了兼容以前,同時掛載到 /storage/emulated/legacy (故名思議,傳統的),還建立三個軟連接 /storage/sdcard0 ,/sdcard,/mnt/sdcard ,都指向 /storage/emulated/legacy
很多同學可能不會認真看上面,這裏我就簡單總結一下:
1、其中sdcard/、mnt/sdcard、storage/sdcard0、storage/emulated/0、storage/emulated/legacy都是同一個路徑的不同”指針“,指向的是同一個地方,只是不同Android版本的叫法不一樣。
2、如果大家想了解每個版本的外部存儲路徑,同學們可以通過獲取getExternalStorageDirectory方法的打印結果進行對比

五、一張圖看懂Ram,Rom,以及擴展存儲(TF卡)的區別;內部存儲,外部存儲的區別。

這裏寫圖片描述
1、首先我們來弄清幾個概念,內存,內部存儲,外部存儲,機身存儲(內置存儲)。
1.1內存;我們在英文中稱作memory,內存是計算機中重要的部件之一,它是與CPU進行溝通的橋樑。計算機中所有程序的運行都是在內存中進行的,所以說它是用於計算機運行時的,它不是用來存儲數據的。
1.2內部存儲,外部存儲;內部存儲我們稱爲InternalStorage,外部我們稱爲ExternalStorage,這兩個概念來自於早期的Android智能機,4.4以前,內置存儲就是內部存儲,外置SD卡就是外置存儲。我們通過getDataDirectory就可以獲取內置存儲根路徑,通過getExternalStorageDirectory就可以獲取外置SD卡根路徑。4.4以後外部存儲就包含兩部分了,其中通過getExternalStorageDirectory獲取的是機身存儲的外部存儲,而外置SD卡我們則需要通過getExternalDirs遍歷來獲取了。
1.3機身存儲;機身存儲是指手機自身攜帶的存儲空間,出廠時就已經有了,4.4以前機身存儲就是內部存儲,4.4及以後機身存儲包含了內部存儲和外部存儲。
2、Ram,Rom,以及擴展存儲(TF卡)的概念。從圖中我們可以看到,一個手機裏面有內存,手機內置存儲,以及SD卡, 它們分別是Ram,Rom,以及TF卡,這三種卡的性能,材質及價格都不一樣,都有各自的用處。
3、內部存儲,外部存儲的概念。很多人對這個存在誤解,認爲機身存儲就是內存,而SD卡才叫外部存儲,這其實是不對的,不同的Android版本是有差別的,請看第1條。
最後附上我的例子:
http://download.csdn.net/download/u010937230/9930396

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