Android 手机存储那些事儿

最近项目开发新增清除缓存需求,于是查阅相关资料并结合功能的实现过程,对手机存储的相关知识做下总结。

首先看下这张图:

可以看到,我们把手机存储分为了内部存储和外部存储。

1. 内部存储

内部存储指的是存储在手机 /data/data/<应用包名> 目录下的数据,附上一张高清无码大图。

从图中可以看到(注意:手机没 ROOT 是无法打开该文件夹的),内部存储有以下几种我们熟悉的数据:

files——文件存储(注:其对应路径 /data/data/<应用包名>/files 可通过 context.getFilesDir() 获取)

shared_prefs——sharedPreference 存储

cache——图片缓存(注:其对应路径 /data/data/<应用包名>/cache 可通过 context.getCacheDir() 获取,由于我的项目中用到了 Glide,所以发现 Glide 的图片存在了该目录下的 image_manager_disk_chache 文件夹内)

databases——数据库存储

2. 外部存储

外部存储分为内置 SD 卡存储和拓展卡存储(外置 SD 卡)。

  • 内置 SD 卡:现在出产的手机基本都自带的,也就是我们常说的存储空间,有 16G,32G,64G,128G(以前的部分老旧低端机没有内置 SD 卡)
  • 外置 SD 卡:可以插入手机的存储卡,扩大手机存储内存

特别注意下,一般我们在新建文件夹的时候需要判断一下手机是否有 SD 卡,代码如下(项目中碰到的坑):

Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)

对于 SD 卡的路径 /storage/sdcard,我们可以通过 Enviroment 中的方法获取到。

Environment.getExternalStorageDirectory()

SD 卡的文件路径也可以分为公有路径和私有路径。

2.1 公有路径

公有路径的获取方式如下:

Environment.getExternalStoragePublicDirectory(String type)

其参数共有 10 种类型,对应的参数和路径如下表:

参数 路径
Environment.DIRECTORY_MUSIC /storage/sdcard0/Music
DIRECTORY_PODCASTS /storage/sdcard0/Podcasts
DIRECTORY_RINGTONES /storage/sdcard0/Ringtones
DIRECTORY_ALARMS /storage/sdcard0/Alarms
DIRECTORY_NOTIFICATIONS /storage/sdcard0/Notifications
DIRECTORY_PICTURES /storage/sdcard0/Pictures
DIRECTORY_MOVIES /storage/sdcard0/Movies
DIRECTORY_DOWNLOADS /storage/sdcard0/Download
DIRECTORY_DCIM /storage/sdcard0/DCIM
DIRECTORY_DOCUMENTS /storage/sdcard0/Documents

2.2 私有目录

在 SD 卡目录下的 Android 文件夹就是对应的私有目录。打开 Android 目录,可以看到里面还有 data 文件夹,再打开这个文件夹,就是许多应用包名组成的文件夹。可以发现这些包名文件夹里面基本都包含有 files 和 cache 这两个文件夹。这两个文件夹的路径可以通过如下方法获取:

files——sdcard/Android/data/应用包名/files(注:可通过 context.getExternalFilesDir() 获取)

cache——sdcard/Android/data/应用包名/cache(注:可通过 context.getExternalCacheDir() 获取)

当调用 getExternalFilesDir() 和 getExternalCacheDir() 即可创建 files 和 cache 文件夹。看到这,你或许会问:

这两个文件夹不是和我们上面看到的内部存储一样吗,为什么要存在这里面呢。

原因在于内部存储空间有限,我们一般都是操作外部存储空间,虽然我们可以获得内部存储的路径,但是我们基本不会去操作内部空间,何况在没有 ROOT 情况下,也没法进行操作。而且 Google 官方也建议我们在外部存储的私有目录下进行数据存储操作。

这是或许你又会问:

很多 APP 都是在 SD 卡目录下创建文件进行数据存储操作的。

的确,很多 APP 是这么干的,但是当用户卸载 APP 的时候,内部存储中的包名文件夹及相关数据会被删除,外部存储的私有目录下相关数据也会跟随包名文件夹一起被删除,但是自己创建的文件夹是不会被删除的。在 sdcard 目录下创建文件不仅不利于系统维护,也会造成用户的反感(反正我是会经常去删除 SD 卡目录下的空文件夹)。

这时候我们再回到清除缓存这个功能上。对于需要被清除的数据,应该是内部存储中相应的 files 和 cache 文件夹内的数据和外部存储私有目录下相应的 files 和 cache 文件夹内的数据(可根据需求清除 files 文件夹内的相应数据)。

最后附上一个清除缓存数据的类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
public class CleanCacheManager {
/**
* @param context
* @return
* @throws Exception
* 获取当前缓存
*/
public static String getTotalCacheSize(Context context) throws Exception {
long cacheSize = getFolderSize(context.getCacheDir());
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
cacheSize += getFolderSize(context.getExternalCacheDir());
}
return getFormatSize(cacheSize);
}
/**
* @param context
* 删除缓存
*/
public static void clearAllCache(Context context) {
deleteDir(context.getCacheDir());
if (Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)) {
deleteDir(context.getExternalCacheDir());
}
}
private static boolean deleteDir(File dir) {
if (dir != null && dir.isDirectory()) {
String[] children = dir.list();
int size = 0;
if (children != null) {
size = children.length;
for (int i = 0; i < size; i++) {
boolean success = deleteDir(new File(dir, children[i]));
if (!success) {
return false;
}
}
}
}
if (dir == null) {
return true;
} else {
return dir.delete();
}
}
// 获取文件
// Context.getExternalFilesDir() --> SDCard/Android/data/你的应用的包名/files/
// 目录,一般放一些长时间保存的数据
// Context.getExternalCacheDir() -->
// SDCard/Android/data/你的应用包名/cache/目录,一般存放临时缓存数据
public static long getFolderSize(File file) throws Exception {
long size = 0;
try {
File[] fileList = file.listFiles();
int size2 = 0;
if (fileList != null) {
size2 = fileList.length;
for (int i = 0; i < size2; i++) {
// 如果下面还有文件
if (fileList[i].isDirectory()) {
size = size + getFolderSize(fileList[i]);
} else {
size = size + fileList[i].length();
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return size;
}
/**
* 格式化单位
* 计算缓存的大小
* @param size
* @return
*/
public static String getFormatSize(double size) {
double kiloByte = size / 1024;
if (kiloByte < 1) {
// return size + "Byte";
return "0KB";
}
double megaByte = kiloByte / 1024;
if (megaByte < 1) {
BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
return result1.setScale(2, BigDecimal.ROUND_HALF_UP)
.toPlainString() + "KB";
}
double gigaByte = megaByte / 1024;
if (gigaByte < 1) {
BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
return result2.setScale(2, BigDecimal.ROUND_HALF_UP)
.toPlainString() + "MB";
}
double teraBytes = gigaByte / 1024;
if (teraBytes < 1) {
BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
return result3.setScale(2, BigDecimal.ROUND_HALF_UP)
.toPlainString() + "GB";
}
BigDecimal result4 = new BigDecimal(teraBytes);
return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString()
+ "TB";
}
}