我們在開發(fā)Android應(yīng)用的過程中,避免不了要用到數(shù)據(jù)持久化技術(shù),所謂的數(shù)據(jù)持久化就是將RAM中的瞬時(shí)數(shù)據(jù)保存到ROM中,保證在App退出或者手機(jī)關(guān)機(jī)后數(shù)據(jù)不會(huì)丟失。我們常用的數(shù)據(jù)持久化的方式有文件存儲(chǔ),數(shù)據(jù)庫存儲(chǔ),SharedPreference存儲(chǔ)等。在window中,當(dāng)我們存儲(chǔ)文件或者數(shù)據(jù)的時(shí)候,我們會(huì)選擇保存到磁盤的某個(gè)目錄,而在Android中有兩個(gè)位置可以讓應(yīng)用進(jìn)行數(shù)據(jù)持久化存儲(chǔ)—內(nèi)部存儲(chǔ)和外部存儲(chǔ)。在開發(fā)過程中可以根據(jù)不同的場景將數(shù)據(jù)存儲(chǔ)在不同的位置,那究竟什么是內(nèi)部存儲(chǔ)?什么是外部存儲(chǔ)?什么時(shí)候使用內(nèi)部存儲(chǔ)?什么時(shí)候使用外部存儲(chǔ)?他們之間區(qū)別又是什么?帶著這些問題,我們開啟今天的探索之旅。
內(nèi)部存儲(chǔ)
一說內(nèi)部存儲(chǔ),有人可能會(huì)和內(nèi)存混淆在一起,其實(shí)這兩個(gè)概念很好區(qū)分,內(nèi)部存儲(chǔ)是用于持久化存儲(chǔ)的,屬于ROM,手機(jī)關(guān)機(jī)或者退出App數(shù)據(jù)是不會(huì)丟失的,而內(nèi)存是RAM,退出App或者關(guān)機(jī)之后數(shù)據(jù)就會(huì)丟失。所以,內(nèi)部存儲(chǔ)不是內(nèi)存。所謂的內(nèi)部存儲(chǔ),其實(shí)是手機(jī)ROM上的一塊存儲(chǔ)區(qū)域,主要用于存儲(chǔ)系統(tǒng)以及應(yīng)用程序的數(shù)據(jù)。內(nèi)部存儲(chǔ)在Android系統(tǒng)對應(yīng)的根目錄是 /data/data/,這個(gè)目錄普通用戶是無權(quán)訪問的,用戶需要root權(quán)限才可以查看。不過我們可以通過Android Studio的View----Tool Windows----Device File Explorer工具來查看該目錄,內(nèi)部存儲(chǔ)目錄的大致結(jié)構(gòu)如下所示。

從上圖可以看到,/data/data目錄是按照應(yīng)用的包名來組織的,每個(gè)應(yīng)用都是屬于自己的內(nèi)部存儲(chǔ)目錄,而且目錄的名稱就是該應(yīng)用的包名,這個(gè)目錄是在安裝應(yīng)用的時(shí)候自動(dòng)創(chuàng)建的,當(dāng)應(yīng)用被卸載后,該目錄也會(huì)被系統(tǒng)自動(dòng)刪除。所以,如果你將數(shù)據(jù)存儲(chǔ)于內(nèi)部存儲(chǔ)中,其實(shí)就是把數(shù)據(jù)存儲(chǔ)到自己應(yīng)用包名對應(yīng)的內(nèi)部存儲(chǔ)目錄中。每個(gè)應(yīng)用的內(nèi)部存儲(chǔ)目錄都是私有的,也就是說內(nèi)部存儲(chǔ)目錄下的文件只能被應(yīng)用自己訪問到,其他應(yīng)用是沒有權(quán)限訪問的。應(yīng)用訪問自己的內(nèi)部存儲(chǔ)目錄時(shí)不需要申請任何權(quán)限。
一個(gè)應(yīng)用典型的內(nèi)部存儲(chǔ)目錄結(jié)構(gòu)如下所示。

