Android6.0以上 上传图片时 需要进行权限申请
2016-07-19 17:28
543 查看
文章来自于:点击打开链接
以下是需要单独申请的权限,共分为9组,每组只要有一个权限申请成功了,就默认整组权限都可以使用了。权限组 | 权限 |
---|---|
android.permission-group.CALENDAR(日历数据) | android.permission.READ_CALENDAR android.permission.WRITE_CALENDAR |
android.permission-group.CAMERA(相机) | android.permission.CAMERA |
android.permission-group.CONTACTS(联系人) | android.permission.READ_CONTACTS android.permission.WRITE_CONTACTS android.permission.GET_ACCOUNTS |
android.permission-group.LOCATION(位置) | android.permission.ACCESS_FINE_LOCATION android.permission.ACCESS_COARSE_LOCATION |
android.permission-group.MICROPHONE(麦克风) | android.permission.RECORD_AUDIO |
android.permission-group.PHONE(电话) | android.permission.READ_PHONE_STATE android.permission.CALL_PHONE android.permission.READ_CALL_LOG android.permission.WRITE_CALL_LOG com.android.voicemail.permission.ADD_VOICEMAIL android.permission.USE_SIP android.permission.PROCESS_OUTGOING_CALLS |
android.permission-group.SENSORS(传感器) | android.permission.BODY_SENSORS |
android.permission-group.SMS(短信) | android.permission.SEND_SMS android.permission.RECEIVE_SMS android.permission.READ_SMS android.permission.RECEIVE_WAP_PUSH android.permission.RECEIVE_MMS android.permission.READ_CELL_BROADCASTS |
android.permission-group.STORAGE(存储) | android.permission.READ_EXTERNAL_STORAGE android.permission.WRITE_EXTERNAL_STORAGE |
一、 官方推荐的权限最佳实践
如果没有节制地频繁请求权限很容易使用户反感,如果用户发现app需要大量的敏感权限,很可能会拒绝使用甚至直接卸载。以下几点可以有效地提升用户的使用体验。
1. 考虑使用Intent
在很多情况下,你可以有两种选择实现你的操作,一种是直接app中请求比如摄像头这样的权限,然后调用摄像头APIs去控制摄像头并获取照片。这种方式可以使你对摄像头有全部的控制权,并且可以自定义相关的UI。然而,如果你不需要完全控制,那么你只需要使用ACTION_IMAGE_CAPTURE intent来请求图片。当你发送这个intent,系统会自动询问用户打开哪个照相app(加入手机上安装不止一个照相app)。用户从选中的照相app中选择照片后,你的app就会从onActivityResult()方法中得到需要的照片。
类似的,如果你需要打电话,访问用户的通讯录等等,你也可以发送相应的intent,或者直接请求相应的权限。以下是两种方式的优缺点:
如果自己申请权限:
你将可以完全控制想要的权限,但是同时也增加了复杂度,例如你要设计相应的UI界面
一旦用户同意了你的权限申请,不管是在使用时还是安装阶段(根据用户手机的系统版本),你将可以一直使用该权限。但是一旦用户拒绝了你的权限申请(或者随后撤回了权限),你的app将无法使用相关的权限和功能
如果使用Intent:
首先你不需要自己设计UI界面,拥有该权限的app会提供UI,那么同时也意味着你将不能控制用户的使用体验,用户将会被一个你甚至完全不知道的app所影响。
如果用户有不止一个相关的app,系统将会提示用户做出选择。如果用户不勾选默认的操作,那么每次调用该权限的时候都会弹出提示框。
2. 不要同时申请大量的权限
如果用户使用的Android 6.0(API为23)及以上版本,用户需要在使用app时选择是否允许使用某权限。如果你一次性向用户申请大量的权限,用户会很反感甚至直接退出app。所以,你应该在只有用到某权限时才询问用户。在有些情况下,你可能需要不止一个权限。你应该在启动app时请求权限,例如,你要做一个照片相关的app,你需要申请摄像头权限。当用户第一次打开app时,他们不会惊讶app询问使用摄像头的权限。同时app也需要分享照片给通讯录中好友,你不应该在app首次启动时申请READ_CONTACTS权限,而是当用户分享照片再去申请。
3. 解释为什么你需要权限
当你调用requestPermissions()方法时,系统会自动弹出权限对话框展示相应的权限描述,但是不会显示申请的原因。这会在某种程度上给用户造成困惑,所以在调用requestPermissions()前解释一下申请的原因会比较合适。例如,一个图片应用想要地址服务以给图片标出地理标签,一个普通用户可能不明白为什么照片需要地理信息,甚至困惑app为什么要申请地址权限。因此,在调用requestPermissions()前告诉用户申请的原因就显得很有必要了。
至于如何在代码中实现显示申请原因下面的代码分析中会提及。
4. 测试权限
从Android 6.0(API为23)开始,用户可以在任意时刻同意和拒绝权限,而不是像之前版本安装时做一次决定。在Android 6.0之前,你可以假定app所有在manifest文件声明的权限是已经通过了。但是在Android 6.0及更高版本,你不能再有这样的假定。以下这些建议将会在Android 6.0及更高版本帮你识别权限相关的代码问题:
确认你的app当前需要的权限和相关的代码
测试用户可以通过权限保护服务
测试各种同意和拒绝的权限组合,例如,一个照相app可能会在manifest文件中罗列CAMERA, READ_CONTACTS, and ACCESS_FINE_LOCATION权限,你应该测试每个权限同意或拒绝,并且保证app可以有相应的处理。
在命令行中使用adb工具管理权限:
按组罗列权限和状态:$ adb shell pm list permissions -d -g
同意和拒绝一个或者多个权限:
$ adb shell pm [grant | revoke] …
例如:
打开READ_CONTACTS权限
adb shell pm grant com.name.app android.permission.READ_CONTACTS1
关闭READ_CONTACTS权限
adb shell pm revoke com.name.app android.permission.READ_CONTACTS1
二、 权限代码分析
以申请摄像头为例:首先需要判断是否要申请摄像头权限:
public void showCamera(View view) { // 检查摄像头权限是否已经有效 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { // 摄像头权限还未得到用户的同意 requestCameraPermission(); } else { // 摄像头权限以及有效,显示摄像头预览界面 showCameraPreview(); } }1
2
3
4
5
6
7
8
9
10
11
正如代码中注释那样,需要先判断摄像头权限是否有限,如果权限还未有效,需要调用申请权限方法,如果已经有效,则直接显示摄像头预览界面。
打开摄像头预览界面不是本文的重点,所以相关的代码就不关注了。那么接下来看一下requestCameraPermission方法。
private void requestCameraPermission() { // 摄像头权限已经被拒绝 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CAMERA)) { // 如果用户已经拒绝劝降,那么提供额外的权限说明 Snackbar.make(mLayout, R.string.permission_camera_rationale, Snackbar.LENGTH_INDEFINITE) .setAction(R.string.ok, new View.OnClickListener() { @Override public void onClick(View view) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA); } }) .show(); } else { // 摄像头还没有被拒绝,直接申请 ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
上述代码先是判断权限是否已经被拒绝,如果被拒绝则通过Snackbar展示权限申请的原因,如果用户同意将会再次申请权限。权限申请的结果是在onRequestPermissionsResult返回的,下面来看这部分代码:
/** * Callback received when a permissions request has been completed. */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_CAMERA) { // BEGIN_INCLUDE(permission_result) // 收到摄像头权限申请的结果 // 检查摄像头权限是否已经通过 if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 摄像头权限已经申请成功,可以展示摄像预览界面了 Snackbar.make(mLayout, R.string.permision_available_camera, Snackbar.LENGTH_SHORT).show(); showCameraPreview(); } else { // 摄像头权限申请失败 Snackbar.make(mLayout, R.string.permissions_not_granted, Snackbar.LENGTH_SHORT).show(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
三、第三方SDK:EasyPermissions
Github上有一个比较火且简单易懂的第三方SDK可以简化权限管理,EasyPermissions可以详见github地址。接下来会大致介绍一下其用法,最后会发现其实和系统提供的权限管理很相似,理解起来也很简单。
1. 简单用法
首先需要在app的build.gradle文件中引入包,操作如下:dependencies { compile 'pub.devrel:easypermissions:0.1.7' }1
2
3
还以申请摄像头权限为例:
public class MainActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // 调用EasyPermissions的onRequestPermissionsResult方法,参数和系统方法保持一致,然后就不要关心具体的权限申请代码了 EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); } @Override public void onPermissionsGranted(int requestCode, List<String> list) { // 此处表示权限申请已经成功,可以使用该权限完成app的相应的操作了 // ... } @Override public void onPermissionsDenied(int requestCode, List<String> list) { // 此处表示权限申请被用户拒绝了,此处可以通过弹框等方式展示申请该权限的原因,以使用户允许使用该权限 // ... } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
首先需要在Activity实现EasyPermissions.PermissionCallbacks接口,该接口提供了onPermissionsGranted和onPermissionsDenied两个方法,也即权限申请成功和失败的回调方法,而EasyPermissions.PermissionCallbacks又实现了ActivityCompat.OnRequestPermissionsResultCallback,该接口提供了onRequestPermissionsResult方法,相当于EasyPermissions将系统的权限申请结果回调方法又进行了二次封装,同时提供了权限申请成功和失败的回调方法。
同时触发摄像头权限申请方法如下:
@AfterPermissionGranted(RC_CAMERA_PERM) public void cameraTask() { if (EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA)) { // 已经有摄像头权限了,可以使用该权限完成app的相应的操作了 Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show(); } else { // app还没有使用摄像头的权限,调用该方法进行申请,同时给出了相应的说明文案,提高用户同意的可能性 EasyPermissions.requestPermissions(this, getString(R.string.rationale_camera), RC_CAMERA_PERM, Manifest.permission.CAMERA); } }1
2
3
4
5
6
7
8
9
10
11
此处会先调用EasyPermissions.hasPermissions方法判断是否允许使用该权限,如果返回值为ture表示已经申请成功过该权限,则直接使用即可,如果返回值为false表示还没有申请过该权限,那么可以通过EasyPermissions.requestPermissions方法进行申请,同时给出申请原因文案。
通过查看EasyPermissions.hasPermissions的源码,可以看到该方法可以接收多个参数,即可以同时检查多个权限。
AfterPermissionGranted注解是可选的,如果有该注解的话,那么当request值对应的权限申请通过的话会自动调用该方法。
需要特别说明的是,当用户在系统弹出的权限申请对话框中拒绝权限并且勾选不再询问,那么下次系统讲不会自动尝试申请,但是可以在onPermissionsDenied方法中通过弹框的方式解释app需要该权限的理由,如果用户同意的话会再次尝试请求。
2. 源码分析
首先来看EasyPermissions.hasPermissions方法,可以看到先是有一个版本检查,因为Android 6.0之前是不需要在运行时检查权限的,然后就是调用系统提供的ContextCompat.checkSelfPermission方法,所以这个方法好理解的。public static boolean hasPermissions(Context context, String... perms) { // Always return true for SDK < M, let the system deal with the permissions if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { Log.w(TAG, "hasPermissions: API version < M, returning true by default"); return true; } for (String perm : perms) { boolean hasPerm = (ContextCompat.checkSelfPermission(context, perm) == PackageManager.PERMISSION_GRANTED); if (!hasPerm) { return false; } } return true; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
然后是权限申请requestPermissions方法,首先是通过checkCallingObjectSuitability判断版本号和是否是Acticity或Fragment调用,然后是根据入参拼接权限申请解释文案并通过对话框显示给用户,如果用户点击同意将会调用系统提供的requestPermissions方法,如果用户点击取消,只直接返回权限申请拒绝的回调方法onPermissionsDenied。
public static void requestPermissions(final Object object, String rationale, @StringRes int positiveButton, @StringRes int negativeButton, final int requestCode, final String... perms) { checkCallingObjectSuitability(object); final PermissionCallbacks callbacks = (PermissionCallbacks) object; boolean shouldShowRationale = false; for (String perm : perms) { shouldShowRationale = shouldShowRationale || shouldShowRequestPermissionRationale(object, perm); } if (shouldShowRationale) { // permission has ever denied Activity activity = getActivity(object); if (null == activity) { return; } AlertDialog dialog = new AlertDialog.Builder(activity) .setMessage(rationale) .setPositiveButton(positiveButton, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { executePermissionsRequest(object, perms, requestCode); } }) .setNegativeButton(negativeButton, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // act as if the permissions were denied callbacks.onPermissionsDenied(requestCode, Arrays.asList(perms)); } }).create(); dialog.show(); } else { executePermissionsRequest(object, perms, requestCode); } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
最后是onRequestPermissionsResult方法,该方法接收系统的权限申请结果方法,并做统一的处理。可以看到也是先通过checkCallingObjectSuitability判断版本号和是否是Acticity或Fragment调用,然后根据不同权限申请结果分别放置到通过和拒绝列表,可以看到如果拒绝列表不为空直接返回申请失败的回调,当成功列表不为空调用之前包含AfterPermissionGranted注解的方法,完成后续的业务动作。
public static void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults, Object object) { checkCallingObjectSuitability(object); PermissionCallbacks callbacks = (PermissionCallbacks) object; // Make a collection of granted and denied permissions from the request. ArrayList<String> granted = new ArrayList<>(); ArrayList<String> denied = new ArrayList<>(); for (int i = 0; i < permissions.length; i++) { String perm = permissions[i]; if (grantResults[i] == PackageManager.PERMISSION_GRANTED) { granted.add(perm); } else { denied.add(perm); } } // Report granted permissions, if any. if (!granted.isEmpty()) { // Notify callbacks callbacks.onPermissionsGranted(requestCode, granted); } // Report denied permissions, if any. if (!denied.isEmpty()) { callbacks.onPermissionsDenied(requestCode, denied); } // If 100% successful, call annotated methods if (!granted.isEmpty() && denied.isEmpty()) { runAnnotatedMethods(object, requestCode); } }
相关文章推荐
- Android 玩转IOC,Retfotit源码解析,教你徒手实现自定义的Retrofit框架
- Android TV开发笔记
- Android 玩转IOC,Retfotit源码解析,教你徒手实现自定义的Retrofit框架
- android控件—MuAutoCompleteTextView
- Android Service 服务(一)—— Service
- Android内存泄漏02
- Android launcher 开发笔记(一) 从脸蛋开始
- Android App监听软键盘按键的三种方式与改变软键盘右下角确定键样式
- Android M权限管理机制:Runtime Permission简介
- 关于 Android 进程保活,你所需要知道的一切
- Android之MVP入门使用(一)
- 今日学习
- Android内存泄漏01
- android自动化测试
- android 使用 .9.png 编译出错 Crunching Cruncher top1_bg.9.png failed, see logs
- Android launcher 开发笔记(二) launcher中常用的类
- 读取android真机里面的文件的方法
- Android Activity的四种LaunchMode!!!
- AndroidDeveloper Weekly No.1
- Android获取一个按钮的状态