说起文件存储,我们可能听说过Android有两个存储区域,分别是内部存储和外部存储,直觉上可能理解成内部存储是手机内置存储,外部存储是指可插拔的SD卡,实际不是这样的理解的,内部和外部并不是以物理来划分,而是以逻辑划分。平时接触较多的Windows系统磁盘划分来理解,Android的内部存储类似于Windows的系统盘,因是Android系统文件和应用安装目录的位置,因此对权限要求严格,除非root,否则用户看不到这个目录,而外部存储是内部存储之外的区域,类似于系统盘之外的其他存储区域。
我们来具体看看区别
-
内部存储:
应用程序在内部存储路径空间的路径为
data/data/应用对应的包名
,该目录只有本程序能访问,其他程序没有权限访问;该目录和应用深度绑定,当应用卸载时,该目录一并删除,不过应用升级更新不会删除该目录;另外内部存储空间有限,文件太多太大影响系统的使用,这就像Windows系统盘文件爆满时影响系统运行,因此尽量别贪图方便往内部存储塞大文件,所谓的贪图方便是操作内部存储不需要开发者申请权限,而操作外部存储多一步申请权限的步骤。
我们来看一下获取内部存储路径的方法,包括
-
:获取应用的目录getApplicationInfo().dataDir
-
:获取应用的目录,上个API一样context.getDataDir()
-
:获取应用目录下的Cache文件夹路径context.getCacheDir()
-
:获取应用目录下的Files文件夹路径context.getFilesDir()
,但返回的居然不是com.test.storagedemo
,而是data/data/package
data/user/0/package
?!
开始我也怀疑是不是搞错了,后来才知道,Android 6.0之后因支持多用户,获取内部存储路径的指令不再返回
,而是返回data/data/package
,其中0是用户编号,每个用户拥有自己的私人数据空间,用户0只能访问/data/user/0/package
下的数据,用户1只能访问/data/user/0/
下的数据。/data/user/1/
变成了快捷方式,返回当前用户的路径,也就是说,如果是用户0在用,则data/data/package
链接到data/data/package
。/data/user/0/package
getApplicationInfo().dataDir:/data/user/0/com.test.storagedemo context.getCacheDir():/data/user/0/com.test.storagedemo/cache context.getFilesDir():/data/user/0/com.test.storagedemo/files context.getDataDir():/data/user/0/com.test.storagedemo
-
-
外部存储:
内部存储之外统称为外部存储,外部存储分为公共目录和私有目录,公共目录包括Download,DCIM,Music等,私有目录是名为Android的文件夹,该文件夹的子目录是data,而data目录下则是包名文件夹,这个目录和内部存储程序文件目录长起来差不多,其实就是程序在外部存储的私有目录,和内部存储不同的是,这里虽然是私有目录,但其他程序只要知道这个目录,都可以访问该目录,是程序在外部存储弄的一个程序自留地,目的也是为了方便开发和管理文件,方便开发是指从Android 4.4 开始,操作外部存储的私有目录不需要申请
或READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
权限,而方便管理是指私有目录会随着应用的卸载一并删除,这样不会因为卸载留下垃圾文件,避免用户反感。
不过操作公共目录仍需要申请
或READ_EXTERNAL_STORAGE
WRITE_EXTERNAL_STORAGE
,申请的方法是在AndroidManifest.xml添加权限申请
操作外部存储也有对应的API,分别是
-
:判断外置存储是否挂载着,因为外部存储可能是可插拔的SD卡,在使用之前先检查是否挂载是一个增加程序健壮性的方法;Environment.getExternalStorageState
-
:获取外部存储目录Environment.getExternalStorageDirectory
-
:获取外部存储DCIM目录,如果没有DCIM则会创建;Environment.getExternalStoragePublicDirector(Environment.DIRECTORY_DCIM
-
:获取外部存储私有目录下的Cache目录;context.getExternalCacheDir()
-
:获取外部存储私有目录下Pictures目录;context.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
运行一下这些API,得到如下结果
Environment.getExternalStorageState:mounted
Environment.getExternalStorageDirectory:/storage/emulated/0
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM:/storage/emulated/0/DCIM
context.getExternalCacheDir():/storage/emulated/0/Android/data/com.test.storagedemo/cache
context.getExternalFilesDir(Environment.DIRECTORY_PICTURES):/storage/emulated/0/Android/data/com.test.storagedemo/files/Pictures
结合运行结果,可以反过来看一下API,不知道大家有没有注意到,公共目录是用
Environment
API获取,私有目录是用
Context
获取,
Context
是应用的上下文,可直接获取到包名,因此包括内部存储,带包名的使用
Context
获取,没有包名则使用
Environment
获取。
因我的环境是在虚拟机上运行,路径中是emulated,在手机上运行可能返回结果不一样。
再将以上内容总结一下:
- Android存储可分为内部存储和外部存储,程序访问自己内部存储目录不需要申请权限,其他程序无法访问本程序的内部存储,内部存储空间有限,大文件尽量别往内部存储保存,程序内部存储随着程序的卸载而删除;
- 外部存储分为公共目录和私有目录,访问外部存储需要申请权限,不管公共目录还是私有目录,任何程序都可以访问,程序外部存储的私有目录同样会随着应用的卸载一并删除;
- 访问带包名的目录使用
API,访问不带包名的使用Context
API;Environment
- Android 6.0后,因支持多用户,因此操作内部存储不是返回
,而是返回data/data/package
,前者变成一个快捷方式,链接到后者。data/user/0/package
说了半天内部存储和外部存储,好像还没提到该怎么存储文件,其实理解内部存储和外部存储,可更好的理解存储API的调用,文件存储API调用是相对简单的,如下是一个在内部存储存下一个文件和从内部存储取出一个文件的代码,存储使用的是
openFileOutput
,内部存储路径是相对固定的,函数不需要指定路径,只需要给一个文件名即可。取数据使用
openFileInput
,同样需要指定文件名即可。从这两个函数可以实际体验到为什么使用内部存储方便,一个是不用申请权限,二是连路径都不用指定。
public void save(){
String data = "Data for Save";
FileOutputStream out = null;
ButteredWriter writer = null;
try{
//第一个参数 文件名;第二个参数 文件操作模式; 返回 FileOutputStream对象
out = openFileOutput("data",Context.MODE_PRIVATE);
//实例化一个ButterWriter
writer = new ButteredWriter(new OutputSreamWriter(out));
writer.write(data);
}catch(IOException e){
e.printStackTrace();
}finally{
try{
if(writer!=null){
writer.close();
}
}catch(IOException e){
e.printStackTrace();
}
}
public String load() {
FileInputStream in = null;
BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
//传入文件名参数,返回FileInputStream对象
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in));
String line = "";
while ((line = reader.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
}
函数写好之后,调用
save()
保存,再调用
load()
加载即可打印出信息。使用Android Studio的Device Explorer找到
data/data/com.test.storagedemo/files
目录,可看到刚才存储的名为data的文件。
