Android内外存储 易混淆点剖析(/mnt/sdcard/storage/sdcard0/storage/emulated/0等区别)

Posted 鸽一门

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android内外存储 易混淆点剖析(/mnt/sdcard/storage/sdcard0/storage/emulated/0等区别)相关的知识,希望对你有一定的参考价值。

说起android“内部存储”,对于开发者而言可以毫不犹豫地脱口而出,不就是/data/data目录嘛,也不尽然,/data/user/0/又如何解释?那说起“外部存储”,例如sd卡目录获取值,更是众说纷纭,是/mnt/sdcard?还是/storage/sdcard0?莫非是/storage/emulated/0

此疑问起源要追溯到笔者在写上一篇博文,即以DexClassLoader类加载原理编写demo实现类替换修复,其中编码过程中涉及到读写Android内外存储的文件数据,逻辑就是读取外部存储sd卡上的dex文件,写入到内部存储对应的app包下的一个自定义目录中。

以上逻辑并不复杂,但是笔者用模拟器和不同版本的Android真机测试时,发现这获取sd卡的路径却各不相同,思索不解一个百度,发现其中大有文章,有的目录只是链接,是个软连接,而大部分原因需归咎于Android版本更新中“多用户”的新特性出现,Google对用户的数据结构进行了调整。可具体实情如何,请看以下解说:

本篇博文涉及到的知识点如下:

  • Android内、外部存储基础知识点及文件夹所在位置
  • 存储路径中“0”的含义与变化
  • 多种sd卡路径表示含义与区别

一. Android内外存储基础知识

Android手机上的存储空间可做如下划分:

  • 内存:RAM
  • 内部存储:内部ROM
  • 外部存储:外部ROM和SDCard

手机上的存储在概念上分成了”内部internal“和”外部external“两部分,但其实都在手机内部。因此无论Android手机是否有可移动的sdcard,它都有外部存储和内部存储,且通过相同的Api方法来访问可移动的sdcard或者手机自带的存储。

以下是一部SamSung手机的存储展示图,用AS自带的Device File Explore打开后展现的文件目录。

(1)内部存储:

Android可以说是一个Linux操作系统,它的内部存储空间对于应用程序和用户来讲就是“/data/data“目录。内部存储与外部存储相比有着比较稳定,存储方便,操作简单,更加安全(可以控制访问权限)等优点,而它唯一的缺点就是空间有限。

内部存储空间的有限意味着应物尽其用,用来保存比较重要的数据,例如用户信息资料,口令秘码等不需要与其他应用程序共享的数据。注意应用程序被卸载时,应用程序在内部存储空间的文件数据将全部被删除,避免占用宝贵的空间。

内部存储即data文件夹,其中里面有两个文件夹值得关注:

  • app文件夹(未root无法打开):存放着所有app的apk文件夹,当开发者调试某个app时,AS控制台输出的内容中有一项是uploading…,代表正在上传apk到这个文件夹。
  • data文件夹:内部都是app的包名,存储着应用程序相关的数据,例如 data/data/包名/(shared_prefs、database、files、cache)

(2)外部存储

外部存储是指用户在使用时自行在手机上添加的外部存储介质,例如TS卡,SD卡等闪存储介质。其显著的优点就是存储空间大,无需担心数据清除问题,与内部存储不同的是当应用程序卸载时,它在外部存储所创建的文件数据不会被清除,因此清理外部存储空间的责任丢给了用户自己。缺点则是不太稳定,闪存介质对于Android手机而言会出现SD卡不能正常使用的情况。

外部存储即storage文件夹或mnt文件夹。需要注意的是storage中有一个sdcard0文件夹,其中又分为公有目录私有目录

  • 公有目录:有9大类,比如DCIM、Download等系统为用户创建的文件夹;
  • 私有目录: 即Android文件夹/storage/sdcard/Android/,其中的data文件夹包含了许多包名组成的文件夹。

若应用程序在运行过程中需要向手机上保存数据,通常是保存在sdcard中/storage/sdcard即应用直接在sdcard的根目录创建一个文件夹用于数据保存,不过当该app被卸载后,数据还保留在sdcard中,意味着留下了垃圾数据。

在开发中,Google官方建议App数据存储在外部存储的私有目录中对应App的包名下storage/sdcard/Android/data/包名/,这样当用户卸载掉App之后,相关的数据会一并删除!

