Android 文件存储

一、前言

最近在项目不是很忙的时候,回顾Android系统的文件存储功能,结合自己的经验,也查阅了好多相关的文档,发现了好多好文章,真的非常感谢,写的挺好的。

一直以来,恐怕都有一大堆的问题困扰着Android初学者,比如说:

  • 内存,内部存储,外部存储,概念的区分?
  • 内部存储都包含哪些目录?外部存储又包含哪些?
  • 清除APP的缓存,都删除了哪些文件目录呢?
  • 清除APP的数据,又删除了哪些文件目录呢?
  • 哪些文件目录是APP私有的,哪些又是公共的目录?
  • SharedPreferences、数据库一般都存储在什么目录?

今天尽我所能的表述一下这些知识点,如有遗漏或者错误,请指正。

二、存储分类

2.1 内部存储

内部存储空间是 App 私有的存储数据的存储空间,系统会阻止其他应用对这部分数据的访问 (除非有root权限),并且在 Android 10(API 级别 29)及更高版本中,系统会对这些位置进行加密。 内部存储空间的特性让它很适合存储只有 App 本身才能访问的敏感数据。

内部存储空间可以通过 Context.getFileDir()Context.getCacheDir() 获取到,主要路径是:

Context.getFileDir() 获取的路径为:

  • data/data/packagename/files (部分手机厂商)
  • data/user/0/packagename/files (部分手机厂商)

Context.getCacheDir() 获取的路径为:

  • data/data/packagename/cache (部分手机厂商)
  • data/user/0/packagename/cache (部分手机厂商)

ps: 该目录内的文件在设备内存不足时会优先被删除掉,所以存放在这里的文件是没有任何保障的,可能会随时丢掉。

最后附上一张截图,此图问内部存储目录

2.2 外部存储

外部存储空间包括 App 私有目录和公共目录。

  • App 私有目录: App 的私有目录虽然名曰私有目录,但是其他应用可以通过绝对路径访问当前目录下的数据,应用卸载后也会随之删除。
  • 公共目录:外部可以自由访问,应用删除后这部分存储的数据不会删除。

2.2.1 APP私有目录

// 可以通过以下函数获取
Context.externalCacheDir
Context.externalCacheDirs
Context.getExternalFilesDir(String)
Context.getExternalFilesDirs(String)
Context.externalMediaDirs

得到的对应目录是:

externalCacheDir: /storage/emulated/0/Android/data/packagename/cache
externalCacheDirs: /storage/emulated/0/Android/data/packagename/cache
ExternalFilesDir: /storage/emulated/0/Android/data/packagename/files
ExternalFilesDirs: /storage/emulated/0/Android/data/packagename/files
externalMediaDirs: /storage/emulated/0/Android/media/packagename

附上一张外部存储的私有目录截图:

可能会有人问这不是"/mnt/sdcard/Android/datapackagename/"吗,和上述的 "/storage/emulated/0/Android/data/packagename/"目录不同啊,后续会提到的。

2.2.2 外部公共目录

不要被这里的“外部”这个词弄糊涂了。最好将此目录视为媒体/共享的存储部分。它是一个文件系统,可以保存相对大量的数据,并且在所有应用程序之间共享(不强制执行权限)。传统上这是一张 SD 卡,但它也可以作为设备中的内置存储实现,与受保护的内部存储不同,并且可以作为文件系统安装在计算机上。

在具有多个用户的设备上(如 UserManager 所述),每个用户都有自己的隔离共享存储。应用程序只能访问它们正在运行的用户的共享存储。

获取方式:

Environment.getExternalStorageState() // SD 卡状态
Environment.getExternalStorageDirectory()
Environment.getExternalStoragePublicDirectory(String)

输出内容:

getExternalStorageState: mounted // 已挂载
getExternalStorageDirectory: /storage/emulated/0 
getExternalStoragePublicDirectory: /storage/emulated/0

根据google官方文档提示,getExternalStorageDirectorygetExternalStoragePublicDirectory已经被标记为弃用,可以使用 Context.getExternalFilesDir(String)MediaStoreIntent.ACTION_OPEN_DOCUMENT等替代方案,它们性能更好。

