Android開發中有五種數據持久化API:java
data/data/<包名>/ | 描述 |
---|---|
Context#getDir(String name,int mode):File! | 內部存儲根目錄下的文件夾(不存在則新建) |
data/data/<包名>/files/ | 描述 |
---|---|
Context#getFilesDir():File! | files文件夾 |
Context#fileList():Array<String!>! | 列舉文件和文件夾 |
Context#openFileInput(String name):FileInputStream! | 打開文件輸入流(不存在則拋出FileNotFoundException) |
Context#openFileOut(String name,int mode):FileOutputStream! | 打開文件輸出流(文件不存在則新建) |
Context#deleteFile(String name):Boolean! | 刪除文件或文件夾 |
data/data/<包名>/cache/ | 描述 |
---|---|
Context#getCacheDir():File! | cache文件夾 |
data/data/<包名>/code_cache/ | 描述 |
---|---|
Context#getCodeCacheDir():File! | 存放優化過的代碼(如JIT優化) |
data/data/<包名>/no_backup/ | 描述 |
---|---|
Context#getNoBackUpFIlesDir():File! | 在Backup過程當中被忽略的文件 |
訪問模式參數android
try(FileOutputStream fos = openFileOutput("file_name",MODE_WORLD_WRITEABLE)){面試
fos.write("Not sensitive information".getBytes());
}catch (IOException e){數組
e.printStackTrace();
}緩存
// 異常:
Caused by: java.lang.SecurityException: MODE_WORLD_READABLE no longer supported
Caused by: java.lang.SecurityException: MODE_WORLD_WRITEABLE no longer supported多線程
* **MODE_PRIVATE**:只對在應用內可見 * **MODE_APPEND**:若是文件存在,則在文件末尾追加;文件不存在,則與 MODE_PRIVATE 相同。 * **MODE_WORLD_READABLE** 和 **MODE_WORLD_WRITEABLE**:容許其餘應用訪問(不要使用) * 版本變動:**棄用**常量 MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE(API 17) * 版本變動:**禁用**常量 MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE(API 24) ## 3\. 外部存儲(External Storage/Shared Storage) ##### 3.1 定義 早期的Android設備存儲空間較小,有一個內置(build-in)的存儲空間,即內部存儲,另外還有一個能夠移除的存儲介質,即外部存儲(如SD卡)。可是隨着設備內置存儲空間增大,不少設備已經足以將內置存儲空間一分爲二,一塊爲內部存儲,一塊爲外部存儲。 * **全部應用都可讀寫**,原則上不該保存敏感信息 * 檢查是否掛載 外部存儲並不老是可用的,由於外部存儲能夠移除(早期設備)或者做爲USB存儲設備鏈接到PC,訪問前**必須檢查是否掛載**(mounted):
boolean mExternalStorageAvailable = false;
boolean mExternalStorageWriteable = false;
/ 檢查外部存儲是否可讀寫 /
void updateExternalStorageState() {架構
String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { // 可讀寫 mExternalStorageAvailable = mExternalStorageWriteable = true; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { // 可讀 mExternalStorageAvailable = true; mExternalStorageWriteable = false; } else { mExternalStorageAvailable = mExternalStorageWriteable = false; }
}併發
* 監聽外部存儲狀態
BroadcastReceiver mExternalStorageReceiver;
/ 開始監聽 /
void startWatchingExternalStorage() {ide
mExternalStorageReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { // 更新狀態 updateExternalStorageState(); } }; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_MEDIA_MOUNTED); filter.addAction(Intent.ACTION_MEDIA_REMOVED); // 動態註冊廣播接收器 registerReceiver(mExternalStorageReceiver, filter); updateExternalStorageState();
}
/ 中止監聽 /
void stopWatchingExternalStorage() {學習
// 註銷廣播接收器 unregisterReceiver(mExternalStorageReceiver);
}
* 權限
<manifest...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" /> ...
</manifest>
* 版本變動:動態權限(API 23) * 讀權限:**android.permission.READ_EXTERNAL_STORAGE** * 讀+寫權限:**android.permission.WRITE_EXTERNAL_STORAGE** * 版本變動:訪問外部存儲的私有目錄不須要申請權限(API 19) ##### 3.2 劃分 ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xNTY3OTEwOC04M2VkYjJkZDE2MzJlYmU2) #### 外部存儲 示意圖 * 私有目錄(private):`storage/emulated/0/Android/` * 每一個應用獨佔以包名命名的私有文件夾 * **在應用卸載時被刪除** * **對MediaScanner不可見**(例外:多媒體文件夾 API 21) * 特色 * 適用場景:**非私密數據,須要隨應用卸載刪除** * 公共目錄(public):外部存儲中除了私有目錄外的其餘空間 * 全部應用共享 * **在應用卸載時不會被刪除** * **對MediaScanner可見** * 特色 * 適用場景:**非私密數據,不須要隨應用卸載刪除** ##### 3.3 外部存儲API ![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xNTY3OTEwOC1hNDM2Nzk5MzFjNjlkZDAx) #### 外部存儲 API > 由於外部存儲不必定可用,因此返回值可爲空或空數組 * 公共目錄: | storage/emulated/0/ | 描述 | | --- | --- | | Environment.getExternalStorageDirectory():File? | 外部存儲根目錄 | | Environment.getExternalStoragePublicDirectory(name:String?):File? | 外部存儲根目錄下的文件夾 | | Environment.getExternalStorageState():String! | 外部存儲狀態 | * 私有目錄: | storage/emulated/0/Android/data/<包名>/ | 描述 | | --- | --- | | Context.getExternalCacheDir():File? | cache文件夾 | | Context.getExternalCacheDirs():Array<File!>! | 多部分cache文件夾(API 18) | | Context.getExternalFilesDir(type: String?):File? | files文件夾 | | Context.getExternalFIlesDirs(type:String?):Array<File!>! | 多部分files文件夾(API 18) | | Context.getExternalMediaDirs():Array<File!>! | 多部分多媒體文件夾(API 21) | * 版本變動:多部分外部存儲——Context#getExternalFilesDirs()(API 18) 有些設備能夠外接存儲設備(如SD卡)來得到更大的外部存儲空間,至關於有多部分外部存儲空間,一塊內置,一塊外置。在存儲空間足夠時,應該優先存儲在內置的部分。 > 兼容:Context.getExternalFilesDirs():Arra<File!>!,在低版本中數組只會返回一個元素,指向內置的外置存儲的路徑 版本變動:外部存儲多媒體文件夾——`Context.getExternalMediaDirs()(API 21`):對`MediaScanner`可見 ## 4\. 補充 ##### 4.1 緩存文件 * 內部存儲和外部存儲中都有一個緩存文件夾: * data/data/<包名>/cache/ * storage/emulated/0/Android/data/<包名>/cache/ * 當設備存儲空間不足時,緩存文件能夠被回收,系統回收策略爲: * 閾值
StorageManager sm = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
UUID uuid = sm.getUuidForPath(getCacheDir());
long byteSize = sm.getCacheQuotaBytes(uuid);
* 查詢已分配的緩存空間
sm.getCacheSizeBytes(uuid)
* 行爲——緩存粒度
// 整個文件夾視爲一個緩存總體,在系統回收空間時清空文件夾sm.setCacheBehaviorGroup(dirFile,true)
* 行爲——保留文件結構
// 在系統回收文件時,清空文件數據(length=0),而不是直接刪除文件sm.setCacheBehaviorTombstone(dirFile,true)
* before Android O(before API 26) 策略:按照文件修改時間(modified time)排序,越早時間將優先被刪除 > 漏洞:應用能夠設置文件修改時間到一個稍晚的時間(好比2050年),保持不被刪除 * since Android O(since API 26) 策略:系統分別爲每一個應用設置緩存空間閾值,設備存儲空間不足時,超過閾值的應用將優先刪除緩存,低於閾值的應用緩存會被保留。系統會動態修改閾值,用戶使用頻率越高的應用閾值越高。 * 清除應用的數據的選項(在系統設置或手機管家中): * 清除緩存:清除應用的**內部存儲緩存文件夾** 與 **外部存儲緩存文件夾**; * 清除數據:清除應用的**內部存儲** 與 **外部存儲空間私有目錄**; ### 4.2 android:installLocation * 可選值 * **internalOnly**(默認):安裝在內部存儲,內部存儲空間不足時沒法安裝; * **auto**:優先安裝在內部存儲,內部存儲空間不足時,嘗試安裝在外部存儲; * **preferExternal**:優先安裝在外部存儲,外部存儲空間不足時,嘗試安裝在內部存儲; * 外部存儲被移除時,安裝在外部存儲空間上的應用會被系統殺死。直到外部存儲從新掛載時,系統發出**ACTION_EXTERNAL_APPLICATIONS_AVAILABLE**廣播。 * 對於佔用存儲空間較大的應用來講,就有必要考慮安裝在外部存儲。舉例:反編譯**王者榮耀**查看AndroidManifest文件,能夠看到使用了「auto」選項。
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.tencent.tmgp.sgame" platformBuildVersionCode="28" platformBuildVersionName="9" android:compileSdkVersion="28" android:compileSdkVersionCodename="9" android:installLocation="auto" android:theme="@android:style/Theme.NoTitleBar">
## 4.3 存儲空間 ##### 4.3.1 查詢 * File
val target = File(context.filesDir,"my-download")
target.freeSpace // 未分配容量(Root用戶可用的容量)
target.usableSpace // 可用容量(非Root用戶可用的容量)
target.totalSpace // 所有容量(包括已分配容量和未分配容量)
* StatFs(API 18)
val target = File(context.filesDir,"my-download")
val stat = StatFs(target)
val blockSize = stat.blockSizeLong
stat.freeBlocksLong * blockSize // 同上
stat.availableBlocksLong * blockSize // 同上
stat.blockCountLong * blockSIze // 同上
* StorageManager(API 26)
val target = File(context.filesDir,"my-download")
val ssm = getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManager
val sm = getSystemService(Context.STORAGE_SERVICE) as StorageManager
val uuid = sm.getUuidForPath(target)
ssm.getFreeBytes(uuid) // 可用容量(非Root用戶可用的容量)
ssm.getTotalBytes(uuid) // 完整的物理容量(好比64G)
* StorageStatsManager(API 26)
val target = File(context.filesDir,"my-download")val ssm = getSystemService(Context.STORAGE_STATS_SERVICE) as StorageStatsManagerval sm = getSystemService(Context.STORAGE_SERVICE) as StorageManagerval uuid = sm.getUuidForPath(target)ssm.getFreeBytes(uuid) // 可用容量(非Root用戶可用的容量)ssm.getTotalBytes(uuid) // 完整的物理容量(好比64G) ```
before API 26
if(downloadSize <= target.getAvailableSpace()){
// 磁盤空間充足,能夠寫入 ...
}
> 注意:即便判斷磁盤空間充足,也可能在寫入過程當中拋出IOException(空間不足),由於沒法避免多線程或多進程併發寫入。 * since API 26
val target = File(context.filesDir,"my-download")
val sm = getSystemService(Context.STORAGE_SERVICE) as StorageManager
val uuid = sm.getUuidForPath(target)
if(downloadSize <= sm.getAllocatableBytes(uuid){
try(FileOutPutStream os = FileOutPutStream(target)){ // 預分配downloadSize大小的空間給當前應用 sm.allocateBytes(os.getFD(),downloadSize) // 寫入 ... }
}else{
// 空間不足,請求用戶自行清理空間 val intent = Intent(StorageManager.ACTION_MANAGE_STORAGE); intent.putExtra(StorageManager.EXTRA_UUID,uuid); // 須要的空間 intent.putExtra(StorageManager.EXTRA_REQUESTED_BYTES,downloadSize); context.tartActivityForResult(intent,REQUEST_CODE);
}
> StorageManager#allocateBytes()能夠避免了併發寫入形成空間不足異常 ## 5\. 總結 * 隱私性 | 位置 | 其餘應用 | 未root用戶 | root用戶 | MediaScanner | | --- | --- | --- | --- | --- | | 內部存儲 | X | X | √ | X | | 私有內部存儲 | √ | √ | √ | 僅多媒體文件夾 | | 公共內部存儲 | √ | √ | √ | √ | * 生存期![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xNTY3OTEwOC04OGJmMzg5YmM2NWMyMzZj) * 版本演進![](https://imgconvert.csdnimg.cn/aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy8xNTY3OTEwOC1iYzk1NWM5YWNhYTBhMTQz) ### Android開發資料+面試架構資料 免費分享 點擊連接 便可領取 [《Android架構師必備學習資源免費領取(架構視頻+面試專題文檔+學習筆記)》](https://shimo.im/docs/VqoXZ2j9E04V4nhk/ )