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 划分  #### 外部存储 示意图 * 私有目录(private):`storage/emulated/0/Android/` * 每一个应用独占以包名命名的私有文件夹 * **在应用卸载时被删除** * **对MediaScanner不可见**(例外:多媒体文件夹 API 21) * 特色 * 适用场景:**非私密数据,须要随应用卸载删除** * 公共目录(public):外部存储中除了私有目录外的其余空间 * 全部应用共享 * **在应用卸载时不会被删除** * **对MediaScanner可见** * 特色 * 适用场景:**非私密数据,不须要随应用卸载删除** ##### 3.3 外部存储API  #### 外部存储 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 | | 私有内部存储 | √ | √ | √ | 仅多媒体文件夹 | | 公共内部存储 | √ | √ | √ | √ | * 生存期 * 版本演进 ### Android开发资料+面试架构资料 免费分享 点击连接 便可领取 [《Android架构师必备学习资源免费领取(架构视频+面试专题文档+学习笔记)》](https://shimo.im/docs/VqoXZ2j9E04V4nhk/ )