[翻译]Android教程-保存数据-保存文件

来源:

http://developer.android.com/training/basics/data-storage/files.html

Android使用了一种类似于其它平台上基于磁盘文件系统的文件系统. 本节课描述了如何使用 File API在Android文件系统中读写文件.

File 对象适用于用一种没有跳跃的从开始一直到结尾的方式读写大量数据. 例如,它很适合通过网络进行图片文件或者任何其它的文件交换.

本课程展示了如何在 你的应用中进行文件相关的基础操作. 本课程假定你熟悉Linux文件系统,还有java.io中的标准文件输入/输出操作.

选择内部会外部的存储


所有安卓设备都有两个存储区域: "内部" 和 "外部" 存储. 这些名称来自早期的安卓, 那时候大多数设备都提供内建的非易丢失内存 (内部存储), 再加上一个可移除的存储介质,比如微型SD卡 (外部存储). 一些设备将永久存储空间分成“内部”和“外部”分区, 因此即使没有可移除的存储介质,也总会两个存储空间,而不管外部存储是不是可移除的,API行为都是一样的. 下面的列表总结的每一个存储空间的一些要点.

内部存储:

  • 它总是可用的.
  • 存储在这里的文件默认只能由你的应用才能访问.
  • 当用户卸载你的应用时,系统会从内部存储中移除你的应用的所有文件.

当你想要确保不管是你的用户还是其它应用都能访问你的文件,内部存储是最合适的.

外部存储:

  • 它不总是可以使用的,因为用户可以把外部存储当做USB存储来安装,并且在某些状况下面,也可能把它从设备移除掉.
  • 它从所有地方都是可读的,因此保存在这里的文件可能在你的控制范围之外被读取.
  • 当用户卸载你的应用时,系统只会在假如你将应用文件保存在来自getExternalFilesDir()的目录时,从这儿把它们移除 .

外部存储时保存那些不需要访问限制的文件的最好地方,还有那些你想要同其它应用共享或者允许用户使用计算机来访问的文件 .

提示: 尽管应用默认被安装到内存存储, 其实你还可以在manifest中指定android:installLocation属性,那样你的应用就可以被安装在外部存储上了. 当APK的尺寸非常大,并且用户拥有一个比内存存储大得多的外部存储时,这一选择会受到欢迎. 更多的信息,可以看看 应用存储位置 .

获得外部存储的权限


为了写入外部存储,你必须在你的manifest文件处请求WRITE_EXTERNAL_STORAGE权限 :

<manifest ...>     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />     ... </manifest>

注意: 当前,所有的应用都有能力不需要一个特定的权限就可以读取内部存储. 不过,这将会在将来的版本中改变. 如果你的应用需要读取内部存储(但不去写入它), 那么你将会需要声明 READ_EXTERNAL_STORAGE 权限. 为了确保你的应用能如预期的运作, 在变化还没有起作用之前,你现在就应该声明这一权限.

<manifest ...>     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />     ... </manifest>

不过,如果你的应用使用了 WRITE_EXTERNAL_STORAGE 权限,那么它也就隐含了读取内部存储的权限了 .

在内部存储上保存文件不需要任何权限. 你的应用程序总是有在其内部存储目录中读写文件的权限.

在内部存储中保存一个文件


当要在一个内部存储中保存一个文件时,你可以通过调用下面两个方法的其中之一,来获取相应的目录 文件 :

getFilesDir()

    返回一个表示你应用的内部路径的 File .

getCacheDir()

    返回一个表示你应用的临时缓存内部路径的 File . 要确保一旦文件不再需要时都删除一次,并且在任何给定时间你使用的内存都有一个合理的大小限制, 比如 1MB. 如果系统开始低存储消耗的运行 , 他可能在没有提示就删除了你的缓存文件.

为了在这些目录中的一个里面创建一个新的文件,你可以使用 File() 构造器,传入由上述指定了你的内部存储路径的方法提供的 File . 例如:

File file = new File(context.getFilesDir(), filename);

此外,你还可以调用 openFileOutput() 来获得一个 FileOutputStream , 它会在你的内部路径中写入一个文件 . 例如,这里是如何将一些文本写入一个文件 :

