参考文献:

Android系统中的目录结构和App缓存管理

1. 内部存储和外部存储

参考:

解析Android内部存储、外部存储的区别

彻底搞懂Android文件存储---内部存储,外部存储以及各种存储路径解惑

内部存储指的不是内存,外部存储指的也不是 SD 卡。

/data/user/0 指向 /data/data

1.1 Android 系统目录介绍

Android 系统使用虚拟文件系统(VFS), VFS 的目录是以/为根节点,根节点下又有不同的节点。而我们的物理存储设备就是挂载都这些节点上,如下图所示:

20180326105931844.png/data/data/ apk 的安装目录。如:百度地图的安装路径是 /data/data/com.baidu.com/ ,该目录需要获取 root 权限才能查看。

/system/ 存放系统应用的 apk 文件,即手机厂商预安装应用的apk文件 (手机厂商只需把需要预安装的 apk 放在该节点的相应路径下,android 系统就会自己解压并安装该 apk)

/storage/ 该节点是内置存储卡和外置 SD 卡的挂载点,/storage/emulated/0/ 是内置存储卡挂载点, /storage/sdcard1 是外置 SD 卡挂载点(不同的设备挂载节点不一样,有些设备可能会挂载到/mnt/ 节点)。

1.2 内部存储

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

从技术上来讲如果你在创建内部存储文件的时候将文件属性设置成可读,其他 app 能够访问自己应用的数据,前提是他知道你这个应用的包名,如果一个文件的属性是私有(private),那么即使知道包名其他应用也无法访问。 内部存储空间十分有限,因而显得可贵,另外,它也是系统本身和系统应用程序主要的数据存储所在地,一旦内部存储空间耗尽,手机也就无法使用了。所以对于内部存储空间,我们要尽量避免使用。内部存储一般用 Context 来获取和操作。

访问内部存储的API方法:

Environment.getDataDirectory()
getFilesDir().getAbsolutePath()
getCacheDir().getAbsolutePath()
getDir(“myFile”, MODE_PRIVATE).getAbsolutePath()

1.3 外部存储

从 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方法:

Environment.getExternalStorageDirectory().getAbsolutePath()
Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath()
getExternalFilesDir(“”).getAbsolutePath()
getExternalCacheDir().getAbsolutePath()

2. 获取路径

// 这个方法是获取内部存储的根路径
Environment.getDataDirectory() = /data
// 这个方法是获取某个应用在内部存储中的 files 路径
getFilesDir().getAbsolutePath() = /data/user/0/packname/files
// 这个方法是获取某个应用在内部存储中的 cache 路径
getCacheDir().getAbsolutePath() = /data/user/0/packname/cache
// 这个方法是获取某个应用在内部存储中的自定义路径
getDir(“myFile”, MODE_PRIVATE).getAbsolutePath() = /data/user/0/packname/app_myFile

// 这个方法是获取外部存储的根路径
Environment.getExternalStorageDirectory().getAbsolutePath() = /storage/emulated/0
// 这个方法是获取外部存储的根路径
Environment.getExternalStoragePublicDirectory(“”).getAbsolutePath() = /storage/emulated/0

// 这个方法是获取某个应用在外部存储中的 files 路径
getExternalFilesDir(“”).getAbsolutePath() = /storage/emulated/0/Android/data/packname/files
// 这个方法是获取某个应用在外部存储中的 cache 路径
getExternalCacheDir().getAbsolutePath() = /storage/emulated/0/Android/data/packname/cache

// 在 4.4 以前的系统中 getExternalFilesDir(“”) 和 getExternalCacheDir() 将返回 null,如果是 4.4 及以上的系统才会返回上面的结果,也即 4.4 以前的系统没插 SD 卡的话,就没有外部存储,它的 SD 卡就等于外部存储;
// 而 4.4 及以后的系统外部存储包括两部分,getExternalFilesDir(“”) 和 getExternalCacheDir() 获取的是机身存储的外部存储部分,也即 4.4 及以后的系统你不插 SD 卡,它也有外部存储。

Environment.getDownloadCacheDirectory() = /cache
Environment.getRootDirectory() = /system
Environment.getDataDirectory() = /data

从上面我们很清楚的可以看到上面的方法可以分为三类,第一类是位于根目录 /data 下;还有一类是位于根目录 /storage 下,可以看到调用它们的 API 方法都带了一个 External;另外一类不在 /data 下也不在 /storage 下,比如系统文件 /system,或者缓存文件 /cache
/data 目录下的文件物理上存放在我们通常所说的内部存储里面
/storage 目录下的文件物理上存放在我们通常所说的外部存储里面
/system 用于存放系统文件,/cache 用于存放一些缓存文件,物理上它们也是存放在内部存储里面的

