安卓存儲(chǔ)
今天晚上Android項(xiàng)目組的小伙伴一起聊了一下Android數(shù)據(jù)持久化的幾種方式外加一些項(xiàng)目中的總結(jié)吧.突然發(fā)現(xiàn)一些不怎么用的東西都已經(jīng)快忘光了,比如說ContentProvider + LoadManger + URIMatcher + CursorAdapter,今天突然提到LoadManager感覺像是一個(gè)沒接觸過的東西,被小伙伴提了一下才記得有這個(gè)類的存在,更別提ContentObserver了.有點(diǎn)跑題了還是聊一下Android的數(shù)據(jù)持久化策略吧!
Android數(shù)據(jù)持久化的方式
總所周知,Android共有五種數(shù)據(jù)持久化的方式,這里是官方文檔.通過閱讀API Guidance可知,這其中方式分別如下:
1. SharePreference
通常用于存儲(chǔ)一些本地化的配置文件,主要分為讀和取,操作如下:
1.寫入操作
// We need an Editor object to make preference changes.
// All objects are from android.context.Context
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("silentMode", mSilentMode);
// Commit the edits!
editor.commit();
//finally we can see these file in data/data/packagename/shared_preference if your device has been rooted;
2.讀取操作
// Restore preferences
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
//false stands for the default value, you can customize yourself
boolean silent = settings.getBoolean("silentMode", false);
2. Internal Storage
第一點(diǎn)提到的SharePreference存儲(chǔ)的方式最終存放的位置就是在Internal Storage中
String FILENAME = "hello_file";
String string = "hello world!";
//finally the file path is data/data/packagename/files/hello_file
//and there are kinds of MODE(MODE_PRIVATE,MODE_APPEND,MODE_WORLD_READABLE,MODE_WORLD_WRITABLE)
FileOutputStream fos = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(string.getBytes());
fos.close();
值得注意的是從API 17開MODE_WORLD_READABLE跟MODE_WORLD_WRITABLE已經(jīng)是deprecated的狀態(tài).從API 23開始,由于谷歌收緊了Android系統(tǒng)的權(quán)限(Android越來越封閉,蘋果越來越開放,目的都是為了體驗(yàn)越來越好!),如果還使用這兩個(gè)屬性會(huì)直接拋出異常SecurityException.因此如果你的應(yīng)用的TargetVersion在API 23,那么如果你想共享應(yīng)用的內(nèi)部數(shù)據(jù)則只能通過主動(dòng)分享的方式發(fā)起共享,詳見參考文檔.
正如官方文檔所提,如果想要存儲(chǔ)靜態(tài)文件,比如說應(yīng)用的鈴聲之類的文件,可以放在項(xiàng)目的res/raw/路徑下,通過openRawResource(R.raw.fileId)獲取對(duì)應(yīng)的資源文件.
保存Cache文件
通過getCacheDir()可以返回app內(nèi)部的cache文件,該目錄即data/data/packagename/cache由系統(tǒng)維護(hù),但是谷歌的官方建議是自行維護(hù)1MB左右,該目錄會(huì)隨著應(yīng)用卸載而被清理掉.其他諸如getFileDir(),getDir()會(huì)在后文有個(gè)總結(jié)性的說明.
3. External Storage
Android的外部存儲(chǔ)可以分為可卸載的存儲(chǔ)例如SD卡,以及不可卸載的內(nèi)部存儲(chǔ)(Internal Storage).通過USB文件傳輸模式連接的時(shí)候,用戶可以對(duì)外部存儲(chǔ)進(jìn)行操作.
當(dāng)用戶掛載外部存儲(chǔ)或者移除外部存儲(chǔ)的時(shí)候,應(yīng)用就無法操作外部存儲(chǔ)了.同時(shí)外部存儲(chǔ)的全局可讀特性也決定了它本身并沒有安全性可言.
獲取訪問外部存儲(chǔ)的權(quán)限
如果你的應(yīng)用需要讀寫外部存儲(chǔ)則需要在manifest文件中配置如下權(quán)限:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
如果只需要讀取權(quán)限只需申明READ_EXTERNAL_STORAGE權(quán)限即可;如果需要讀寫權(quán)限只需要申明WRITE_EXTERNAL_STORAGE權(quán)限即可,因?yàn)橄到y(tǒng)會(huì)同時(shí)聲明讀取權(quán)限.
注意:從Android4.4開始,如果只需要讀寫app內(nèi)部的文件,則無需申明讀寫權(quán)限.
檢查是否具備讀寫權(quán)限
無論在什么地方進(jìn)行外部存儲(chǔ)操作的時(shí)候你都應(yīng)該檢查外部存儲(chǔ)是否處于可用狀態(tài).
/*Ckecks if external storage is available for read and write\*/
public boolean isExternalStorageAvailable(){
String state = Environment.getExternalStorageState();
return Environment.MEDIA_MOUNTED.equals();
}
通過Environment.getExternalStorageState()可以獲得外部存儲(chǔ)的狀態(tài),如連接到電腦,徹底移除,不恰當(dāng)?shù)囊瞥鹊?此時(shí)你可以通過判斷外部存儲(chǔ)的狀態(tài)來決定是否需要訪問外部存儲(chǔ)的媒體文件.如下所示為外置存儲(chǔ)的不同狀態(tài):
public static final String MEDIA_BAD_REMOVAL = "bad_removal";
public static final String MEDIA_CHECKING = "checking";
public static final String MEDIA_EJECTING = "ejecting";
public static final String MEDIA_MOUNTED = "mounted";
public static final String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
public static final String MEDIA_NOFS = "nofs";
public static final String MEDIA_REMOVED = "removed";
public static final String MEDIA_SHARED = "shared";
public static final String MEDIA_UNKNOWN = "unknown";
public static final String MEDIA_UNMOUNTABLE = "unmountable";
public static final String MEDIA_UNMOUNTED = "unmounted";
保存文件共享給其他應(yīng)用
主要是公共的多媒體庫(kù),例如MUSIC,PICTURE等等,通過以下方式獲得
Environment.getExternalPublicDirectory(String type)
其中type可分為如下幾種,對(duì)應(yīng)到外部存儲(chǔ)的不同位置.
public static String DIRECTORY_ALARMS;
public static String DIRECTORY_DCIM;
public static String DIRECTORY_DOCUMENTS;
public static String DIRECTORY_DOWNLOADS;
public static String DIRECTORY_MOVIES;
public static String DIRECTORY_MUSIC;
public static String DIRECTORY_NOTIFICATIONS;
public static String DIRECTORY_PICTURES;
public static String DIRECTORY_PODCASTS;
public static String DIRECTORY_RINGTONES;
保存應(yīng)用私有文件
如果你的應(yīng)用有一些私有文件,如音效文件,此時(shí)可以在外部存儲(chǔ)中創(chuàng)建一個(gè)私有的文件目錄:
//if you pass null as the type return the root directory
//storage/emulated/0/Android/data/packagename/
//of course you can pass non null type to create a subdirectory
Context.getExternalFilesDir(String type)
從Android 4.4開始,讀寫應(yīng)用的私有空間的文件并不需要讀寫權(quán)限,因此如果其他地方無需讀寫權(quán)限的情況下可以通過一下方式申明:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />
...
</manifest>
這里有個(gè)事情需要說明的是,這個(gè)應(yīng)用的私有目錄會(huì)隨著應(yīng)用的卸載而刪除.同時(shí),在媒體庫(kù)中并不會(huì)顯示應(yīng)用的私有文件,因此,如果在你的app中屬于用戶的文件是不能保存到這個(gè)目錄的,例如用戶購(gòu)買的音樂.
有些手機(jī)會(huì)把內(nèi)置存儲(chǔ)劃出來一部分做為外置存儲(chǔ)使用(我們現(xiàn)在使用的多數(shù)都是這種方式),但是的手機(jī)還提供了SD卡卡槽.對(duì)于Android 4.3及以下的設(shè)備,通過getExternalFilesDir()只能獲得內(nèi)置存儲(chǔ)中劃出來的那一部分,也就是說并不能獲得SD卡部分.從Android4.4開始通過getExternalFilesDir()返回的是一個(gè)數(shù)組.只有在內(nèi)置存儲(chǔ)劃分的外置存儲(chǔ)不可用或者占用滿的情況下才選擇使用SD卡做為應(yīng)用的私有存儲(chǔ)空間.如果想要在Android4.3及以下的設(shè)備中獲取該路徑則通過兼容包ContextCompact.getExternalFilesDirs()獲取.
需要注意的是,盡管對(duì)于媒體中心(MediaStore)而言,應(yīng)用外部存儲(chǔ)的私有空間是不可見的,但是對(duì)于其他具有讀取或者寫入存儲(chǔ)權(quán)限的軟件而言,放在外部存儲(chǔ)的文件是可讀可寫的.因此,如果你不想你的文件被別的軟件使用或者更改的話,就只能放到應(yīng)用的內(nèi)部空間了.
保存緩存文件
通過如下方式獲得緩存文件的保存目錄,緩存目錄會(huì)隨著應(yīng)用的卸載而被刪除:
getExternalCacheDir();
跟上面提到的類似,你也可以通過ContextCompact.getExternalCacheDirs()獲得SD卡之類的外置存儲(chǔ)的緩存路徑.
在代碼開發(fā)的時(shí)候我們應(yīng)該特別注意緩存文件的維護(hù),防止緩存文件占用過多的空間.比如說我們使用一些第三方的框架的時(shí)候會(huì)讓我們配置緩存空間大小.
Using Databases
Android支持SQLite的所有功能,在應(yīng)用里面創(chuàng)建的數(shù)據(jù)庫(kù)能夠被應(yīng)用的任何一個(gè)類訪問,但是不能在應(yīng)用外訪問.通常可以配合URIMatcher + ContentProvider + SQLiteOpenHelper進(jìn)行數(shù)據(jù)庫(kù)數(shù)據(jù)共享,列入聯(lián)系人的獲取.
Using NetWork Connection
即數(shù)據(jù)存儲(chǔ)在服務(wù)端,通過網(wǎng)絡(luò)獲取持久化數(shù)據(jù).
總結(jié)
前面基本上是對(duì)于官方文檔的一種翻譯和理解,后面來說一下自己的總結(jié),首先觀察一下現(xiàn)象
///storage/emulated/0/Android/data/com.max.testuninstall/cache
Log.d(TAG, "onCreate: 外部緩存存儲(chǔ)" + this.getExternalCacheDir().toString());
//storage/emulated/0/Android/data/com.max.testuninstall/files
Log.d(TAG, "onCreate: 外部私有存儲(chǔ)" + this.getExternalFilesDir(null).toString());
///storage/emulated/0
Log.d(TAG, "onCreate: 外部共用存儲(chǔ)" + Environment.getExternalStorageDirectory().toString());
///data/user/0/com.max.testuninstall/cache
Log.d(TAG, "onCreate: 內(nèi)部緩存存儲(chǔ)" + this.getCacheDir().toString());
///data/user/0/com.max.testuninstall/files
Log.d(TAG, "onCreate: 內(nèi)部文件存儲(chǔ)" + this.getFilesDir().toString());
///data/user/0/com.max.testuninstall/app_null
Log.d(TAG, "onCreate: 內(nèi)部文件存儲(chǔ)" + this.getDir(null, MODE_PRIVATE).toString());
///data/user/0/com.max.testuninstall/app_hello_world
Log.d(TAG, "onCreate: 內(nèi)部文件存儲(chǔ)" + this.getDir("hello_world", MODE_PRIVATE).toString());
///data/user/0/com.max.testuninstall/files(List)
Log.d(TAG, "onCreate: 內(nèi)部文件存儲(chǔ),返回目錄下所有文件" + this.fileList().toString());
String FILENAME = "hello_file";
String string = "hello world!";
//data/data/packagename/files/hello_file
FileOutputStream fos = this.openFileOutput(FILENAME, Context.MODE_PRIVATE);
fos.write(string.getBytes());
fos.close();
以上對(duì)應(yīng)前文所說的通過不同方式獲取內(nèi)部,外部文件及緩存路徑.
讀寫權(quán)限
Internal Storage本身無需聲明任何權(quán)限即可進(jìn)行讀寫操作.External Storage
從Android4.4開始讀寫應(yīng)用私有空間無需聲明讀寫權(quán)限,Android4.3及以下需要聲明讀寫權(quán)限.Android系統(tǒng)中寫入External Storage權(quán)限包含讀取External Storage權(quán)限.
應(yīng)用卸載
通過上面對(duì)應(yīng)的日志結(jié)合實(shí)際操作發(fā)現(xiàn),Context獲取的路徑在應(yīng)用卸載的時(shí)候會(huì)被同時(shí)刪除,其實(shí)這也是可以理解的,畢竟是屬于應(yīng)用私有的文件.通過Environment獲得的路徑屬于外界環(huán)境的,所以不會(huì)跟隨應(yīng)用的卸載而被刪除掉.
多用戶
在/storage/emulated/目錄下面會(huì)有 0(默認(rèn)用戶),如果新建一個(gè)用戶則是10,再新建則是11依此類推.