(3)内外部存储常用目录操作

  • context.getFilesDir() 内部存储data/data/包名/files目录
  • context.getCacheDir() 内部存储data/data/包名/cache目录
  • Environment.getExternalStorageDirectory() 外部存储根目录
  • Environment.getExternalStoragePublicDirectory (Environment.DIRECTORY_DCIM) 外部存储公有目录
  • context.getExternalFilesDir() 外部存储私有目录storage/sdcard/Android/data/包名/files。一般存储长时间保存的数据。
  • context.getExternalCacheDir() 外部存储私有目录storage/sdcard/Android/data/包名/cache。一般存储临时缓存数据。

注意上述最后两个API:当app被卸载后,sdCard/Android/data/PackageName/下的所有文件都会被删除,不会留下垃圾信息。两个API对应的目录分别对应着 设置->应用->应用详情里面的“清除数据”与“清除缓存”选项。




二. 各个易混淆路径识别

1. 文件夹中0的出现

在Android版本4.2JellyBean之前,获取sdcard的路径是/sdcard/,但在JellyBean版本之后的路径成为了/sdcard/0,或者是/sdcard/legacy(legacy可以是0、1、2……),这个“0”到底代表着什么含义?

这是从JellyBean版本起的一个新特征——多用户。因此为了处理单独的账户,部分目录结构必须被改变,/sdcard/legacy始终指向当前登录的用户的SD卡目录。

正因“多用户”功能的增加,内外部存储发生了以下变化:

  • 内部存储: 原先的/data/data/其实相当于直接链接到当前用户文件夹的,变成了/data/user/0/
  • 外部存储:例如sd卡路径不再是/sdcard/,而是/sdcard/legacy/(legacy可以是0、1、2……),其中的“0”可以当成“设备拥有者”,或者称为“第一用户”(“第一用户”毫无疑问的是“设备所有者”,只有此用户才能创建额外账户)。

大致理解了其由来后,可能少数人还遇到过/sdcard/0/0 这种表示,以下是谷歌对此的官方解释:

Google在Android 4.2中引入了“多个用户”作为新功能, 为了适应“多个用户”,Google现在为每个用户提供了自己的文件夹以供存储。 如果您从4.1升级到4.2,则4.2 ROM将在/data中查找某个文件,以确定是否需要将所有文件迁移到新的多用户数据结构。 默认情况下,4.2将所有/data/media迁移到/data/media/0不过使用自定义恢复会出现问题,自定义恢复在出厂重置期间会保留/data/media文件夹。当您重新设置工厂并再次启动4.2 ROM时,4.2 ROM将再次迁移/data/media中的所有内容。 每次出厂重置时它都会迁移您的文件,这种多次迁移会导致一些人将他们的文件移动到/sdcard/0甚至/sdcard/0/0等等。



2. sd卡路径随着Android版本变化

public static final String SDPATH = Environment .getExternalStorageDirectory().getAbsolutePath();//获取外部存储的路径返回绝对路径的,就是你的设备SD卡的文件路径

如上是获取SD卡目录的Api,你会发现使用不同Android移动设备得到的sd卡目录不同,不仅如此,其中路径指向的都是相同的文件,缘由为何?

/sdcard/
/mnt/sdcard/
/storage/sdcard0/
/storage/emulated/legacy/

上面四个皆指代sd卡路径,其中演变的由来见以下Android版本分享:

(1)android 4.0

Galaxy Nexus(GN)手机上的userData分区很大,被挂在/data 目录下。用户的数据通常是存储在SD卡上,可是GN无SD卡,即只有intenal内部存储。(第四点中会讲解Galaxy Nexus为何只有内部存储缘由)

Google爸爸并未被眼前困难打倒,直接在userData分区下虚拟了一个media目录,它是是内置sd卡的数据存储位置,具体使用了fuse技术将/data/media虚拟成为一个叫做/dev/fuse的设备,为了让程序能认出来,被同时挂载在 /mnt/sdcard 目录,又为了兼容以前的程序,做了一个快捷方式(linux系统里叫软连接),因此 /sdcard 指向的就是 /mnt/sdcard


(2)android 4.1

