Android 的存储结构
下面的『内外』,是相对应用而言的。应用内部沙盒称为内部存储,其外部称为外部存储。
内部存储
位置
Android 内部存储在/data/data/
目录下,根据应用的包名划分出来。
每个应用都有如下几个子文件夹:
-
data/data/包名/shared_prefs
:存放该APP内的SP信息 -
data/data/包名/databases
:存放该APP的数据库信息 -
data/data/包名/files
:将APP的文件信息存放在files文件夹 -
data/data/包名/cache
:存放的是APP的缓存信息
读取方法
内部存储不需要申请读取权限,可以任君使用! 读写文件分别使用:
-
openFileOutput()
write()
写入close()
关闭
-
openFileInput
read()
读取close()
关闭
也可以直接使用:
-
getCacheDir()
来获取缓存目录 -
getFilesDir()
来获取文件目录
外部存储
外置存储就必须申请权限,而且这里也有一些需要注意的地方,可以移步阅读。
一般来说,使用Environment.getExternalStorageDirectory()
获取的是『外置存储』,但是实际上这个并不是很准确。反而回因为这个『外置』而让人困惑:如果有外置 SD 卡的情况…那谁才是外置呢?
看一下这个方法的注释,他解释得很清楚:
Note: don’t be confused by the word “external” here. This directory can better be thought as media/shared storage
他说你不要被『外置』这个词搞蒙了,实际上更像是一个『共享』存储器。这样一说其实你就知道了,即便是有外置 SD 卡的情况,两者都属于『外置存储器』。因为这个概念是相对于 App 内部沙盒存储器来说的。 但是这个时候直接调用这个方法获取的,可能并不是 SD 卡的路径。因为用户并没有将 SD 卡设置为『默认存储器』,所以上面这个方法将会得到原本的『共享』存储器。而不是 SD 卡。
我们看一下实现:
public static File getExternalStorageDirectory() {
throwIfUserRequired();
return sCurrentUser.getExternalDirs()[0];
}
...
public File[] getExternalDirs() {
final StorageVolume[] volumes = StorageManager.getVolumeList(mUserId,
StorageManager.FLAG_FOR_WRITE);
final File[] files = new File[volumes.length];
for (int i = 0; i < volumes.length; i++) {
files[i] = volumes[i].getPathFile();
}
return files;
}
getExternalDirs()
方法返回了一个分卷列表,然后getExternalStorageDirectory()
直接返回了该列表的第一个元素。也就是『默认存储器』了。
那么我们如何获取 SD 卡路径呢?
获取外置 SD 卡路径
/**
* 返回外置存储卡路径
* @param context
* @return 返回存储卡路径
*/
public static String getExtendedMemoryPath(Context context) {
StorageManager mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class storageVolumeClazz;
try {
storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isRemovable = storageVolumeClazz.getMethod("isRemovable");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String path = (String) getPath.invoke(storageVolumeElement);
boolean removable = (Boolean) isRemovable.invoke(storageVolumeElement);
if (removable) {
return path;
}
}
} catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
这里利用了反射获取 SD 卡的路径。
获取 SD 卡的 UUID
/**
* 获取 SD 卡的 UUID,FAT32 格式为 xxxx-xxxx;NTFS 是更长的 hex 字符串
* @param context
* @return 返回 SD 卡的 UUID
*/
private static String getRealSDCardId(Context context){
StorageManager mStorageManager = (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
// 如果 API 大于 23,可以直接调用
if (mStorageManager == null || mStorageManager.getStorageVolumes().size() <= 1) {
return null;
}
StorageVolume sdVolume = mStorageManager.getStorageVolumes().get(1);
return sdVolume.getUuid();
}else {
String storagePath = getExtendedMemoryPath(context);
if (!TextUtils.isEmpty(storagePath)){
// 只考虑 FAT 32 格式的情况,TODO 兼容 NTFS 格式
Pattern pattern = Pattern.compile(PATTERN_GET_SD_CARD_ID);
Matcher matcher = pattern.matcher(storagePath);
if (matcher.find()){
return matcher.group();
}
}
}
return null;
}
这里还做了版本判断,如果 API 大于 23 的版本,可以直接调用getStorageVolumes()
方法,获取所有卷的卷标。
参看: