为什么在Android Lollipop上使用getExternalCacheDir()需要WRITE_EXTERNAL_STORAGE权限?

10

我的应用程序将缓存文件写入(和读取)getExternalCacheDir()位置。在Android Lollipop(API 21)之前,我一直成功使用此权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />

maxSdkVersion存在是因为在API v18之后不应该需要此权限: http://developer.android.com/reference/android/Manifest.permission.html#WRITE_EXTERNAL_STORAGE

但在Android Lollipop(5.0)上,我会像这样获得访问权限(并显示我的日志输出以显示实际使用的路径):

11-19 13:01:59.257    4462-4541/com.murrayc.galaxyzoo.app E/android-galaxyzoo﹕     createCacheFile(): IOException for filename=/storage/emulated/0/Android/data/com.murrayc.galaxyzoo.app/cache/52
    java.io.IOException: open failed: EACCES (Permission denied)
            at java.io.File.createNewFile(File.java:941)
            at com.murrayc.galaxyzoo.app.provider.ItemsContentProvider.createCacheFile(ItemsContentProvider.java:528)

我在模拟器和我的Nexus 4设备上都看到了这个问题。是否有什么变化,还是我一直做错了什么?我只想访问我的应用程序缓存。

更新:现在我只在我的设备上(Nexus 4运行标准的Android 5.1.1,自从我第一次遇到这个问题以来甚至进行了全新的Android刷机)看到这个问题。我不再在模拟器中看到这个问题了-当然,系统映像已经更新了好几次。


是的。我必须始终请求WRITE_EXTERNAL_STORAGE才能使其工作。我想知道是否有一种方法可以说它在<18但>20时需要,在21(棒棒糖)确实需要它。 - murrayc
1
根据文档,从API 19开始,在使用getExternalFilesDir(String)和getExternalCacheDir()返回的应用程序特定目录中读写文件时,不需要此权限,因此我们不需要在API 19或更高版本中使用此权限。但是,如果没有此权限,结论是无法正常工作。 - Haresh Chhelana
嗨@murrayc,我打算在将我的应用程序定位到Masmallow时使用<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="18" />。我在Masmallow上进行了测试,它可以正常工作。您在KitKat和Lollipop中是否仍然遇到此问题? - Cheok Yan Cheng
是的,在Lollipop中我仍然看到这个问题。在Lollipop中,这方面没有任何改变。它似乎在Marshmallow中得到了修复,但是Lollipop仍然存在,我们必须处理它。 - murrayc
如果我在Marshmallow中明确禁用权限,则无法写入应用程序外部缓存目录。我不理解,因为禁用权限只应影响我尝试在其他地方而不是在应用程序目录中写入的情况。 - Héctor Júdez Sapena
显示剩余2条评论
1个回答

5

我们在Nexus 5上的API 21(棒棒糖)上看到了相同的行为:

java.io.FileNotFoundException: /storage/emulated/0/Android/data/[package name]/cache/http/journal.tmp: open failed: EACCES (Permission denied)
   at libcore.io.IoBridge.open(IoBridge.java:456)
   at java.io.FileOutputStream.<init>(FileOutputStream.java:87)
   at java.io.FileOutputStream.<init>(FileOutputStream.java:72)
   at com.android.okhttp.internal.DiskLruCache.rebuildJournal(DiskLruCache.java:346)
   at com.android.okhttp.internal.DiskLruCache.open(DiskLruCache.java:239)
   at com.android.okhttp.HttpResponseCache.<init>(HttpResponseCache.java:140)
   at android.net.http.HttpResponseCache.install(HttpResponseCache.java:199)
...
   at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1011)
   at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4518)
   at android.app.ActivityThread.access$1500(ActivityThread.java:144)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1339)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:135)
   at android.app.ActivityThread.main(ActivityThread.java:5221)
   at java.lang.reflect.Method.invoke(Method.java)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)
Caused by: android.system.ErrnoException: open failed: EACCES (Permission denied)
   at libcore.io.Posix.open(Posix.java)
   at libcore.io.BlockGuardOs.open(BlockGuardOs.java:186)
   at libcore.io.IoBridge.open(IoBridge.java:442)
   at java.io.FileOutputStream.<init>(FileOutputStream.java:87)
   at java.io.FileOutputStream.<init>(FileOutputStream.java:72)
   at com.android.okhttp.internal.DiskLruCache.rebuildJournal(DiskLruCache.java:346)
   at com.android.okhttp.internal.DiskLruCache.open(DiskLruCache.java:239)
   at com.android.okhttp.HttpResponseCache.<init>(HttpResponseCache.java:140)
   at android.net.http.HttpResponseCache.install(HttpResponseCache.java:199)
...
   at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1011)
   at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4518)
   at android.app.ActivityThread.access$1500(ActivityThread.java:144)
   at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1339)
   at android.os.Handler.dispatchMessage(Handler.java:102)
   at android.os.Looper.loop(Looper.java:135)
   at android.app.ActivityThread.main(ActivityThread.java:5221)
   at java.lang.reflect.Method.invoke(Method.java)
   at java.lang.reflect.Method.invoke(Method.java:372)
   at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:899)
   at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:694)

在谷歌推送Android 5.0到AOSP之前,我们无法确定这是一个错误还是故意的(但未记录在案的)更改,但我已经提出了此问题:

https://code.google.com/p/android/issues/detail?id=81357

添加WRITE_EXTERNAL_STORAGE权限可以防止抛出上述异常,但需要终端用户许可来升级现有应用程序。由于我们的应用程序不使用此权限,也不想添加它,因此我们回退到仅对KitKat设备使用内部缓存。

顺便说一下,我发现这是KitKat引入的变化背景很有趣:http://www.doubleencore.com/2014/03/android-external-storage/


我感谢确认我不是唯一遇到这个问题的人,但这似乎只是问题的重申,而不是一个答案。 - murrayc
@murrayc 我也向谷歌报告了这个错误,尽管当然为时已晚,因为Lollipop已经在使用中。仅使用内部缓存的回退对我们的需求已经足够。 - fingertricks
好的,谢谢。你有什么迹象表明他们修复了它,是太晚了还是没有? - murrayc

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接