在上述的需要传递 String 参数的方法中,例如Context.getExternalFilesDir(String)getExternalStoragePublicDirectory(String),String 有以下几个常量值:

  • DIRECTORY_MUSIC // 音乐
  • DIRECTORY_PODCASTS // 博客
  • DIRECTORY_RINGTONES // 铃声
  • DIRECTORY_ALARMS // 闹钟
  • DIRECTORY_NOTIFICATIONS // 通知
  • DIRECTORY_PICTURES // 图片
  • DIRECTORY_MOVIES // 电影
  • DIRECTORY_DOWNLOADS // 下载
  • DIRECTORY_DCIM // 照片
  • DIRECTORY_DOCUMENTS // 文档

附上一张外部公共目录的截图:

39.png

2.3 系统目录

Environment 还提供了对一些系统目录的访问方法:
Environment.getRootDirectory()  // 系统分区的 root 路径
Environment.getDataDirectory()  // 获取用户数据目录的路径
Environment.getDownloadCacheDirectory() // 获取用户缓存目录的路径

// 输出为

getRootDirectory: /system
getDataDirectory: /data
getDownloadCacheDirectory: /data/cache

三、问题解答

3.1 内存,内部存储,外部存储,概念的区分?内部存储都包含哪些目录?外部存储又包含哪些?

1、内存(Memory/RAM):

  • 内存(Memory)是计算机的重要部件,也称内存储器和主存储器,拥有极高的读写速度,用于暂时存放CPU中的运算数据,以及与硬盘等外部存储器交换的数据。

  • 它是外存与CPU进行沟通的桥梁,计算机中所有程序的运行都在内存中进行,内存性能的强弱影响计算机整体发挥的水平。

  • 只要计算机开始运行,操作系统就会把需要运算的数据从内存调到CPU中进行运算,当运算完成,CPU将结果传送出来。

2、内部存储

  • 注意内部存储不是内存。

  • 内部存储位于系统中很特殊的一个位置,如果你想将文件存储于内部存储中,那么文件默认只能被你的应用访问到,且一个应用所创建的所有文件都在和应用包名相同的目录下。也就是说应用创建于内部存储的文件,与这个应用是关联起来的。

  • 当一个应用卸载之后,内部存储中的这些文件也被删除。

  • 从技术上来讲,如果你在创建内部存储文件的时候将文件属性设置成可读,其他app能够访问自己应用的数据,前提是他知道你这个应用的包名,如果一个文件的属性是私有(private),那么即使知道包名其他应用也无法访问。

  • 内部存储空间十分有限,因而显得可贵,另外,它也是系统本身和系统应用程序主要的数据存储所在地,一旦内部存储空间耗尽,手机也就无法使用了。所以对于内部存储空间,我们要尽量避免使用。Shared Preferences和SQLite数据库都是存储在内部存储空间上的。内部存储一般用Context来获取和操作。

3、外部存储

  • 最容易混淆的是外部存储,因为老的Android系统的跟新的Android系统是有差别的,很多人去网上查找资料,看了一下以前的资料,又看了一下现在的资料,但是发现它们说法不一样然后就困惑了。

  • 首先说一个大家普遍的概念:如果在pc机上是区分外部存储和内部存储的话,那么电脑自带的硬盘算是内部存储,U盘或者移动硬盘就是外部存储了。 因此很多人带着这样的理解去看待安卓手机,把内置存储(机身存储)当做内部存储,而把扩展的SD卡当做是外部存储。

  • 这么认为确实没错,因为在4.4(API19)以前的手机上确实是这样的,手机自身带的存储卡就是内部存储,而扩展的SD卡就是外部存储。但是从4.4的系统开始,很多的中高端机器都将自己的机身存储扩展到了8G以上,比如有的人的手机是16G的,有的人的手机是32G的,但是这个16G,32G是内部存储吗,不是的!!!,它们依然是外部存储,也就是说4.4系统及以上的手机将机身存储存储(手机自身带的存储叫做机身存储)在概念上分成了 内部存储internal外部存储external两部分。

  • 既然16G,32G是外部存储,那有人又有疑惑了,那4.4系统及以上的手机要是插了SD卡呢,SD卡又是什么呢,如果SD卡也是外部存储的话,那怎么区分机身存储的外部存储跟SD卡的外部存储呢?对,SD卡也是外部存储,那怎么区分呢,在4.4以后的系统中,API提供了这样一个方法来遍历手机的外部存储路径:

