我們在開發Android應用的過程中,避免不了要用到數據持久化技術,所謂的數據持久化就是將RAM中的瞬時數據保存到ROM中,保證在App退出或者手機關機后數據不會丟失。我們常用的數據持久化的方式有文件存儲,數據庫存儲,SharedPreference存儲等。在window中,當我們存儲文件或者數據的時候,我們會選擇保存到磁盤的某個目錄,而在Android中有兩個位置可以讓應用進行數據持久化存儲—內部存儲和外部存儲。在開發過程中可以根據不同的場景將數據存儲在不同的位置,那究竟什么是內部存儲?什么是外部存儲?什么時候使用內部存儲?什么時候使用外部存儲?他們之間區別又是什么?帶著這些問題,我們開啟今天的探索之旅。
內部存儲
一說內部存儲,有人可能會和內存混淆在一起,其實這兩個概念很好區分,內部存儲是用于持久化存儲的,屬于ROM,手機關機或者退出App數據是不會丟失的,而內存是RAM,退出App或者關機之后數據就會丟失。所以,內部存儲不是內存。所謂的內部存儲,其實是手機ROM上的一塊存儲區域,主要用于存儲系統以及應用程序的數據。內部存儲在Android系統對應的根目錄是 /data/data/,這個目錄普通用戶是無權訪問的,用戶需要root權限才可以查看。不過我們可以通過Android Studio的View----Tool Windows----Device File Explorer工具來查看該目錄,內部存儲目錄的大致結構如下所示。
從上圖可以看到,/data/data目錄是按照應用的包名來組織的,每個應用都是屬于自己的內部存儲目錄,而且目錄的名稱就是該應用的包名,這個目錄是在安裝應用的時候自動創建的,當應用被卸載后,該目錄也會被系統自動刪除。所以,如果你將數據存儲于內部存儲中,其實就是把數據存儲到自己應用包名對應的內部存儲目錄中。每個應用的內部存儲目錄都是私有的,也就是說內部存儲目錄下的文件只能被應用自己訪問到,其他應用是沒有權限訪問的。應用訪問自己的內部存儲目錄時不需要申請任何權限。
一個應用典型的內部存儲目錄結構如下所示。
相信很多人看到內部存儲的目錄結構,都有似曾相識的感覺,沒錯我們平常經常和內部存儲打交道,只不過我們不知道罷了,下面我們來看下內部存儲目錄下各個子目錄的作用。
- app_webview:主要用于存儲webview加載過程中的數據,例如Cookie,LocalStorage等。
- cache:主要用于存儲使用應用過程中產生的緩存數據。
- databases:主要用于存儲數據庫類型的數據。我們平常創建的數據庫文件就是存儲在這里。
- files:可以在該目錄下存儲配置文件,敏感數據等。
- shared_prefs:用于存儲SharedPreference文件。我們使用SharedPreference的時候只指定了文件名,并沒有指定存儲路徑,其實SP的文件就是保存到了這個目錄下。
那么有哪些API可以獲取到內部存儲目錄呢,我們主要是使用Context類提供的接口來訪問內部存儲目錄。
1.getDataDir() //獲取的目錄是/data/user/0/package_name,即應用內部存儲的根目錄
2.getFilesDir() //獲取的目錄是/data/user/0/package_name/files,即應用內部存儲的files目錄
3.getCacheDir() //獲取的目錄是/data/user/0/package_name/cache,即應用內部存儲的cache目錄
4.getDir(String name, int mode) //獲取的目錄是/data/user/0/package_name/app_name,如果該目錄不存在,系統會自動創建該目錄。
獲取到對應的目錄后,我們就可以對目錄下的文件進行讀寫。細心的同學可能會發現代碼中獲取的內部存儲根目錄是 /data/user/0,并不是前面提到的/data/data,這是怎么回事呢?因為在Android4.2以后增加了多用戶的功能,為了適應多用戶的功能,原來的/data/data/相當于直接鏈接到當前用戶文件夾的,變成了/data/user/0/,所以我們代碼中打印出來的路徑是/data/user/0,而不是/data/data,說白了/data/data和/data/user/0/是一個東西。
內部存儲空間容量有限,如果內部存儲空間被用完,系統會報內存不足。所以,不要把所有的數據都放到內部存儲中。在開發應用過程中,我們可以把較敏感的應用數據放在內部存儲中,而其他的數據可以放在外部存儲中。那外部存儲又是什么呢?下面我們接著來學習外部存儲。
外部存儲
我們知道內部存儲中的數據對應用來說是私密的,用戶和其他應用都沒有訪問權限,而外部存儲中的數據是可以被其他應用或用戶訪問甚至刪除的,用戶可以通過USB方式和PC之間交互外部存儲中的數據。我們平常在Android手機的文件管理工具下看到的目錄其實就是外部存儲。在Android4.4以前,外部存儲就是指SD卡,手機自帶的存儲就是內部存儲;但是在Android4.4以后,隨著手機機身存儲越來越大,手機的機身存儲已經可以滿足大多數用戶的需求,所以很多手機都不需要再安裝SD卡。此時外部存儲和內部存儲都位于手機機身存儲上,他們只是同一個存儲介質上的不同存儲區域。但是很多手機還是保留了SD卡插槽,方便用戶自行拓展。如果手機安裝了SD卡,那么很顯然SD卡目錄也屬于外部存儲目錄。這時手機都有了兩個外部存儲空間,一個位于手機機身存儲上,一個位于SD卡上。但是隨著機身存儲越累越大,SD卡一般可能只適用于轉移文件,對于一般應用來說應該也不會把數據寫到外置的SD卡上了,所以這里主要以機身存儲為例來分析外部存儲。
和內部存儲不同的是,外部存儲根據存儲特點不同分為兩種類型:外部私有存儲和外部共有存儲。先來看外部私有存儲。
外部私有存儲目錄
通常來說,應用涉及到的持久化數據一般分為兩類:應用相關數據和應用無關數據。前者是指應用使用的數據信息,比如一些配置信息,調試信息,緩存文件等。當應用被卸載,這些信息也應該被隨之刪除,避免占用不必要的存儲空間。例如下面兩種場景。
- 在用戶使用應用過程中,產生的文件,圖片,視頻,音頻等數據,這些數據不太敏感但是占用空間比較大,卸載App時不希望這些數據繼續保留在用戶手機中。
- 當應用發生閃退時,希望把一些閃退信息保存下來,讓用戶獲取閃退信息文件后通過特定渠道發送給開發人員進行問題定位。同樣的,這些信息在卸載App后也不希望繼續留在用戶手機中。
對于問題一,我們可以直接把數據存儲在內部存儲中,但是考慮到內部存儲空間有限,把這些數據存儲到內部存儲會浪費內部存儲的空間。對于問題二,普通用戶(指沒有root權限的用戶)無法直接查看其中的文件,把數據直接存儲在內部存儲中是行不通的。這些數據有一個共同點就是他們的生命周期和應用是一致的,而且不太適合于放在內部存儲中。為了存儲這種類型的數據,Android規定來一個專門的存儲空間,這個空間被稱為外部私有存儲空間。外部私有存儲空間屬于外部存儲,對于某個應用來說,外部私有存儲的根目錄(這里暫時不考慮SD卡)是 /storage/emulated/0/Android/data/package_name,這個目錄有點類似于內部存儲目錄,都是以包名來命名私有存儲空間的。外部私有存儲空間有以下特點
- 內部私有存儲中的數據會隨著App的卸載一起刪除
- 僅僅安裝應用不會在/storage/emulated/0/Android/data/目錄下生成該應用的外部私有存儲目錄,只有在應用中調用API訪問外部私有存儲目錄時,才會創建以package_name命名的私有存儲目錄。
- App在訪問自己的外部私有存儲目錄時不需要任何權限
- 自 Android 7.0 開始,系統對外部存儲目錄中 應用私有目錄的訪問權限進一步限制。其他 App 無法通過 file:// 這種形式的 Uri 直接讀寫其他應用的外部私有存儲目錄,而是需要通過 FileProvider 訪問。
在代碼中我們可以通過以下方式來獲取外部私有存儲目錄。
1.getExternalCacheDir()
/*獲取到的目錄是/storage/emulated/0/Android/data/package_name/cache,如果該目錄不存在,調用這個方法會自動創建該目錄。*/
2.getExternalFilesDir(String type)
/* 1.如果type為"",那么獲取到的目錄是 /storage/emulated/0/Android/data/package_name/files
2.如果type不為空,則會在/storage/emulated/0/Android/data/package_name/files目錄下創建一個以傳入的type值為名稱的目錄,例如你將type設為了test,那么就會創建/storage/emulated/0/Android/data/package_name/files/test目錄,這個其實有點類似于內部存儲getDir方法傳入的name參數。但是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";*/
外部共有存儲目錄
外部存儲目錄還有一個存儲空間就是外部共有存儲目錄,顧名思義,外部共有存儲目錄存儲的數據無論對應用還是用戶都是可見的應用只要有外部訪問權限,就可以讀取外部公共目錄下的文件。外部公共目錄主要存放和應用無關的數據,這些數據在卸載App的時候不會被刪除。外部共有存儲目錄有以下特點。
- 當卸載App時,共有存儲目錄下的文件不會被刪除
- 應用在訪問外部公有目錄之前,首先要申請外部存儲權限,在Android6.0以后,外部存儲權限還要動態申請。
- 任何應用只要有外部存儲權限,都可以訪問共有存儲目錄下的數據。
在代碼中,我們可以通過以下方式來訪問外部公共存儲目錄:
1.Environment.getExternalStorageDirectory()
//獲取到的目錄是/storage/emulated/0,這個也是外部存儲的根目錄。
2.Environment.getExternalStoragePublicDirectory(String type)
/* 1.如果type為"",那么獲取到的目錄是外部存儲的根目錄即 /storage/emulated/0
2.如果type不為空,則會在/storage/emulated/0目錄下創建一個以傳入的type值為名稱的目錄,例如你將type設為了test,那么就在外部存儲根目錄下創建test目錄,這個方法和getExternalFilesDir的用法一樣。android官方推薦使用以下的type類型,我們在SK卡的根目錄下也經常可以看到下面的某些目錄。
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";*/
外部存儲和內部存儲對比
要區分外部存儲和內部存儲,我們最好從邏輯上來理解這兩個概念,而不是從物理上。雖然在Android4.4以前,邏輯上和物理上是統一的,但是Android4.4以后,隨著外置SD卡的使用越來越少,內部存儲和外部存儲和物理介質的內外就沒有任何關系了。首先通過一個圖來說明下外部存儲和內部存儲與物理存儲的關系。
外部存儲和內部存儲的對比如下表所示。
明白了內部存儲和外部存儲的區別,在開發的過程中,我們就可以根據我們的需求來選擇對應的存儲空間了。