String filename = "myfile"; 
String string = "Hello world!"; 
FileOutputStream outputStream; 
try {   
    outputStream = openFileOutput(filename, Context.MODE_PRIVATE);  
    outputStream.write(string.getBytes());   
    outputStream.close(); 
} catch (Exception e) {   
    e.printStackTrace(); 
}

或者,如果你需要缓存一些文件,你就应该换用 createTempFile(). 例如,下面的方法可以获取名称来自一个 URL 的文件,并使用这个名称在你的应用的内部缓存路径中创建一个文件 :

public 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; 
}

注意: 你的应用的内部存储路径将用你的应用的包名,在Android文件系统的一个特殊位置指定. 技术上,如果你将文件模式设置为可读,那么其它的应用也可以读取你的内部文件. 不过,其它的应用也会需要知道你的应用的报名和文件名. 除非你明确将文件设置为可读或者可写的,其它的应用不能浏览到你的内部路径. 因此一旦你在你内部存储中的文件上使用了 MODE_PRIVATE , 它们就再也不会被其它应用访问到了.

在外部存储上保存一个文件


由于外部存储可能不可用——比如用户已经将其挂载到了一台PC上,或者已经将提供外部存储的SD卡移除——你应该在访问它之前总是去验证一下其可用性 . 你可以调用 getExternalStorageState() 来检查外部存储的状态. 如果返回的状态是 MEDIA_MOUNTED, 那么你就可以读写你的文件 . 例如,下面的方法在决定存储可用性方面会很有用 :

