最近碰到一个项目要用到Android文件的存取,之前也一直没有完全搞清楚。最近整理了一下给大家一起分享,也希望大家如果遇到这方面的问题少走一些弯路。
前面两小节主要是一些java的基础如果基础比较好的同学可以直接跳过看后面两节。
Demo下载地址
关于Java I/O流
为什么讲这个是因为我觉得会有很多和我一样Java基础不扎实的小伙伴在看到java.io下面这么多输入输出流类实在是没有头绪不知道该用哪个,所以我这里通过查阅的资料给大家大概讲一讲。
Java中的流是对字节序列的抽象,可作为一个输入源,也可作为一个输出的目的地。主要有两种:字节流和字符流。
- 字节流处理的基本单位为单个字节,通常用来处理二进制数据也就是说我们用它来处理文件。
- 字符流最基本的单元是Unicode码元(大小2字节),通常用来处理文本数据。
JDK所提供的所有流类位于java.io包中,都分别继承自以下四种抽象流类:
InputStream
:继承自InputStream的流都是用于向程序中输入数据的,且数据单位都是字节(8位)。
OutputSteam
:继承自OutputStream的流都是程序用于向外输出数据的,且数据单位都是字节(8位)。
Reader
:继承自Reader的流都是用于向程序中输入数据的,且数据单位都是字符(16位)。
Writer
:继承自Writer的流都是程序用于向外输出数据的,且数据单位都是字符(16位)。
这样大家应该再看这些class的时候就大概有方向了。
File类
既然我们要进行文件读写那我们就要对我们操作的类深入理解。
An abstract representation of file and directory pathnames.User interfaces and operating systems use system-dependent pathname strings to name files and directories. This class presents an abstract, system-independent view of hierarchical pathnames.
这个是官方API的解释,大概翻译一下就是:
这个类是文件和目录路径名的抽象表示。它提供了一个抽象的独立系统分层的路径名视图。用户界面和操作系统使用与系统相关的路径名字符串来命名文件和目录。
为什么说与系统相关呢。主要有两个
- 一个是依赖系统的前缀字符串,例如磁盘驱动说明符,UNIX根目录为“/”等等。
- 另一个就是分割符了这个很显然也跟系统相关。
当然了我们Android不用考虑那么多,我们只需要注意分隔符,而这个分隔符File类也帮我们申请好了File.separator
我们new 一个File官方给了四个构造方法File(String pathname)
,File(String parent,String child)
,File(File parent,String child)
,FIle(URI uri)
。这四个方法也非常好理解我这里也不详细说明了。
FIle类里面的方法也无非是一些对文件的判断和一些基本操作,大家可以看一下API文档,相信机智的你一定看的懂的。但是有几点要注意:
- 文件是分区的,不同区域是有不同的权限。这一点我在下一节将会详细讲解。
- File类的实例是不可变的,也就是说一旦创建,由File对象表示的抽象路径名将永远不会改变。
- 在Android上,文件名的基础文件系统编码始终是UTF-8。
Android的内部储存和外部储存
既然我们要操作文件那我们还要知道我们要把文件放在哪。那文件放在哪里那就跟Android系统有关了。Android把我们手机分为内部储存和外部储存。我们来看一下官方的解释:
Internal storage:
- It's always available.
- Files saved here are accessible by only your app.
- When the user uninstalls your app, the system removes all your app's files from internal storage.
External storage:
- It's not always available, because the user can mount the external storage as USB storage and in some cases remove it from the device.
- It's world-readable, so files saved here may be read outside of your control.
- When the user uninstalls your app, the system removes your app's files from here only if you save them in the directory from
getExternalFilesDir()
大家可以自行谷歌翻译一下,我给大家提炼一下:
内部储存是指只有自己应用才能访问,并且如果应用删除了,那么这些文件也就跟着一起删除了。
外部储存是整个手机都可以访问的所以在内容上它是不稳定的,因为只要有读写的权限就可以修改这些文件。(关于权限我会在最后一节给大家详解)而在硬件上来说它也有可能是不稳定的(比如sd卡)。
所以这就和大家以前所理解的概念不同了,不能单纯的以为不是sd卡就是内部储存。
操作内部储存:
获得内部储存文件夹的方法都在Cntext这个类中,主要有以下几个方法:
-
getFilesDir()
:这个返回的是内部储存的根目录的绝对路径。 -
getCacheDir()
这个方法返回的是内部储存缓存目录的绝对路径。 -
getDir(String name, int mode)
:检索,根据需要创建一个新目录,应用程序可以在其中放置自己的自定义数据文件。后面那个参数mode
一般写0或者MODE_PRIVATE
。
给大家看一下这些目录具体在哪里
Log.e("File","内部缓存自定义目录 : "+getDir("getDir",MODE_PRIVATE));
Log.e("File","内部储存的根目录 : "+getFilesDir());
Log.e("File","内部储存缓存目录 : "+getCacheDir());
Log.e("File" ,"内部储存缓存代码目录 : "+getCodeCacheDir());
以下是Log:
E/File: 内部缓存自定义目录 : /data/data/com.kachidoki.learnfiletest/app_getDir
E/File: 内部储存的根目录 : /data/data/com.kachidoki.learnfiletest/files
E/File: 内部储存缓存目录 : /data/data/com.kachidoki.learnfiletest/cache
E/File: 内部储存缓存代码目录 : /data/data/com.kachidoki.learnfiletest/code_cache
这些返回的目录都是在data/data/包名
下,这个目录用户是不给用户看的,你的手机如果没有root通过文件管理看不到。
Android给操作内部储存文件提供了一个简便的方法也在Context
这个类中,FileOutputStream openFileOutput (String name, int mode)
。这个方法返回一个FileOutputStream
方便我们操作。后面那个mode
参数默认操作使用0或MODE_PRIVATE
表示会创建一个文件或者直接替换原有的文件。也可以使用MODE_APPEND
直接加到现有文件中。
下面是操作内部储存的Demo:
/** * 写在内部储存Demo */private void createPrivateFile(){
String filename = "myfile.txt";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes());
outputStream.close();
Toast.makeText(MainActivity.this,"文件位置在"+getFilesDir().getAbsolutePath(),Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this,"出错误了哦",Toast.LENGTH_SHORT).show();
}
}
操作外部储存
外部储存呢分为两个部分但是操作是一样的,功能上有一些区别:
- 公共文件
获得这些目录的方法都在Environment
这个类中
getExternalStoragePublicDirectory(String type)
:这是用户通常放置和管理自己的文件的地方,所以你应该小心放在这里,以确保你不要删除他们的文件或妨碍自己的组织的方式。这里的type
可以用DIRECTORY_MUSIC
,DIRECTORY_MOVIES
这些Environment中的常量。
Log.e("File","外部储存的根目录一般不用:"+Environment.getExternalStorageDirectory());
Log.e("File","外部储存的公共文件根目录: "+Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES));
Log.e("File","外部储存的状态: "+Environment.getExternalStorageState());
Log.e("File","下载缓存目录 :"+Environment.getDownloadCacheDirectory());
Log:
E/File: 外部储存的根目录一般不用:/storage/emulated/0
E/File: 外部储存的公共文件根目录: /storage/emulated/0/Pictures
E/File: 外部储存的状态: mounted
E/File: 下载缓存目录 :/cache
要写在这部分需要读写的权限,并且官方建议用之前先判断一下外部储存的状态,下面是从官方抄来的:
/* 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;
}
- 私有部分
获得这些目录的方法都在Context
。怎么又回到Context
了呢。因为这些文件也会在应用删除的时候跟随这一起删掉~只是和内部储存不同的是这个部分可以给用户和其他应用访问。所以才叫外部储存的私有部分嘛。
Log.e("File","外部储存私有部分根目录 : "+ getExternalFilesDir(null));
Log.e("File","外部储存私有部分音乐 : "+ getExternalFilesDir(Environment.DIRECTORY_MUSIC));
Log.e("File","外部储存私有部分缓存 : "+getExternalCacheDir());
Log:
E/File: 外部储存私有部分根目录 : /storage/emulated/0/Android/data/com.kachidoki.learnfiletest/files
E/File: 外部储存私有部分音乐 : /storage/emulated/0/Android/data/com.kachidoki.learnfiletest/files/Music
E/File: 外部储存私有部分缓存 : /storage/emulated/0/Android/data/com.kachidoki.learnfiletest/cache
以下是操作外部储存的Demo:
/**
* 写在外部储存应用专属位置demo
*/
public void createExternalStoragePrivateFile(){
File file = new File(getExternalFilesDir(null),"demoFile.jpg");
try {
InputStream is =getResources().openRawResource(R.drawable.supreme);
OutputStream os = new FileOutputStream(file);
byte[] data = new byte[is.available()];
is.read(data);
os.write(data);
os.close();
is.close();
Toast.makeText(MainActivity.this,"图片位置在"+getExternalFilesDir(null),Toast.LENGTH_LONG).show();
} catch (Exception e) {
e.printStackTrace();
Toast.makeText(MainActivity.this,"出错误了哦",Toast.LENGTH_SHORT).show();
}
}
public void deleteExternalStoragePrivateFile() {
// Get path for the file on external storage. If external
// storage is not currently mounted this will fail.
File file = new File(getExternalFilesDir(null), "demoFile.jpg");
if (file != null) {
file.delete();
}
}
最后呢再给大家摘一段API在介绍Environment.getExternalStorageDirectory()
这个方法的时候特地写的一段文字来加深大家对外部储存的理解,大家自己翻译体会一下:
Note: don't be confused by the word "external" here. This directory can better be thought as media/shared storage. It is a filesystem that can hold a relatively large amount of data and that is shared across all applications (does not enforce permissions). Traditionally this is an SD card, but it may also be implemented as built-in storage in a device that is distinct from the protected internal storage and can be mounted as a filesystem on a computer.
权限问题
涉及到操作文件肯定会有权限的问题,所以这里我是一定要讲滴。
内部储存:无需任何权限,即可在内部存储中保存文件。 您的应用始终具有在其内部存储目录中进行读写的权限。
外部储存私有部分:以
Context.getExternalFilesDir
为首的一系列方法,从Android4.4开始不需要WRITE_EXTERNAL_STORAGE
和/或READ_EXTERNAL_STORAGE
权限。(之前的版本还是需要)
但是,如果要访问其他软件的外部储存的私有部分那就还是需要这两个权限(前提是你要知道别的包名,就是路径嘛)。外部储存公共部分:这部分肯定是需要权限的嘛~~~~就是需要在
manifest
声明READ_EXTERNAL_STORAGE
,WRITE_EXTERNAL_STORAGE
。
注:如果您的应用使用WRITE_EXTERNAL_STORAGE
权限,那么它也隐含读取外部存储的权限。
还有一点要注意,如果你的app的targetSdkVersion
大于等于23,也就是说大于Android6.0的版本那你还要在读写文件之前还要动态申请Runtime权限,否则你的程序默认是没有读写权限的。关于这个如果不了解的可以看一下下面这篇文章写的比较易懂:
聊一聊Android 6.0的运行时权限
最后
放一下文章里的Demo,我的Demo里面还有运用Retrofit+Rxjava实现下载文件并显示进度的例子哦,如果也有小伙伴正在为这个苦恼的话可以参考一下~
Demo下载地址
本人水平有限,如果文章中出现什么问题欢迎及时指出大家一起探讨一同进步,有什么问题可以留言,可以私信。
参考文档:
保存文件
Context.Class
Environment.Class
File.Class
android中的文件操作详解以及内部存储和外部存储