File[] files;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    files = getExternalFilesDirs(Environment.MEDIA_MOUNTED);
    for(File file:files){
        Log.e("main",file);
    }
}

如果你的手机插了SD卡的话,那么它打印的路径就有两条了,例如华为荣耀7插了SD卡,它的结果如下:

/storage/emulated/0/Android/data/packname/files/mounted
/storage/B3E4-1711/Android/data/packname/files/mounted

其中 /storage/emulated/0 目录就是机身存储的外部存储路径,而 /storage/B3E4-1711/ 就是SD卡的路径,他们统称为外部存储,那么,访问外部存储的API方法有:

 (1)Environment.getExternalStorageDirectory().getAbsolutePath()
 (2)Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath()
 (3)getExternalFilesDir(“”).getAbsolutePath()
 (4)getExternalCacheDir().getAbsolutePath()

大家对Android的外部存储会产生疑问,主要是现在很多的手机已经从物理上看不到外部存储了,以前的手机都有,就是那种黑色的内存卡,8G,16G,32G的,可以像U盘一样插拔,以前很流行,存储空间不够了,就去买个内存卡(准确说是SD卡,说成内存卡又会引起误解)回来,后来的手机比如现在我用的华为荣耀7,厂家已经把机身存储扩展到了16G了,只是在存储概念上了分为了内部存储(内部internal)外部存储(外部external),其实它们都集成在一起了。
当然如果你觉得16G不够用,那他支持通过插SD卡来扩充容量吗?支持的,荣耀7为例,它是三合二卡槽。卡槽1:Nano SIM卡;卡槽2:Nano SIM卡或Micro SD卡。默认卡槽1为4G主卡,可以在设置中更改4G主卡卡槽;不支持热插拔,插拔卡托后需重启手机。这样插入的SD卡也属于外部存储。

  • 所以手机的外部存储可能包含两部分,一是机身存储的外部存储部分,还有一个是SD卡部分
  • 那么外部存储的目录在哪呢,在storage文件夹中有一个 sdcard文件夹,这个文件夹中的文件又分为两类,一类是公有目录,还有一类是私有目录,其中的公有目录有九大类,比如DCIMDOWNLOAD等这种系统为我们创建的文件夹,私有目录就是Android这个文件夹,这个文件夹打开之后里边有一个data文件夹,打开这个data文件夹,里边有许多包名组成的文件夹。
3.2 清除APP的缓存,都删除了哪些文件目录呢?
  • 缓存是程序运行时的临时存储空间,它可以存放从网络下载的临时图片,从用户的角度出发清除缓存对用户并没有太大的影响,但是清除缓存后用户再次使用该APP时,由于本地缓存已经被清理,所有的数据需要重新从网络上获取。

  • 很多人简单的认为,清除缓存数据时清除的肯定就是cache下的数据,但是事实却不是这样的。我们知道应用程序在运行过程中需要经过很多过程,比如读入程序,计算,输入输出等等,这些过程中肯定会产生很多的数据,它们在内存中,以供程序运行时调用。所以清除缓存清除的是APP运行过程中所产生的临时数据。

  • 不过为了保证在清除缓存的时候,能够正常清除与应用相关的缓存,我们一般将缓存文件存放在getCacheDir()或者 getExternalCacheDir()路径下。

3.3 清除APP的数据,又删除了哪些文件目录呢?

清除数据 清除的是保存在app中所有数据,就是上面提到的位于packagename下面的所有文件,内部存储(/data/data/packagename/)和外部存储(/storage/emulated/0/Android/data/packagename/), 包括cache,files,lib,shared_prefs等等。当然除了SD卡上面的数据,SD卡上面的数据当app卸载之后还会存在的。