/* 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; 
}

尽管外部存储可以被用户和其它应用修改,还是有两类文件你可以保存在这里 :

  • 那些可以被其它应用和用户自由使用的公共文件 . 当用户卸载你的应用时,这些文件留下来继续给用户使用 .例如,你的应用拍下来的照片和其它下载下来的文件 .
  • 那些理应属于你的应用的私有文件,并且在用户卸载你的应用时也应一并删除. 尽管这些文件技术上由于存储在外部存储上,可以为用户和其它应用访问,它们实际上却不会在你的应用之外为用户提供价值 . 当用户卸载你的应用,系统会删除你的应用程序外部私有路径下的所有文件 .例如,你的应用下载的额外的资源或者临时媒体文件 .

如果你想要在外部存储上保存公共的文件,使用 getExternalStoragePublicDirectory() 方法来获取一个表示外部存储上对应文件的 File . 该方法会得到一个指定你想要保存的文件类型的参数,那样它们就可以在本地同其它公共文件组织在一起,比如 DIRECTORY_MUSIC 或者 DIRECTORY_PICTURES. 例如 :

public File getAlbumStorageDir(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; 
}

如果你想要保存为你应用所私有的文件,你可以通过调用getExternalFilesDir()并传入指定你想要的路径类型的名称来获取相应的路径 . 每一个通过此方法创建路径都被添加到了一个所有你的应用程序的外部存储文件的父路径 , 它们会在用户卸载你的应用时被系统删除掉 .

例如,这里有一个你可以用来为一个单独的相册创建一个路径的方法 :

public File getAlbumStorageDir(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; 
}

如果之前定义的子路径名称没有一个适合你的文件,你可以调用 getExternalFilesDir() 并传入null. 这回返回外部存储上你的应用的私有路径的根路径 .

请记住当用户卸载你的应用时,getExternalFilesDir() 在一个路径中创建的路径都会被删除掉 . 如果你想要存储的文件在用户卸载你的应用后还能用 — 诸如当你的应用是一个照相机,而用户想要保留这些照片时 — 你应该换用 getExternalStoragePublicDirectory().

不管你是使用文件可以被共享的 getExternalStoragePublicDirectory() 或者文件为你的应用所私有的 getExternalFilesDir() , 你所使用的由API提供像DIRECTORY_PICTURES这样的常量都很重要 . 这些路径名称确保了文件为系统正常对待 . 例如,存储在 DIRECTORY_RINGTONES 中的文件可以被系统的媒体搜索器归为铃声一类,而不是音乐 .

查询空闲空间


如果你事先知道要保存多少数据,你就可以通过调用 getFreeSpace() 或者 getTotalSpace() 发现是否有足够空间保存这些数据,而不会导致一个 IOException . 这些方法分别提供了存储卷中当前有多少可用空间以及总空间. 这种信息在避免填充的数据量超过一定的阈值时也同样有用 .

不过,系统并不能确保你可以写入同 getFreeSpace() 所获取到的剩余空间大小同等量的数据. 如果返回的数量比你想要保存的数据多几个MB,或者如果文件系统占率低于90%,那么往往还算安全。否则,你可能就不应该再往里面写入了.

注意: 你并不一定要在保存你的文件之前检查剩余空间的数量. 你可以尝试首先写入文件,然后获取一个 IOException ,如果这个异常发生了的话 . 如果你并不知道你需要多少空间的时候,可能就得这么做 . 例如,如果你在保存文件之前改变了文件的编码方式,将一张PNG图片转换成了JPG的,你是不会事先知道文件的大小的 .

删除一个文件


你应该总是删除你不再需要的文件。删除一个文件最直接的方式是让打开的文件引用自身调用 delete() .

myFile.delete();

如果文件被保存在外部存储上,你也可以通过调用deleteFile()叫 Context来定位并删除一个文件 :

myContext.deleteFile(fileName);

注意: 当用户卸载你的应用时,Android系统会删除下面这些东西 :

  • 所有你保存在外部存储中的文件
  • 所有你使用 getExternalFilesDir() 保存在内部存储上的文件.

不过,你应该定期手动的去删除使用 getCacheDir() 创建的缓存文件,也要有规律的去删除你不再需要的文件.

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏技术点滴

远程线程注入引出的问题

远程线程注入引出的问题 一、远程线程注入基本原理 远程线程注入——相信对Windows底层编程和系统安全熟悉的人并不陌生,其主要核心在于一个Windows AP...

297100
来自专栏刘望舒

带你彻底了解Android Jetpack组件的Paging库

初次接除 paging, 可能会一脸懵逼,感觉出来了很多 API, 不知道从哪里下手。我们先对 paging 的组成部分进行一个了解。

26220
来自专栏技术之路

将某个Qt4项目升级到Qt5遇到的问题[转]

该Qt4项目以前是使用Qt4.7.4 MSVC2008开发的,因为使用到了OWC10(Office Web Components),使用MSVC编译器的话无法正...

25390
来自专栏Jackson0714

sys.dm_db_wait_stats

385120
来自专栏ASP.NET MVC5 后台权限管理系统

ASP.NET MVC5+EF6+EasyUI 后台管理系统(70)-微信公众平台开发-成为开发者

前言: 一、阅读这段系列之前,你必须花半天时间大致阅读微信公众平台的API文档,我尽量以简短快速的语言与大家分享一个过程 二、借助微信公众平台SDK Sen...

35160
来自专栏kongkong的专栏

Nodejs调用 SRF/TAF 服务记录

组里面前后端交互使用的是SRF框架, 基于TAF的RPC框架, 在我的理解里无论是SRF还是TAF其实和protobuf, thrift差不多, 就是前台作为R...

63260
来自专栏向治洪

Android监听自身卸载,弹出用户反馈调查

1,情景分析         在上上篇博客中我写了一下NDK开发实践项目,使用开源的LAME库转码MP3,作为前面几篇基础博客的加深理解使用的,但是这样的项目用...

34950
来自专栏ASP.NET MVC5 后台权限管理系统

ASP.NET MVC5+EF6+EasyUI 后台管理系统(70)-微信公众平台开发-成为开发者

前言: 一、阅读这段系列之前,你必须花半天时间大致阅读微信公众平台的API文档,我尽量以简短快速的语言与大家分享一个过程 二、借助微信公众平台SDK Sen...

27360
来自专栏小白鼠

ZookeeperZNode基本命令四字命令SessionWatcherACLZookeeper集群Paxos算法ZAB协议Curator分布式锁

在Zookeeper中,ZNode可以分为持久节点和临时节点两类。所谓持久节点是指一旦这个ZNode被创建了,除非主动进行ZNode的移除操作,否则这个ZNod...

12830
来自专栏Linux驱动

9.按键之使用异步通知(详解)

之前学的应用层都是: 1)查询方式:一直读 2)中断方式.同样一直读,直到中断进程唤醒 3)poll机制:一直在poll函数中睡眠,一定时间读一次 以上3种,我...

21590

扫码关注云+社区

领取腾讯云代金券