在上篇文章【安卓基础】一文搞懂Android历代版本文件访问权限变化我们对同一个应用的的文件访问权限做了比较。
那么不同应用之间文件访问又有什么限制呢?我们准备分二到三篇文件来阐述。
这篇文章,主要来看下不同系统版本下,我们直接通过路径来访问其它应用的内部存储、外部存储私有目录,看看能不能访问以及不同系统版本的区别。
可能说得有些啰嗦,心急的同学可以直接看大红字哟。
项目地址:https://github.com/codersth/android-foundation-samples/blob/master/app/src/main/java/com/codersth/android/foundation/filesystem/InterAppFileAccessActivity.kt
我们在api18的虚拟机上,当前应用的私有目录下放一个文件。
然后通过路径的方式尝试直接读取这个文件。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_inter_app_file_access)
// 读取当前应用私有目录的文件
Log.d(TAG, "onCreate: ${readFile("/sdcard/Android/data/com.codersth.android.foundation/files/hello.txt")}")
// 读取其他应用私有目录的文件(确保手机上其他应用的私有目录下有对应文件)
}
/**
* 读取指定路径下的文件内容
* @param path 待读取文件的路径,路径可能是其他应用程序的私有目录
* @return 文件文本内容,如果文件读取失败返回null
*/
private fun readFile(path: String): String? {
File(path).takeIf { it.exists() }?.also {
return FileUtil.readFileContent(File(path))
}
Log.d(TAG, "readFile: $path not exists.")
return null
}
可以看到,直接通过路径访问自己的私有目录是可以读取文件的。
接下来,我们访问本机上其他应用私有目录的文件,上传一个文件先。
接下来我们把上面的读取路径改成另一个应用的私有目录下的文件。
Log.d(TAG, "onCreate:其他应用的私有目录 ${readFile("/sdcard/Android/data/com.example.demo/files/hello.txt")}")
我们看到同样可以访问。
所以说,能不能直接访问其他应用私有目录,也是要看版本的,至少api 18上还是可以删除的,低版本应该也可以,大家不妨试下。
甚至我们可以直接把文件删除。
// 删除其他应用私有目录的文件
Log.d(TAG, "onCreate:其他应用的私有目录 ${File("/sdcard/Android/data/com.example.demo/files/hello.txt").delete()}")
不妨再延伸下,试试操作其他应用的内部存储。
// 读取其他应用内部存储的文件(确保手机上其他应用的私有目录下有对应文件)
Log.d(TAG, "onCreate:其他应用的私有目录 ${readFile("/data/data/com.example.demo/files/hello.txt")}")
// 删除其他应用内部存储的文件
Log.d(TAG, "onCreate:其他应用的私有目录 ${File("/data/data/com.example.demo/files/hello.txt").delete()}")
结果竟然成功了,就问你意不意外,惊不惊喜
所以我们可以总结下了:Android4.4(api19)以下完全可以通过路径直接操作其他应用的私有目录和内部存储。
既然api19以下如此为所欲为,我们看下Android4.4是不是安全一些。
使用上述方法,以下是执行结果。
可以看到,针对其他应用私有目录,读取和删除是可以的;针对内部存储,读取成功,删除失败。
接下来们在api=23(6.0)手机复现上述步骤,通过下面的日志可以发现,在第一步读取的时候就报权限问题了。
在《【安卓基础】一文搞懂Android历代版本文件访问权限变化》
这篇文章中我们测试过,api > 18访问应用自身外部存储的私有目录是不需要读写权限的,那导致上面的错误是权限问题还是系统做了其它安全策略呢?
我们不妨动态给个写入权限,结果与api=19上一致。
总结:19 <= api <=23上,A应用可以直接通过路径读写应用B的私有目录,也可以读取应用B的内部存储,但无法删除内部存储。
由于android N对存储访问策略做了进一步限制,我们在api=24的机器上也试下,提得一得的是,不同系统版本的私有目录路径可能不同。
路径稍微改下(以测试设备为准):
Log.d(TAG, "onCreate:其他应用的私有目录 ${readFile("/sdcard/Android/data/com.example.demo/files/hello.txt")}")
// 删除其他应用私有目录的文件
Log.d(TAG, "onCreate:其他应用的私有目录 ${File("/sdcard/Android/data/com.example.demo/files/hello.txt").delete()}")
// 读取其他应用内部存储的文件(确保手机上其他应用的内部存储下有对应文件)
Log.d(TAG, "onCreate:其他应用的内部存储 ${readFile("/data/user/0/com.example.demo/files/hello.txt")}")
// 删除其他应用内部存储的文件
Log.d(TAG, "onCreate:其他应用的内部存储 ${File("/data/user/0/com.example.demo/files/hello.txt").delete()}")
从下面的日志来看,android N开始对访问其它应用内部存储的读取作了限制。
我们在api = 28上再试下,同样如此(但没有报错)。
最后我们在30上再看下,如果30的虚拟机上无法查看私有目录,可以在被访问的应用下执行下面代码,然后把路径贴到上面的测试代码中:
private fun saveFile() {
val file1 = File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), "hello.txt")
Log.d(TAG, "saveFile: $file1 saved status: ${file1.createNewFile()} ${file1.exists()}")
}
从运行结果来看,api=30是彻底不能操作其他应用的私有目录和内部存储了。
28可以,30不行,但29呢?
29的表现也28相同。
由此我们可以给出结论了:
总结:api 24开始,应用A无法直接通过路径访问应用B的内部存储,android 30开始,应用A无法直接通过路径访问应用B的内部存储和私有目录。