您的位置:首页 > 移动开发 > Android开发

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_CONTACTS
1

关闭READ_CONTACTS权限
adb shell pm revoke com.name.app android.permission.READ_CONTACTS
1


二、 权限代码分析

以申请摄像头为例:

首先需要判断是否要申请摄像头权限:
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);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: