一篇文章完全明白Android文件存储

前言

  • Android中常常须要使用文件存储用户数据
  • 本文将梳理各个版本中的文件存储,但愿能帮上忙。

文件存储 思惟导图

1. 简介

Android开发中有五种数据持久化API:java

持久化 示意图

2. 内部存储空间(Internal Storage)

2.1 划分

内部存储 示意图

  • 目录:/data/data/
  • 特色:
  • 每一个应用独占一个以包名命名的私有文件夹
  • 在应用卸载时被删除
  • 对MediaScanner不可见
  • 适用场景:私密数据
2.2 API

内部存储 API

  • 相关的API
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)
```

4.3.2 分配

  • 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/ )
相关文章
相关标签/搜索