疑问1. 那getFilesDir().getAbsolutePath()和getCacheDir().getAbsolutePath()有什么区别呢?

20170616001424912.pnggetFilesDir 获取的是 files 目录,getCacheDir 获取的是 cache 目录,它们位于同一级目录,只是为了用来存放不同类型的数据的,由文件名不难看出:cache 下存放缓存数据,databases 下存放使用 SQLite 存储的数据,files 下存放普通数据(log 数据,json 型数据等),shared_prefs 下存放使用 SharedPreference 存放的数据。这些文件夹都是由系统创建的。

疑问2. getFilesDir().getAbsolutePath() 和 getExternalFilesDir(“”).getAbsolutePath() 有什么区别呢?
我们先看它们的路径:
/data/user/0/packname/files
/storage/emulated/0/Android/data/packname/files
很显然这两个的区别是一个在内部存储里面,一个在外部存储里面,这是它们的区别。它们的共同点呢,就是它们的路径都带有包名,表明是这个 APP 的专属文件,这类文件应该是随着 app 卸载而一起被删除的,并且我们在设置里面清除该应用的数据时,这两个文件夹下的数据都会被清除。

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

疑问4. 既然内部存储与外部存储都有 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. 缓存和数据

清除数据和清除缓存到底清除了什么数据?

这个很容易搞混,为什么呢?通过上面我们知道:
/data/user/0/packname/files 它是用来存储普通数据的
/data/user/0/packname/cache 它是用来存储缓存数据的

所以很多人就以为我清除数据时清除的肯定就是 files 下的数据,而我清除缓存数据时清除的肯定就是 cache 下的数据,但是事实却不是这样的。

正确应该是:
清除缓存:我们知道应用程序在运行过程中需要经过很多过程,比如读入程序,计算,输入输出等等,这些过程中肯定会产生很多的数据,它们在内存中,以供程序运行时调用。所以清除缓存清除的是APP运行过程中所产生的临时数据。
清除数据:清除数据才是真正的删除了我们保存在文件中的数据(永久性数据,如果不人为删除的话会一直保存在文件中)例如当我们在设置里面清除了某个应用的数据,那么 /data/user/0/packname//storage/emulated/0/Android/data/packname/ 下的文件里面的数据会全部删除,包括 cache,files,lib,shared_prefs 等等。

4. 不同版本的外部存储路径

4.1 系统中,getExternalStorageDirectory 方法获取到的路径为 /storage/sdcard0
4.2 系统中,getExternalStorageDirectory 方法获取到的路径为 /mnt/sdcard,因为 4.2 是模拟器打印的结果,如果是真机的话也是 /storage/sdcard0
4.4 系统中,getExternalStorageDirectory 方法获取到的路径为 /storage/emulated/0,它的 SD 卡存储路径为 /storage/sdcard1;6.0 系统中,getExternalStorageDirectory 方法获取到的路径为 /storage/emulated/0,它的 SD 卡存储路径为 /storage/B3E4-1711

所以在真机上,getExternalStorageDirectory获取到的路径如下表所示:

系统版本结果
4.0/mnt/sdcard
4.1/storage/sdcard0
4.2/storage/sdcard0
4.4/storage/emulated/0
6.0/storage/emulated/0

其中 sdcard/mnt/sdcardstorage/sdcard0storage/emulated/0storage/emulated/legacy 都是同一个路径的不同”指针“,指向的是同一个地方,只是不同 Android 版本的叫法不一样。

有软连接来进行兼容处理。

5. Context 操作文件模式

  • MODE_PRIVATE:默认模式,仅此应用可操作
  • MODE_APPEND:向文件追加
  • MODE_WORLD_READABLE:别的应用可读
  • MODE_WORLD_WRITEABLE:别的应用可读可写
public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	public void privateMode(View v) {
		try {
			FileOutputStream fos = openFileOutput("private.txt", MODE_PRIVATE);
			fos.write("private".getBytes());
			fos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void append(View v) {
		try {
			FileOutputStream fos = openFileOutput("append.txt", MODE_APPEND);
			fos.write("append".getBytes());
			fos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void worldReadable(View v) {
		try {
			FileOutputStream fos = openFileOutput("worldReadable.txt", MODE_WORLD_READABLE);
			fos.write("worldReadable".getBytes());
			fos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void worldWritable(View v) {
		try {
			FileOutputStream fos = openFileOutput("worldWritable.txt", MODE_WORLD_WRITEABLE);
			fos.write("worldWritable".getBytes());
			fos.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}