1、内部存储和外部存储
这里的存储指的是永久非易失的存储(rom),不是内存(ram)。
这里的外部存储不是指特定的可移动的存储介质(如SD卡),现在很多Android手机都是一体机,普通消费者无法拆卸的,也没有扩展SD卡的的卡槽了,我们把手机本身自带的rom叫机身存储或内置存储。现在的Android设备都将机身存储划分了内部存储和外部存储,体现内部和外部的区别。如果可扩展可移动存储(如SD卡),这也是外部存储,这样手机则包含多个外部存储。
注:下面的讨论都是基于Android 4.4之后的,因为在老的Android版本(4.4之前)上 机身存储没有划分内部存储和外部存储,机身存储即内部存储,SD卡即外部存储。
因为外部存储是可移动、可移除的,存在明显的差异,它们具体不同的使用场景和功能,下面是大致差异,后面再具体说明。

内部存储
可用:一直可用
访问:应用保存文件在这里,只有应用本身能访问
卸载:卸载应用时,应用的所有文件都会从内部存储中删除

外部存储(机身存储中的外部存储+挂载的SD卡)
可用:非一直可用,可用挂载外部存储作为USB存储,甚至可用移除外部存储
访问:存储在外部存储的文件,很容易被其他应用访问和修改
卸载:卸载应用时,应用在外部存储创建的文件不一定都会被删除掉,只是在getExternalFilesDir()路径下的文件会被删除(后面会具体说明)

2、内部存储
当存储在内部存储时,可用通过下面方法获取合适的File对象。
Context getFilesDir():
返回应用程序的内部files目录的File对象。如:/data/data/com.flx.testfilestorage/files
Context getCacheDir():
返回一个应用程序内部临时文件目录的File对象,这个临时文件在不需要时会被删除,在系统可用存储很低时也会被系统删除。如:/data/data/com.flx.testfilestorage/cache

3、外部存储
首先,简单介绍下getExternalFilesDir()、getExternalFilesDirs()两个方法的差异以及主外部存储。

String state = Environment.getExternalStorageState();
File externalFile = context.getExternalFilesDir( null );
File[] externalFiles = context.getExternalFilesDirs( Environment.DIRECTORY_PICTURES );
for (File file : externalFiles) {
    Log.d( TAG, "state="+ state + ";\nexternalFiles=" + file + ";\nexternalFile="+externalFile);
    try {
        FileOutputStream fileOutputStream = new FileOutputStream( new File( file, "aaaa.txt" ) );
        fileOutputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

运行的手机支持SD卡 并插入了一张SD卡,看看运行结果

2019-07-15 14:46:07.819 12704-12704/com.flx.testfilestorage D/flx_storage: state=mounted;
externalFiles=/storage/emulated/0/Android/data/com.flx.testfilestorage/files/Pictures;
externalFile=/storage/emulated/0/Android/data/com.flx.testfilestorage/files
2019-07-15 14:46:07.821 12704-12704/com.flx.testfilestorage D/flx_storage: state=mounted;
externalFiles=/storage/553C-0E05/Android/data/com.flx.testfilestorage/files/Pictures;
externalFile=/storage/emulated/0/Android/data/com.flx.testfilestorage/files

从上面看到,getExternalFilesDirs获取的有两个外部存储,getExternalFilesDir是一个。这两个外部存储,一个是主外部存储,即手机本身存储中分为:内部存储和外部存储的外部存储部分,另一个是SD卡的挂载路径。
getExternalFilesDir(),获取就是主外部存储路径。
getExternalFilesDirs(),获取所有外部存储的路径,包括本身的外部存储和扩展出来的存储(如SD卡)。
在一开始就说过,应用存储到外部存储的文件。当应用卸载时只有getExternalFilesDir()路径下的会被删除。
上面代码在log后,所有外部存储路径下 都创建了aaaa.txt的文件,实际操作结果也是符合的,当卸载应用时,/storage/553C-0E05/Android/data/com.flx.testfilestorage/files/这个下面的aaaa.txt 仍然存在的。
补充:如果你希望将文件存放到外置SD卡目录而非手机存储的外部存储。可以尝试如下方法:

File targetDir = getExternalFilesDir(null);
        String targetDirPath = targetDir.getAbsolutePath();
        File[] dirs = getExternalFilesDirs(null);
        String dirPath;
        for(int i=0;i<dirs.length;i++) {
            dirPath = dirs[i].getAbsolutePath();
            if(dirPath.equalsIgnoreCase(targetDirPath)) {
                // 该目录是机身存储的外部存储
            } else {
                // 该目录是SD的外部存储
            }
        }

4、可用性判断
由于外部存储不一定一直有用,可能被移除等情况,所以使用外部存储需要进行判断,外部存储是否可用。
Environment.getExternalStorageState()这个方法可以返回外部存储的状态,不过它只返回主外部存储的状态。从源码中,也能直接的看出来

public static String getExternalStorageState() {
        final File externalDir = sCurrentUser.getExternalDirs()[0];
        return getExternalStorageState(externalDir);
    }

5、保存文件到公共目录
保存文件到外部存储的公共目录,其他应用也能直接访问,有以下两种方法(非直接创建读写文件)
媒体文件如图片、音频文件、录像文件等,使用MediaStore的API进行操作。
保存其他文档文件,如PDF,使用ACTION_CREATE_DOCUMENT intent操作。这个就属于SAF(Storage Access Framework)相关的了。
注:如果媒体文件,不希望被 Media Scanner 扫描,可以在相应目录创建一个空的文件命名为 .nomedia 。这样就能阻止Media Scanner的扫描和读取文件 并 通过MediaStore的API提供给其他应用使用。

6、保存文件到私有目录
应用保存文件到外部存储的私有目录,可以使用getExternalFilesDir()方法。具体实例 参考外部存储开始的示例。
注意:1.注意getExternalFilesDir()和getExternalFilesDirs()的区别、使用
   2.getExternalFilesDir()里的文件 会随着应用的卸载而删除
   3.注意这两个方法的参数,尽量正确使用API提供的常量,这样系统才能正确处理文件。如:使用Environment.DIRECTORY_RINGTONES,系统media scanner能够识别到是铃声而不是音乐。