内部存储和外部存储的概念随着Android版本的更新也在发生不断的变化。最早的内部存储指的是系统自带的ROM存储,外部存储指的是外置的Sdcard或者通过OTG挂在的USB存储。随着硬件价格越来愈低,系统内置的ROM也越来越大,现在即使是一台千元机,一般存储容量也是在16GB以上。所以在Android 4.4以后,系统将内部存储分为了Internal Storage和External Storage。而原来的外置Sdcard或者通过OTG挂载的USB,也都是属于External Storage。
内部存储(Internal File Storage)
内部存储指的是应用内部独有的存储,这部分存储的文件、数据,只能被应用自身访问到,其他应用都没有权限访问。
一般情况下,/data开头的路径都是Internal Storage。而一般应用所能够访问到的就是下面几个路径,称为应用内部私有存储。
应用内部私有存储:
/data/user/0/<包名>包名>
/data/user/0/<包名>/files #存放文件数据包名>
/data/user/0/<包名>/databases #存放Sqlite的数据库文件包名>
/data/user/0/<包名>/shared_prefs #存放SharedPreference的数据包名>
/data/user/0/<包名>/cache #存放缓存文件包名>
需要注意的是,在支持多用户之前,应用内部私有存储的路径是/data/data/<包名>,在Android L系统引入多用户功能之后,每个用户对于每个应用都会有一个独立的存储空间,而这个路径等价于机主用户的应用存储空间也就是/data/user/0/<包名>。其他用户比如访客用户,就会以/data/user/10/<包名>路径进行存储。包名>包名>包名>
由于应用内部私有存储的路径都是与应用包名强相关的,所以在应用内部访问这些路径都是通过Context相关API去访问的。
应用内部私有存储路径 | 获取路径的API | 说明 |
---|---|---|
/data/user/0/<包名>包名> | Context#getDataDir | 应用内部私有数据存储根目录 |
/data/user/0/<包名>/files包名> | Context#getFilesDir | 存放文件 |
/data/user/0/<包名>/cache包名> | Context#getCacheDir | 存放缓存文件,在设备内存不足时,系统可能会删除以回收空间 |
/data/user/0/<包名>/foldername包名> | Context#getDir | 存放自定义文件 |
/data/user/0/<包名>/databases包名> | 数据库相关API | 存放数据库文件 |
/data/user/0/<包名>/shared_prefs包名> | SharedPreference相关API | 存放SharedPreference的xml文件 |
应用内部私有存储的特点
- 存储永远都是处于可用状态的
- 只有App自己才能够访问保存的文件
- 一旦App被卸载,系统将会移除内部存储中相关应用的数据
文件读写实例
/data/user/0/<包名>/files 下面文件的读写,可以直接通过Context#openFileOutput写入
如下面的代码,在/data/user/0/<包名>/files 目录下,创建myfile文件,可以通过context.getFilesDir获取包名>包名>
String filename = "myfile";
String fileContents = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(fileContents.getBytes());
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
存储一个内部cache文件
private File getTempFile(Context context, String url) {
File file;
try {
String fileName = Uri.parse(url).getLastPathSegment();
file = File.createTempFile(fileName, null, context.getCacheDir());
} catch (IOException e) {
// Error while creating file
}
return file;
}
外部存储(External File Storage)
外部存储指的是是公共的存储,这部分存储理论上是全局可见的,所有的应用都可以访问这部分数据,一般情况下,路径都是以/storage开头的,比如说/storage/emulated/0就是属于外部存储,这个路径的实际的挂载点是/data/media。又比如外置sdcard的路径为/storage/13FC-0F0B。 相比较内部存储一定会存在,外部存储可能是sdcard或者通过otg挂载的U盘形式,所以可能出现没有挂载的情况,所以所有的外部存储要在使用前通过下面的方式判断是否有被挂载。
检查外部存储是否可读写
/* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}
检查外部存储是否可读
/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}
外部存储可以分为两个部分,一个是外部应用私有存储,另外一个是外部公共存储。
应用访问外部存储需要通过下面的方式在AndroidManifest.xml中申请动态权限,其中WRITE_EXTERNAL_STORAGE包含了读跟写的权限。不过在Android 4.4之后,访问外部应用私有存储不需要进行权限的申请。
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
...
</manifest>
应用外部私有存储
这部分存储的所有权属于应用,一般不会主动向外部提供数据,虽然应用可以通过类似绝对路径这样的方式访问到。 这部分数据会在应用卸载时会被清除,主要涉及的路径见下面的表格:
应用外部私有存储路径 | 获取路径的API | 说明 |
---|---|---|
/storage/emulated/0/Android/data/包名/files/Music | Context#getExternalFilesDir() | 包含如Music,Pictures等文件夹 |
/storage/emulated/0/Android/data/包名/cache | Context#getExternalCacheDir | 外部缓存文件 |
/storage/emulated/0/Android/media/<包名> /storage/13FC-0F0B/Android/media/<包名>包名>包名> |
Context#getExternalMediaDirs | 如果有外部Sdcard,会有对应路径 |
存储到私有路径实例 可以通过在文件夹目录下添加.nomedia防止该目录下的文件被MediaScanner扫描到
public File getPrivateAlbumStorageDir(Context context, String albumName) {
// Get the directory for the app's private pictures directory.
File file = new File(context.getExternalFilesDir(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
外部共有存储
这部分存储一般指的是/storage/emulated/0下面也就是/data/media挂载出来的)除去/storage/emulated/0/Android之外的所有路径,比如storage/emulated/0/Pictures这些文件夹。也包括外置sdcard,通过OTG挂载USB出来的存储,一般路径为/storage/xxxx-xxxx.
外部共有存储的路径跟应用本身没有关系,所以一般都是通过Environment类进行获取。而这些存储的数据也不会随着应用的卸载而丢失。
外部共有存储路径 | 获取路径的API | 说明 |
---|---|---|
/storage/emulated/0 | Environment.getExternalStorageDirectory() | |
/storage/emulated/0/Pictures | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) |
获取图片公有目录,并在内部创建 albumName 文件夹
public File getPublicAlbumStorageDir(String albumName) {
// Get the directory for the user's public pictures directory.
File file = new File(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs()) {
Log.e(LOG_TAG, "Directory not created");
}
return file;
}
应用清除数据和清除缓存
在设置->应用->应用详情->存储页面下面有”清除数据“与”清除缓存”两个菜单,点击之后分别删除了哪些文件呢?
-
清除数据
这个操作将会清除
/data/user/0/<包名>/ /storage/emulated/0/Android/data/<包名>/ 权限授予数据包名>包名> -
清除缓存 点击清除缓存之后,将会对下面路径下的缓存文件进行清除
/data/user/0/<包名>/cache /storage/emulated/0/Android/data/<包名>/cache包名>包名>
关于外置Sdcard
外置Sdcard由于传输速率等原因,虽然到目前还支持,但也逐渐开始被Google放弃,很多厂商也开始不在支持外置Sdcard。
外置SDcard在Android M之后支持两种挂载方式
Portable Storage
这种挂载方式是Android最早支持的外置sdcard的方式,sdcard存粹作为可移动存储的方式进行存储,一般用来存储照片、音乐这样的多媒体文件。外置存储将会以/mnt/media_rw/的形式挂载到系统中
Filesystem Size Used Avail Use% Mounted on
… …
/data/media 24G 3.9G 20G 17% /mnt/runtime/default/emulated
/dev/block/vold/public:179,129 941M 64K 941M 1% /mnt/media_rw/35B6-0CFA
/mnt/media_rw/35B6-0CFA 941M 64K 941M 1% /mnt/runtime/default/35B6-0CFA
如果通过File接口直接在外置sdcard路径下去创建文件,会报下面的错误。
08-10 20:51:04.033 24575 24575 W System.err: java.io.IOException: Permission denied
08-10 20:51:04.033 24575 24575 W System.err: at java.io.UnixFileSystem.createFileExclusively0(Native Method)
08-10 20:51:04.034 24575 24575 W System.err: at java.io.UnixFileSystem.createFileExclusively(UnixFileSystem.java:281)
08-10 20:51:04.034 24575 24575 W System.err: at java.io.File.createNewFile(File.java:1008)
08-10 20:51:04.034 24575 24575 W System.err: at com.ryan.demostore.storagedemo.StorageDemoFragment$17.onClick(StorageDemoFragment.java:337)
08-10 20:51:04.034 24575 24575 W System.err: at android.view.View.performClick(View.java:6597)
08-10 20:51:04.034 24575 24575 W System.err: at android.view.View.performClickInternal(View.java:6574)
08-10 20:51:04.034 24575 24575 W System.err: at android.view.View.access$3100(View.java:778)
08-10 20:51:04.034 24575 24575 W System.err: at android.view.View$PerformClick.run(View.java:25885)
08-10 20:51:04.034 24575 24575 W System.err: at android.os.Handler.handleCallback(Handler.java:873)
08-10 20:51:04.035 24575 24575 W System.err: at android.os.Handler.dispatchMessage(Handler.java:99)
08-10 20:51:04.035 24575 24575 W System.err: at android.os.Looper.loop(Looper.java:193)
08-10 20:51:04.035 24575 24575 W System.err: at android.app.ActivityThread.main(ActivityThread.java:6669)
08-10 20:51:04.035 24575 24575 W System.err: at java.lang.reflect.Method.invoke(Native Method)
08-10 20:51:04.035 24575 24575 W System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
08-10 20:51:04.036 24575 24575 W System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
在Android P之前,带有平台签名的应用在申请android.permission.WRITE_MEDIA_STORAGE权限之后,可以读写外置sdcard文件,而在Android P之后,由于权限策略的修改,即使是平台签名应用也无法读写外置sdcard。所有应用都需要通过DocumentFile的方式去读写。
Adoptable storage
这种挂载方式会讲外置sdcard格式化为加密的存储,替代原先ROM中挂载出来的/data/media,这种sdcard的数据无法被其他机器识别。如下面所示/mnt/expand/735f55e1-4a68-44aa-885c-e04c18cb8534/media将会代替原先/data/media成为系统中的Primary外置存储。
/dev/block/dm-2 24G 3.9G 20G 17% /data
/dev/block/dm-3 895M 1.6M 893M 1% /mnt/expand/735f55e1-4a68-44aa-885c-e04c18cb8534
/mnt/expand/735f55e1-4a68-44aa-885c-e04c18cb8534/media 895M 1.6M 893M 1% /mnt/runtime/default/emulated
所有的存储在data/media应用数据将会迁徙到sdcard上,同时原先ROM中的外部存储空间将会被废弃掉,只能被用户以应用的数据进行填充,无法被用户作为存储如MTP这样的形式使用。这种方式的体验并不好,因为在一般情况下,外置sdcard的读写速度是比不上ROM的存储。即使是EMMC的Flash,读写速度也要比一般的外置sdcard快的多,如果使用外置sdcard作为Primary存储,很容易造成系统的卡顿。
尾记
随着Android版本的更新,存储权限的控制越来越精细,就像在Android P上,应用无法通过file:///这的uri直接共享文件,否则会爆出FileUriExposedException的错误。
据说在Android Q平台,Google对于应用存储的权限做了比较大的变更。传统的 READ_EXTERNAL_STORAGE/WRITE_EXTERNAL_STORAGE 读写权限已经被更加细化的权限替代了,READ_MEDIA_IMAGES 和 READ_MEDIA_VIDEO 分别对应读取图片和视频,授权是会作为一组权限同时授予,而 READ_MEDIA_AUDIO 读取音频权限为单独一组授予,它们会控制应用通过 MediaStore 读取媒体的能力。(此段摘自 https://feng.moe/archives/47/)
参考链接
https://developer.android.com/guide/topics/data/data-storage
https://feng.moe/archives/47/