转载请注明出处:
http://blog.csdn.net/chennaxin/article/details/50880750
今天在Android6.0的模拟器上写了一个写外部存储的Demo,结果一直报错.核心代码如下
FileOutputStream outputStream = new FileOutputStream(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)+"/abced.txt",true)
outputStream.write("123".getBytes())
outputStream.close()
//已经给了权限"android.permission.WRITE_EXTERNAL_STORAGE"/>
结果报错
java.io.FileNotFoundException: /storage/emulated/0/Download/abced.txt: open failed: EACCES (Permission denied)
原因
Android6.0中对权限的管理更加严格了,已经不仅仅是在Manifest文件中声明权限就可以了.某些权限要求用户在运行时再次确认才可以.比如这里的写外部存储权限.
解决办法有三个
- (推荐)在运行时申请写外部存储权限(见下文详细介绍)。
- 将targetSdkVersion设置成小于23(Android6.0)的数,并声明
WRITE_EXTERNAL_STORAGE
权限。由于新的权限模型是向后兼容的,也就是说如老版本的权限用法在6.0中也是可以用的。前提是必须将app的targetSdkVersion设置成22及以下来明确告诉系统”我的程序目前还不能完全适用Android6.0的系统”。
- 使用允许使用的存储位置,比如
openFileOutput
方法。该方法返回的输出流默认将文件存在/data/data//files
目录下。
由于博主也是个新手,暂时只能帮助大家翻译一篇文章以理解新的权限模型。
原文地址:
http://www.captechconsulting.com/blogs/runtime-permissions-best-practices-and-how-to-gracefully-handle-permission-removal
友情提示:Android6.0就是AndroidM就是棉花糖
什么是运行时权限
在Android 6.0 棉花糖中,谷歌为了帮助用户更好地理解为什么应用会请求某个特定的权限,而引入了一个新的权限模型。现在,当应用请求某个权限的时候用户才需要去接受,而不是盲目的在安装应用时就接受所有的权限。正如你了解到的那样,这种变化同时需要程序员做出一些努力。但如果我们还像以前那样申请权限会怎么样呢?而又需要做出什么样的努力才能使的你的应用在各个Android版本都能正常运行呢?
这篇博客将通过一个样例应用去解释新的权限模型的实现以及它对你的应用有着怎么样的影响。
什么时候使用新的模型
好消息
对许多应用最重要的问题是:我的应用在AndroidM上还能正常用吗?好消息是新的模型具有后向兼容性——也就是说如果你没有将 target version设置成23的话,你可以不用去使用新的模型你的应用也能够正常工作。如果你的应用的target version在22及以下,你的应用将会在安装时就请求所有的权限,就和你的应用运行在版本低于AndroidM的设备上是一样的效果。
坏消息
可以兼容并不意味着我们可以通过不适配AndroidM而不需要了解新模型。使用安卓M设备的用户现在已经可以通过应用设置来拒绝那些危险的权限(之后我会说哪些是所谓的危险权限)。这就意味着,即使用户在安装时候接受了你提供的所有权限,在使用的时候仍然可以选择拒绝授予这些权限。
纠结的消息
如果选择不用新的权限模型,权限撤销机制则可能会降低用户体验,在某些情况下甚至可能会导致应用崩溃。操作系统会尽可能优雅的处理好这些情况,但开发者也不能总是指望操作系统去完美地解决。根据我的经验,大多数权限撤销并不会导致应用崩溃,但绝对会降低用户体验。而权限撤销的副作用仅仅与应用的具体实现有关。所以,我强力建议,至少是在AndroidM的模拟器上测试一下你的应用使用权限撤销机制的情况。
如何去实现——最佳实践
向下兼容
在使用新的权限模型的时候,你的应用能够支持AndroidM和准AndroidM设备是非常重要的。例如准AndroidM是不支持运行时申请权限的,因此我们需要搞清楚什么时候能够去申请权限。在样例程序中,我使用下面的方法来确定用户是否使用了AndroidM设备。
private boolean canMakeSmores(){
return(Build.VERSION.SDK_INT>Build.VERSION_CODES.LOLLIPOP_MR1);
}
危险权限和非危险权限
和老权限模型相似的是:新的权限模型也需要将所有需要的权限声明在Manifest文件中。无论你运行在什么版本的系统上都是如此。如果你忘记了在Manifest文件中声明,应用程序将无法请求运行时权限。
Android将某型权限定义为“危险”,某些定义为“一般”。两者都需要在Manifest文件中声明,但前者会在运行时再次发出请求。“一般”的权限在安装时用户就同意了,之后不能再反悔了。一个“一般”的权限例子就是
android.permission.INTERNET
。“危险”权限被划分成组,这是为了让用户明白自己正在许可应用做哪些事情。如果用户接受了组中的某个权限,那个他将接受整个组的权限。反之亦然,如果他拒绝了组中的某一个权限,那么他将拒绝整个组。通过接下来的示例你就会明白,
FINE_LOCATION
和
COARSE_LOCATION
就是LOCATION组中的权限,同时你会发现一旦用户授予了其中一个权限,另外一个无需询问用户就被自动授权了。下表列出了当前所有的“危险”权限和他们各自所处的组。
如何使用新的权限模型
为方便起见,你可以在给定时间同时请求多个权限。你能在接下来的示例中看到:我同时申请了
CAMERA
和
RECORD_AUDIO
权限。例如你想做一个自定义的摄像软件,那么你就会需要这些权限。某些情况下你可能也会用到。
String[] perms = {"android.permission.RECORD_AUDIO", "android.permission.CAMERA"};
int permsRequestCode = 200;
requestPermissions(perms, permsRequestCode);
requestPermissions(String[] permissions, int requestCode)
方法是Activity类中的一个public方法。需要注意到,这种情况下
CAMERA
和
RECORD_AUDIO
权限属于两个不同组中,所以将会有两个不同的权限对话框来提示用户接受或者拒绝。这意味着用户可以只接受其中一个。如果两个权限属于同一个组中,你将在
onRequestPermissionResult
方法中接到如下结果:
@Override
public void onRequestPermissionsResult(int permsRequestCode, String[] permissions, int[] grantResults){
switch(permsRequestCode){
case 200:
boolean audioAccepted = grantResults[0]==PackageManager.PERMISSION_GRANTED;
boolean cameraAccepted = grantResults[1]==PackageManager.PERMISSION_GRANTED;
break;
}
}
接到结果后,你就可以按照自己的需要去处理了。在上述情况中,应用的这个功能需要同时申请这两个权限,用户拒绝了任何一个都会导致该应用无法工作,因此最好显式一些信息来提示用户操作。
再耐心一点点
当你向用户申请了某个权限之后,除非是用户要求的,否则不要再次申请这个权限。将示例程序中,我会用下面的方法来确定我需要的权限是否被授权了。
private boolean hasPermission(String permission){
if(canMakeSmores()){
return(checkSelfPermission(permission)==PackageManager.PERMISSION_GRANTED);
}
return true;
}
每当你需要执行那些需要“危险”操作的事情的时候,你都应该用上述方法来检查该权限是否已被授权。即使该权限以前已经被授权过了,仍然有必要再次检查以确保用户后来没有撤销该授权。
hasPermission
使用了
checkSelfPermission(String perm)
方法,这个也是Activity中的方法,用来返回
PERMISSION_GRANTED
或者
PERMISSION_DENIED
对应的整数值。
假如你的权限请求被用户拒绝了,你就不应该再次去发出请求。相反你应该告诉用户再次获得该权限的方式。在接下来示例中,我使用了一个
snackbar
来提示用户某个关键权限之前被拒绝了,你应该授权才能够继续使用接下来的功能。通过这个
snackbar
我可以再次向用户发出权限授予请求。记住,只向用户请求最少的权限。如果你可能会多次请求某个权限,你应该给用户一个“不再询问”功能,这样用户就不用每次都亲自授权了。如果用户选择了“不想再询问”,应用就不能再给用户继续发出权限。
不幸的是,新的权限模型中没有什么现成的办法可以记录用户授权或拒绝过的权限。你还是需要使用
SharedPreferences
来记录这些信息。在接下来的示例中我将用这样的方法来检查我是否之前已经申请过某些权限。
private boolean shouldWeAsk(String permission){
return (sharedPreferences.getBoolean(permission, true));
}
private void markAsAsked(String permission){
sharedPreferences.edit().putBoolean(permission, false).apply;
}
不要多次检查
有的时候就像我们的例子中的情况一样,应用需要多个权限才能完成某些功能。在录像软件的例子中,
camera
和
audio
都是必须的。用户如果之前已经授权了其中的某一个权限,那么我们再次申请权限的时候应该只申请另外一个权限。在下面的例子中,我使用一个方法来过滤出用户之前已经授权的权限以确保我们能够只申请另外一个权限。这并不是意味着这个权限之前以及各被授权了而是说我们之前已经申请过这个权限了。要询问多少次一个用户已经拒绝过的权限是取决于特定应用的,但永远不要请求一个用户已经授权过的权限。
private ArrayList findUnAskedPermissions(ArrayList wanted){
ArrayList result = new ArrayList<~>();
for(String perm : wanted){
if(!hasPermission(perm) && shouldWeAsk(perm)){
result.add(perm);
}
}
return result;
}
一个推荐的权限流程
下图以可视化的方式描述了我例子中的实现过程。需要注意的是,这个建议基于Google guideline的重复许可最小化的实现,你自己的实现可能会和此流程有稍许不同。
结语
不管是对用户还是开发者来说,新的权限模型绝对是朝着正确方向迈出的一步。用户能够很快就能搞清楚为什么一个应用要访问自己的联系人,这意味着在Play Store中的投诉会少很多,也会因此而增加更多的下载量。在这个博客中提到的示例应用程序可以在
这里找到,我同意你下载和使用这些代码!
博主打滚求支持
第一次写博客,也是第一次翻译,真是好花时间啊=。= 肯定也是问题多多,见谅.如果大家有什么疑问可以留言共同学习!求回复求支持啊~~