3.4 哪些文件目录是APP私有的,哪些又是公共的目录?
  • 内部存储空间, 通过 Context.getFileDir()或 Context.getCacheDir() 获取到的目录,绝对是APP私有的,其他进程是无法访问的;

  • 外部存储控件的私有目录,比方说通过Context.externalCacheDirs获取的缓存目录,也是私有的,但是,其他进程可以通过路径进行访问,只不过无法通过Context进行访问了;

  • 外部存储控件的公共目录,是纯粹的共有的资源了,任意进程都可以进行访问,比如Environment.getDataDirectory() 获取用户数据目录的路径。

3.5 SharedPreferences、数据库一般都存储在什么目录?
  • 既然是涉及到APP的私有数据,而且是数据库这么重要的东西,那肯定是存储在内部存储中比较安全了,所以,SharedPreferences存储在 "/data/data/Android/packagename/shared_prefs/xxx.xml"文件中,数据库存储在 "/data/data/Android/packagename/databases/xxx.xml"文件中。
  • 如果真有向其他进程共享数据的情况,可以通过ContentProvider进行提供。
3.6 getFilesDir().getAbsolutePath()和getCacheDir().getAbsolutePath()有什么区别呢?

我们知道 getFilesDir()getCacheDir()是都获取内部存储的相关目录,只不过 getFilesDir() 获取的是files目录,getCacheDir() 获取的是cache目录,它们位于同一级目录,只是为了用来存放不同类型的数据的,由文件名不难看出:cache 下存放缓存数据,databases 下存放使用SQLite存储的数据,files 下存放普通数据(log数据,json型数据等),shared_prefs下存放使用SharedPreference存放的数据。这些文件夹都是由系统创建的。APP被卸载或者删除数据时,这些目录下的文件都会被清除。

3.7 getFilesDir().getAbsolutePath()和getExternalFilesDir(“”).getAbsolutePath()有什么区别呢?

我们先看它们的路径:

/data/user/0/packname/files

/storage/emulated/0/Android/data/packname/files

很显然这两个的区别是一个在内部存储里面,一个在外部存储里面,这是它们的区别。它们的共同点呢,就是它们的路径都带有包名,表明是这个APP的专属文件,这类文件应该是随着app卸载而一起被删除的,并且我们在设置里面清除该应用的数据时,这两个文件夹下的数据都会被清除。

3.8 什么是APP专属文件?

所谓专属文件就是它是属于某个具体的应用的,他的文件路径都带有相应的包名,当APP卸载时,它们会随应用一起删除,当我们在设置里面手动清除某个应用数据时(不是清除缓存),它们也会一起被清掉。Android使用这种专属文件的目的就是为了方便文件管理,避免文件随意存储,显得很乱,另一个目的就是为了当应用被卸载时不会留下很多垃圾文件。

3.9 既然内部存储与外部存储都有APP专属文件,那么我们该优先使用哪个呢?
  • 内部存储与外部存储都有APP专属文件,我们该用哪个呢,很显然应该用外部存储的,因为内部存储本身就比较小,而且已经存储了一些系统的文件,因此内部存储我们尽量不要去使用。

  • 但是当手机没有外部存储时,我们还是得使用内部存储,一般程序员会做判断是否有外部存储,没有再使用内部存储,代码如下:

public static String getFilePath(Context context,String dir) {
    String directoryPath="";
    if (MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ) {//判断外部存储是否可用 
        directoryPath =context.getExternalFilesDir(dir).getAbsolutePath();
        }else{//没外部存储就使用内部存储  
        directoryPath=context.getFilesDir()+File.separator+dir;
        }
        File file = new File(directoryPath);
        if(!file.exists()){//判断文件目录是否存在
        file.mkdirs();
        }
    return directoryPath;
}
  • 还有种情况,我们要保存数据库这种重要数据,那么应该也会选择内部存储了。
3.10 /storage/sdcard,/sdcard,/mnt/sdcard,/storage/emulated/0之间的关系

简单的说,是Android系统开发者为了兼容不同的版本,做出的结果。这些路径都是同一个路径的不同 ”指针“,指向的是同一个地方,只是不同Android版本的叫法不一样。具体分析参考:https://blog.csdn.net/u010937230/article/details/73303034

最后附上参考的文章

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