以上是Android 4.0的应对措施,在4.1中同样使用fuse技术,/dev/fuse 会被同时挂载到/storage/sdcard0 目录。“sdcard0”表示第一个sd卡(若有外置sd卡,那会多一个 /storage/sdcard1/sdcard 软连接会指向 /storage/sdcard0 ,此时/mnt/sdcard 也是个软连接,会指向/storage/sdcard0

因此/storage/sdcard0才是最终源头,/sdcard/mnt/sdcard是指向它的一个软连接而已。

如果你通过otg线接U盘,会被挂载到 /storage/usb0目录,stickmount这个软件为了让图库、快图、mx player等软件,能看到u盘里的数据,又同时挂载到 /storage/sdcard0/usStorage/sda1。为何命名为“usb0”,而不是“sda1”?

这是Linux的对硬盘的命名方式,如果你的u盘有多个分区,就分别是按sda1、sda2此规律命名下去。


(3)android 4.2及之后

在4.2之后,Android出现了一个“多用户”的功能特征,意味着一台设备可能被多个人同时应用,因此需要将每个人的数据、应用、个性配置分开。后两者容易实现,通过权限控制即可,可是数据这一块如何是好?

面对需求的变更,只好再调整用户数据的挂载结构,4.2版本中同样使用fuse技术,而/dev/fuse 会被挂载到/storage/emulated/0 目录。(若有多个用户,0文件夹名称依次增加,例如/storage/emulated/1

不仅如此,为了兼容Android版本升级,还同时挂载到/storage/emulated/legacy,建立了三个软连接指向自己:

  • /storage/sdcard0
  • /sdcard
  • /mnt/sdcard

(4)不要太在意“0”

也许在手机设备开发中似乎没太注意到“多用户”新特性的作用,无需太过在意,它主要在平板上使用,手机端是禁用的,但底层实现原理相同。
/mnt/shell/emulated 目录和 /storage/emulated 下的文件夹是一样的。

(此点“版本总结”来源于机锋论坛之关于android的4.2的0文件夹的详解,笔者稍有修改)



3. 多种sd卡路径表示的区别总结

在上一点中了解了表示sd卡几种路径的演变过程,此部分对以下几个路径进行简单总结区别:

(注意:下面路径中的文件夹相同)

/sdcard/
/storage/sdcard0/
/storage/emulated/0/
/storage/emulated/legacy/
  • /sdcard/:只是一个符号链接,链接到/storage/sdcard0/
    • /mnt/sdcard:Android4.0版本之前的显示
    • /storage/sdcard0 :Android4.0版本之后的显示
  • /storage/emulated/0/:这是参照“emulated MMC”,通常指其内部,“0”代表第一个用户,即设备拥有者。 如果您创建其他用户,则此数字将为每个用户增加。
  • /storage/emulated/legacy/:与上同理,但指向当前工作用户的部分。(对于“0”用户而言,这是/storage/emulated/0/的符号链接)
  • /storage/sdcard0/:注意这里的“0”并非是一个单独文件夹名,而是作为后缀一样,意味着“0”并不代表用户,而是设备(卡)本身,因此它不需要legacy链接。人们可以通过OTG将读卡器与另一个SD卡连接起来,然后路径将成为/storage/sdcard1/

为了兼容Android版本升级,还同时挂载到/storage/emulated/legacy,建立了三个软连接指向自己:/storage/sdcard0/sdcard/mnt/sdcard

sdcard0名称变化

“sdcard”这个文件夹是手机外置SD卡的文件夹名称,“sdcard0”这个文件夹是手机内存文件夹名称。手机内存本身是无法扩展的,但是外置SD卡的空间用户可以根据手机最大使用限制购买更换。因此“sdcard0”是内置储存,“sdcard1”是扩展储存,就是一般的sd卡。

如果更多的设备连接,但命名可能会有所不同,取决于设备:

  • 当内部虚拟SD卡:/mnt/sdcard0/
  • SD插槽中的物理SD:/mnt/sdcard1/
  • 可调用其他设备,如连接适配器的USB闪存驱动器:/mnt/media_rw/usbdisk(kitkat 4.4及以上?)或/mnt/usbdisk(jellybean 4.1 - 4.2)

命名可能取决于设备、制造商、 ROM等, 但通常情况下,/mnt/*在所有这些情况下只是一个符号链接,而源头是位于/storage路径下。

(此点回答来自于StackExchange,结合自身理解翻译叙述)



4. “/sdcard/”是一个“假”路径?

上一点解决了各个路径的识别问题,可仍剩下一个疑惑:Glaxy Nexus没有用于SD卡的外部插槽,即只有内部存储,但它仍有一个名为/sdcard/的根文件夹(第二点中的(1)部分已简单解释了原因),详情为何?以下是Google工程师回应Galaxy Nexus背后缺乏USB大容量存储的理由,即Android为什么从USB大容量存储转移到内部存储的MTP访问的解释,可以揭秘为何GN中/sdcard/“假”路径的问题。

UMS:通信技术;
MTP:multi-path transmission网络传输机制,多路径传输是指采用多条不相交的路径来投递应用分组以增加连接的容量和可靠性的机制;
ICS:Internet连接共享

Galaxy Nexus不支持USB海量存储(UMS),即不支持USB存储模式,这给许多用户带来了震撼。 所有拥有XOOM,Nexus S,Galaxy Tab 10.1或任何缺少SD卡插槽的设备的人都熟悉这种设置,因为上述所有设备的工作方式与Galaxy Nexus类似,都是使用MTP网络传输机制而不是UMS通信技术。以下Google工程师回应了Galaxy Nexus相关问题:

Q: ICS不支持USB大容量存储?

A: ICS支持USB海量存储(UMS), Galaxy Nexus不会,这与Honeycomb(蜂巢)相同,例如HC支持USB大容量存储,而Xoom不支持。

如果给定设备有可移动的SD卡,它将支持USB大容量存储。如果设备只有内置存储(如Xoom、Galaxy Nexus),它通常仅支持MTP和PTP。在没有专用存储分区的设备上(如可移动SD卡或像Nexus S这样的单独分区),在物理上不可能支持UMS。这是因为UMS是块级协议,主机PC直接访问存储上的物理块,以便Android不能同时安装它。

通过我们在Honeycomb中引入的统一存储模型,可以在应用程序数据和媒体数据之间分享完整的32GB(或16GB其他),即当您的内部应用数据分区已满时,Nexus S上的5GB免费空间不会再无所事事。然而这样的代价是,Android不能再为主PC直接通过USB传输存储。相反使用MTP。在Windows(大多数用户使用)上,它在资源管理器中内置了MTP支持,使其看起来完全像磁盘。(Linus和Mac上没那么容易,sad)

总体而言,这是一个更好的手机体验。


Q: 由于Galaxy Nexus仅具有内部存储,因此ASTRO文件管理器等应用程序如何在不需要root权限的情况下工作?

A:首先我们在内部存储器上指定一个特定的目录作为“SD卡”, 然后实现了一个FUSE文件系统,除了丢弃所有权限检查之外,什么也不做,除非重新将该目录挂载为/sdcard。 除了权限之外,FUSE文件系统是直通的,因此实际的文件可以从目录中读取或写入目录。

我们使用“假代理” FUSE文件系统来重新安装特定的目录以伪装成SD卡, 这对于应用程序来说是完全透明的,他们不能说没有直接与磁盘通话。

(阅读自此,GN中为何有/sdcard/ 这个“假”路径的秘密已经被揭开,下一个Q&A则是帮助开发者更深入理解Glaxy Nexus为何舍弃USB大容量存储,他们考虑的思路又是如何?)


Q: Galaxy Nexus这么做也许有它的理由,可是Micro-SD卡插槽需要多少空间?为何不可两者兼得?

A:舍弃SD卡插槽主要原因并不在硬件上,而是没有一个适合的使用UI。Android核心原则之一是你永远不需要文件管理器,因此我们想要避开其他操作系统的一个综合症——文件选择器。app应该知道如何处理它的本地数据,即存储在本地或者云端,而不是搜查你的Micro-SD卡来获取数据。

同时拥有内部存储和SD卡的这个目标确实有些难以实现,例如指定的相片,相机应该保存在内部的16GB还是SD卡?市场中的app应该安装到内部存储还是SD?诸如此类等等。没错,你也许会说一个简单的选择或者设置即可解决,但那样类似于文件选择器的体验并非是我们的理想期望。除此缺点之外,还要考虑到API,若用户在SD卡上存储照片,是否将其添加到系统媒体内容提供商?如果需要的话这对app而言是个灾难,因为他们没有操作照片来去这种概念。

而我们最终可能做的是将“导入/导出”这种概念添加到可移动存储。因此相机将始终保存到内部存储中,并且当您弹出SD卡时(或在USB主机设备上插入拇指驱动器),您可以启动“”迁移或“导入/导出”对话框。

(此点Q&A来源于androidpolice新闻,结合自身理解翻译叙述)




Android开发中有一个隐性的问题,就是市面上各个手机的Android版本并不统一。会被不同版本所影响的逻辑处理,开发人员需要做好兼容处理,去适应市场上大部分机型,例如此篇文章的混淆点。

此篇文章主要的疑问通过搜索多篇文章最终解决,其中涉及到不少英文网站,例如StackOverflow、Stack Exchange,还有国内的论坛、博客等等,知识开源的感觉还是很棒的,虽然国内还是偏少,共勉~

若翻译、理解有误,虚心指教~

以上是关于Android内外存储 易混淆点剖析(/mnt/sdcard/storage/sdcard0/storage/emulated/0等区别)的主要内容,如果未能解决你的问题,请参考以下文章

Android 存储目录

分析PMP易混淆知识十大点

c++概述--易混淆点记录

c++概述--易混淆点记录

C++中三个继承易混淆点说明一下

js 变量声明易混淆的几点知识