Android文件读写探索

最近碰到一个项目要用到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的解释,大概翻译一下就是:
这个类是文件和目录路径名的抽象表示。它提供了一个抽象的独立系统分层的路径名视图。用户界面和操作系统使用与系统相关的路径名字符串来命名文件和目录。

为什么说与系统相关呢。主要有两个

  1. 一个是依赖系统的前缀字符串,例如磁盘驱动说明符,UNIX根目录为“/”等等。
  2. 另一个就是分割符了这个很显然也跟系统相关。

当然了我们Android不用考虑那么多,我们只需要注意分隔符,而这个分隔符File类也帮我们申请好了File.separator
我们new 一个File官方给了四个构造方法File(String pathname)File(String parent,String child),File(File parent,String child),FIle(URI uri)。这四个方法也非常好理解我这里也不详细说明了。
FIle类里面的方法也无非是一些对文件的判断和一些基本操作,大家可以看一下API文档,相信机智的你一定看的懂的。但是有几点要注意:

  1. 文件是分区的,不同区域是有不同的权限。这一点我在下一节将会详细讲解。
  2. File类的实例是不可变的,也就是说一旦创建,由File对象表示的抽象路径名将永远不会改变。
  3. 在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中的文件操作详解以及内部存储和外部存储

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容