相信很多人看到內(nèi)部存儲(chǔ)的目錄結(jié)構(gòu),都有似曾相識(shí)的感覺,沒錯(cuò)我們平常經(jīng)常和內(nèi)部存儲(chǔ)打交道,只不過我們不知道罷了,下面我們來看下內(nèi)部存儲(chǔ)目錄下各個(gè)子目錄的作用。
- app_webview:主要用于存儲(chǔ)webview加載過程中的數(shù)據(jù),例如Cookie,LocalStorage等。
- cache:主要用于存儲(chǔ)使用應(yīng)用過程中產(chǎn)生的緩存數(shù)據(jù)。
- databases:主要用于存儲(chǔ)數(shù)據(jù)庫類型的數(shù)據(jù)。我們平常創(chuàng)建的數(shù)據(jù)庫文件就是存儲(chǔ)在這里。
- files:可以在該目錄下存儲(chǔ)配置文件,敏感數(shù)據(jù)等。
- shared_prefs:用于存儲(chǔ)SharedPreference文件。我們使用SharedPreference的時(shí)候只指定了文件名,并沒有指定存儲(chǔ)路徑,其實(shí)SP的文件就是保存到了這個(gè)目錄下。
那么有哪些API可以獲取到內(nèi)部存儲(chǔ)目錄呢,我們主要是使用Context類提供的接口來訪問內(nèi)部存儲(chǔ)目錄。
1.getDataDir() //獲取的目錄是/data/user/0/package_name,即應(yīng)用內(nèi)部存儲(chǔ)的根目錄
2.getFilesDir() //獲取的目錄是/data/user/0/package_name/files,即應(yīng)用內(nèi)部存儲(chǔ)的files目錄
3.getCacheDir() //獲取的目錄是/data/user/0/package_name/cache,即應(yīng)用內(nèi)部存儲(chǔ)的cache目錄
4.getDir(String name, int mode) //獲取的目錄是/data/user/0/package_name/app_name,如果該目錄不存在,系統(tǒng)會(huì)自動(dòng)創(chuàng)建該目錄。
獲取到對應(yīng)的目錄后,我們就可以對目錄下的文件進(jìn)行讀寫。細(xì)心的同學(xué)可能會(huì)發(fā)現(xiàn)代碼中獲取的內(nèi)部存儲(chǔ)根目錄是 /data/user/0,并不是前面提到的/data/data,這是怎么回事呢?因?yàn)樵贏ndroid4.2以后增加了多用戶的功能,為了適應(yīng)多用戶的功能,原來的/data/data/相當(dāng)于直接鏈接到當(dāng)前用戶文件夾的,變成了/data/user/0/,所以我們代碼中打印出來的路徑是/data/user/0,而不是/data/data,說白了/data/data和/data/user/0/是一個(gè)東西。
內(nèi)部存儲(chǔ)空間容量有限,如果內(nèi)部存儲(chǔ)空間被用完,系統(tǒng)會(huì)報(bào)內(nèi)存不足。所以,不要把所有的數(shù)據(jù)都放到內(nèi)部存儲(chǔ)中。在開發(fā)應(yīng)用過程中,我們可以把較敏感的應(yīng)用數(shù)據(jù)放在內(nèi)部存儲(chǔ)中,而其他的數(shù)據(jù)可以放在外部存儲(chǔ)中。那外部存儲(chǔ)又是什么呢?下面我們接著來學(xué)習(xí)外部存儲(chǔ)。
外部存儲(chǔ)
我們知道內(nèi)部存儲(chǔ)中的數(shù)據(jù)對應(yīng)用來說是私密的,用戶和其他應(yīng)用都沒有訪問權(quán)限,而外部存儲(chǔ)中的數(shù)據(jù)是可以被其他應(yīng)用或用戶訪問甚至刪除的,用戶可以通過USB方式和PC之間交互外部存儲(chǔ)中的數(shù)據(jù)。我們平常在Android手機(jī)的文件管理工具下看到的目錄其實(shí)就是外部存儲(chǔ)。在Android4.4以前,外部存儲(chǔ)就是指SD卡,手機(jī)自帶的存儲(chǔ)就是內(nèi)部存儲(chǔ);但是在Android4.4以后,隨著手機(jī)機(jī)身存儲(chǔ)越來越大,手機(jī)的機(jī)身存儲(chǔ)已經(jīng)可以滿足大多數(shù)用戶的需求,所以很多手機(jī)都不需要再安裝SD卡。此時(shí)外部存儲(chǔ)和內(nèi)部存儲(chǔ)都位于手機(jī)機(jī)身存儲(chǔ)上,他們只是同一個(gè)存儲(chǔ)介質(zhì)上的不同存儲(chǔ)區(qū)域。但是很多手機(jī)還是保留了SD卡插槽,方便用戶自行拓展。如果手機(jī)安裝了SD卡,那么很顯然SD卡目錄也屬于外部存儲(chǔ)目錄。這時(shí)手機(jī)都有了兩個(gè)外部存儲(chǔ)空間,一個(gè)位于手機(jī)機(jī)身存儲(chǔ)上,一個(gè)位于SD卡上。但是隨著機(jī)身存儲(chǔ)越累越大,SD卡一般可能只適用于轉(zhuǎn)移文件,對于一般應(yīng)用來說應(yīng)該也不會(huì)把數(shù)據(jù)寫到外置的SD卡上了,所以這里主要以機(jī)身存儲(chǔ)為例來分析外部存儲(chǔ)。
和內(nèi)部存儲(chǔ)不同的是,外部存儲(chǔ)根據(jù)存儲(chǔ)特點(diǎn)不同分為兩種類型:外部私有存儲(chǔ)和外部共有存儲(chǔ)。先來看外部私有存儲(chǔ)。
外部私有存儲(chǔ)目錄
通常來說,應(yīng)用涉及到的持久化數(shù)據(jù)一般分為兩類:應(yīng)用相關(guān)數(shù)據(jù)和應(yīng)用無關(guān)數(shù)據(jù)。前者是指應(yīng)用使用的數(shù)據(jù)信息,比如一些配置信息,調(diào)試信息,緩存文件等。當(dāng)應(yīng)用被卸載,這些信息也應(yīng)該被隨之刪除,避免占用不必要的存儲(chǔ)空間。例如下面兩種場景。
- 在用戶使用應(yīng)用過程中,產(chǎn)生的文件,圖片,視頻,音頻等數(shù)據(jù),這些數(shù)據(jù)不太敏感但是占用空間比較大,卸載App時(shí)不希望這些數(shù)據(jù)繼續(xù)保留在用戶手機(jī)中。
- 當(dāng)應(yīng)用發(fā)生閃退時(shí),希望把一些閃退信息保存下來,讓用戶獲取閃退信息文件后通過特定渠道發(fā)送給開發(fā)人員進(jìn)行問題定位。同樣的,這些信息在卸載App后也不希望繼續(xù)留在用戶手機(jī)中。
對于問題一,我們可以直接把數(shù)據(jù)存儲(chǔ)在內(nèi)部存儲(chǔ)中,但是考慮到內(nèi)部存儲(chǔ)空間有限,把這些數(shù)據(jù)存儲(chǔ)到內(nèi)部存儲(chǔ)會(huì)浪費(fèi)內(nèi)部存儲(chǔ)的空間。對于問題二,普通用戶(指沒有root權(quán)限的用戶)無法直接查看其中的文件,把數(shù)據(jù)直接存儲(chǔ)在內(nèi)部存儲(chǔ)中是行不通的。這些數(shù)據(jù)有一個(gè)共同點(diǎn)就是他們的生命周期和應(yīng)用是一致的,而且不太適合于放在內(nèi)部存儲(chǔ)中。為了存儲(chǔ)這種類型的數(shù)據(jù),Android規(guī)定來一個(gè)專門的存儲(chǔ)空間,這個(gè)空間被稱為外部私有存儲(chǔ)空間。外部私有存儲(chǔ)空間屬于外部存儲(chǔ),對于某個(gè)應(yīng)用來說,外部私有存儲(chǔ)的根目錄(這里暫時(shí)不考慮SD卡)是 /storage/emulated/0/Android/data/package_name,這個(gè)目錄有點(diǎn)類似于內(nèi)部存儲(chǔ)目錄,都是以包名來命名私有存儲(chǔ)空間的。外部私有存儲(chǔ)空間有以下特點(diǎn)
- 內(nèi)部私有存儲(chǔ)中的數(shù)據(jù)會(huì)隨著App的卸載一起刪除
- 僅僅安裝應(yīng)用不會(huì)在/storage/emulated/0/Android/data/目錄下生成該應(yīng)用的外部私有存儲(chǔ)目錄,只有在應(yīng)用中調(diào)用API訪問外部私有存儲(chǔ)目錄時(shí),才會(huì)創(chuàng)建以package_name命名的私有存儲(chǔ)目錄。
- App在訪問自己的外部私有存儲(chǔ)目錄時(shí)不需要任何權(quán)限
- 自 Android 7.0 開始,系統(tǒng)對外部存儲(chǔ)目錄中 應(yīng)用私有目錄的訪問權(quán)限進(jìn)一步限制。其他 App 無法通過 file:// 這種形式的 Uri 直接讀寫其他應(yīng)用的外部私有存儲(chǔ)目錄,而是需要通過 FileProvider 訪問。
在代碼中我們可以通過以下方式來獲取外部私有存儲(chǔ)目錄。
1.getExternalCacheDir()
/*獲取到的目錄是/storage/emulated/0/Android/data/package_name/cache,如果該目錄不存在,調(diào)用這個(gè)方法會(huì)自動(dòng)創(chuàng)建該目錄。*/
2.getExternalFilesDir(String type)
/* 1.如果type為"",那么獲取到的目錄是 /storage/emulated/0/Android/data/package_name/files
2.如果type不為空,則會(huì)在/storage/emulated/0/Android/data/package_name/files目錄下創(chuàng)建一個(gè)以傳入的type值為名稱的目錄,例如你將type設(shè)為了test,那么就會(huì)創(chuàng)建/storage/emulated/0/Android/data/package_name/files/test目錄,這個(gè)其實(shí)有點(diǎn)類似于內(nèi)部存儲(chǔ)getDir方法傳入的name參數(shù)。但是android官方推薦使用以下的type類型
public static String DIRECTORY_MUSIC = "Music";
public static String DIRECTORY_PODCASTS = "Podcasts";
public static String DIRECTORY_RINGTONES = "Ringtones";
public static String DIRECTORY_ALARMS = "Alarms";
public static String DIRECTORY_NOTIFICATIONS = "Notifications";
public static String DIRECTORY_PICTURES = "Pictures";
public static String DIRECTORY_MOVIES = "Movies";
public static String DIRECTORY_DOWNLOADS = "Download";
public static String DIRECTORY_DCIM = "DCIM";
public static String DIRECTORY_DOCUMENTS = "Documents";*/
外部共有存儲(chǔ)目錄
外部存儲(chǔ)目錄還有一個(gè)存儲(chǔ)空間就是外部共有存儲(chǔ)目錄,顧名思義,外部共有存儲(chǔ)目錄存儲(chǔ)的數(shù)據(jù)無論對應(yīng)用還是用戶都是可見的應(yīng)用只要有外部訪問權(quán)限,就可以讀取外部公共目錄下的文件。外部公共目錄主要存放和應(yīng)用無關(guān)的數(shù)據(jù),這些數(shù)據(jù)在卸載App的時(shí)候不會(huì)被刪除。外部共有存儲(chǔ)目錄有以下特點(diǎn)。
- 當(dāng)卸載App時(shí),共有存儲(chǔ)目錄下的文件不會(huì)被刪除
- 應(yīng)用在訪問外部公有目錄之前,首先要申請外部存儲(chǔ)權(quán)限,在Android6.0以后,外部存儲(chǔ)權(quán)限還要?jiǎng)討B(tài)申請。
- 任何應(yīng)用只要有外部存儲(chǔ)權(quán)限,都可以訪問共有存儲(chǔ)目錄下的數(shù)據(jù)。
在代碼中,我們可以通過以下方式來訪問外部公共存儲(chǔ)目錄:
1.Environment.getExternalStorageDirectory()
//獲取到的目錄是/storage/emulated/0,這個(gè)也是外部存儲(chǔ)的根目錄。
2.Environment.getExternalStoragePublicDirectory(String type)
/* 1.如果type為"",那么獲取到的目錄是外部存儲(chǔ)的根目錄即 /storage/emulated/0
2.如果type不為空,則會(huì)在/storage/emulated/0目錄下創(chuàng)建一個(gè)以傳入的type值為名稱的目錄,例如你將type設(shè)為了test,那么就在外部存儲(chǔ)根目錄下創(chuàng)建test目錄,這個(gè)方法和getExternalFilesDir的用法一樣。android官方推薦使用以下的type類型,我們在SK卡的根目錄下也經(jīng)??梢钥吹较旅娴哪承┠夸洝? public static String DIRECTORY_MUSIC = "Music";
public static String DIRECTORY_PODCASTS = "Podcasts";
public static String DIRECTORY_RINGTONES = "Ringtones";
public static String DIRECTORY_ALARMS = "Alarms";
public static String DIRECTORY_NOTIFICATIONS = "Notifications";
public static String DIRECTORY_PICTURES = "Pictures";
public static String DIRECTORY_MOVIES = "Movies";
public static String DIRECTORY_DOWNLOADS = "Download";
public static String DIRECTORY_DCIM = "DCIM";
public static String DIRECTORY_DOCUMENTS = "Documents";*/
外部存儲(chǔ)和內(nèi)部存儲(chǔ)對比
要區(qū)分外部存儲(chǔ)和內(nèi)部存儲(chǔ),我們最好從邏輯上來理解這兩個(gè)概念,而不是從物理上。雖然在Android4.4以前,邏輯上和物理上是統(tǒng)一的,但是Android4.4以后,隨著外置SD卡的使用越來越少,內(nèi)部存儲(chǔ)和外部存儲(chǔ)和物理介質(zhì)的內(nèi)外就沒有任何關(guān)系了。首先通過一個(gè)圖來說明下外部存儲(chǔ)和內(nèi)部存儲(chǔ)與物理存儲(chǔ)的關(guān)系。

外部存儲(chǔ)和內(nèi)部存儲(chǔ)的對比如下表所示。

明白了內(nèi)部存儲(chǔ)和外部存儲(chǔ)的區(qū)別,在開發(fā)的過程中,我們就可以根據(jù)我們的需求來選擇對應(yīng)的存儲(chǔ)空間了。