今天在测试APP的时候发现部分手机APP定位权限使用不了。看了一下机型都是Android6.0以上版本的Android手机。
之前就听说Android6.0以上版本权限管理更严格了,没想到今天踩坑了。
6.0版本之前的权限管理都是一些手机系统自己写的权限管理,比如说小米手机系统,魅族...这些都自己带有自己权限管理。
这样子容易出现的问题是:
1.如果某个权限被禁用了。应用开发中根本没提供方法来判断该权限是否被禁用。我们就需要自己的方法去判断。
比如说录音权限被禁用了:http://blog.csdn.net/omrapollo/article/details/51150280,是从录音功能下手做判断。而不是从权限列表获取权限状态。这是真的坑。
2.我们知道权限被禁用了,还需要判断一下是哪个系统好提醒用户按照操作去开启这个权限。
也就是说开发过程中需要把主流的机型测试一遍。然后如果有权限管理的话把权限流程过一遍。加个判别系统的方法,然后在把流程做成一个一个加上去。简直可怕。
Google最终解决方案,给系统弄一个权限管理。
太棒了,权限管理由Android系统本身来做才是对开发者最友好的解决方案。
废话不多说,接下来就讲讲6.0版本权限问题的终极解决方案吧。
Android在 6.0以上版本需要进行动态的权限配置。
一、SDK新增方法
1.在ActivityCompat增加的方法
ActivityCompat.requestPermissions(context, needPermissions, REQUEST_CODE_REQUEST_PERMISSION);
这个方法用来动态配置权限。如果没有所需的权限。将会弹窗提醒用户配置权限。 三个参数分别为:当前上下文,需要申请的权限列表列表,请求码。请求码用于后面回调返回请求结果用。
ActivityCompat.checkSelfPermission(context, needPermission);
这个方法用来监测某个权限是否被配置。 两个参数分别为:当前上下文,需要监测的权限信息。 这个方法的返回值有两个0或-1 分别是已授权,和未授权
在PackManager这个类中有与之相匹配的常量
/**
* Permission check result: this is returned by {@link #checkPermission}
* if the permission has been granted to the given package.
*/
public static final int PERMISSION_GRANTED =
0;
/**
* Permission check result: this is returned by {@link #checkPermission}
* if the permission has not been granted to the given package.
*/
public static final int PERMISSION_DENIED = -
1;
2.Activity中增加回调方法
@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull
int[]grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
这个方法会在用户配置完权限后回调 三个回调参数分别是:请求码,请求的权限,权限当前状态
二、写个demo试验一下
package cn.xiaolongonly.myapplication;
import android.Manifest;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
public class MainActivity extends AppCompatActivity {
private String[] requestPermissions = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int write = ActivityCompat.checkSelfPermission(
this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int read = ActivityCompat.checkSelfPermission(
this, Manifest.permission.READ_EXTERNAL_STORAGE);
int coarse = ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_COARSE_LOCATION);
int fine = ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION);
Log.d(
"permissionState:",
"write:"+write +
"_" +
"read:"+read +
"_" +
"coarse:"+coarse +
"_" +
"fine:"+fine);
ActivityCompat.requestPermissions(
this, requestPermissions,
10086);
}
@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
Log.d(
"RequestCode", requestCode +
"");
for (
int i =
0; i < permissions.length; i++) {
Log.d(
"permissionInfo",
"permissionName:" + permissions[i] +
"permission State:" + grantResults[i]);
}
int write = ActivityCompat.checkSelfPermission(
this, Manifest.permission.WRITE_EXTERNAL_STORAGE);
int read = ActivityCompat.checkSelfPermission(
this, Manifest.permission.READ_EXTERNAL_STORAGE);
int coarse = ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_COARSE_LOCATION);
int fine = ActivityCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION);
Log.d(
"permissionState:",
"write:"+write +
"_" +
"read:"+read +
"_" +
"coarse:"+coarse +
"_" +
"fine:"+fine);
}
}
打印出的Log如下
允许的情况
D/CreatePermissionState:: write:-1_read:-1_coarse:-1_fine:-1 D/RequestCode: 10086 D/permissionInfo: permissionName:android.permission.WRITE_EXTERNAL_STORAGEpermission State:0 D/permissionInfo: permissionName:android.permission.READ_EXTERNAL_STORAGEpermission State:0 D/permissionInfo: permissionName:android.permission.ACCESS_COARSE_LOCATIONpermission State:-1 D/permissionInfo: permissionName:android.permission.ACCESS_FINE_LOCATIONpermission State:-1 D/FinishPermissionState:: write:0_read:0_coarse:-1_fine:-1
拒绝后
D/CreatePermissionState:: write:-1_read:-1_coarse:-1_fine:-1 D/RequestCode: 10086 D/permissionInfo: permissionName:android.permission.WRITE_EXTERNAL_STORAGEpermission State:-1 D/permissionInfo: permissionName:android.permission.READ_EXTERNAL_STORAGEpermission State:-1 D/permissionInfo: permissionName:android.permission.ACCESS_COARSE_LOCATIONpermission State:-1 D/permissionInfo: permissionName:android.permission.ACCESS_FINE_LOCATIONpermission State:-1 D/FinishPermissionState:: write:-1_read:-1_coarse:-1_fine:-1
界面:
出现的问题
发现只出现一个授权文件读写的提醒框。 最后发现我在清单文件Manifest中只注册了读写权限。没有注册网络定位和精准定位权限。加上这两个。 这次启动的时候就出现了两个提示框了。(授权文件读写和定位) 可以看出,文件读写是归一个权限控制来管理的,两个定位权限也是归于一个权限控制来管理的。 清单权限注册
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
注意事项:
需要动态配置的权限一定要在清单文件中做注册,不然默认都是不做配置的。之前踩过的一个坑是只注册了COARSE_LOCATION没有注册FINE_LOCATION,发现FINE_LOCATION一直是未授权,但是COARSE_LOCATION授权成功了。
当用户拒绝权限授权之后,在次权限申请的时候是会出现不在询问的提示框的。勾选之后只能选择拒绝权限。拒绝之后以后就不会再做提醒了, 权限会自动被拒绝,requestPermissions方法是有走的,系统自动拒绝了这个权限。所以在onRequestPermissionResult()中一样能看到不提醒但requestPermissions做了申请的权限。
三、最后再对代码进行简单的封装
package cn.xiaolongonly.myapplication;
import android.Manifest;
import android.app.Activity;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.Settings;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.widget.Toast;
public class PermissionUtil {
public static final int REQUEST_CODE_REQUEST_PERMISSION =
1027;
private static final String PACKAGE_URL_SCHEME =
"package:";
private static final int REQUEST_CODE_REQUEST_SETTING =
1028;
public interface PermissionListener {
void allGranted();
}
public static class PermissionTool {
public String[] lackPermission;
/**
* 需要进行检测的权限数组
*/
public String[] mainNeedPermissions = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION,
};
/**
* 需要进行检测的权限数组
*/
public String[] qrCodeNeedPermissions = {
Manifest.permission.CAMERA
};
/**
* 需要进行检测的权限数组
*/
public String[] requestNeedPermissions = {
Manifest.permission.READ_PHONE_STATE
};
private PermissionListener mListener;
public PermissionTool(PermissionListener listener) {
mListener = listener;
}
public void checkAndRequestPermission(Activity activity, String... needPermissions) {
boolean isAllGranted =
true;
for (String needPermission : needPermissions) {
if (ActivityCompat.checkSelfPermission(activity, needPermission)
!= PackageManager.PERMISSION_GRANTED) {
isAllGranted =
false;
}
Log.d(
"state", ActivityCompat.checkSelfPermission(activity, needPermission) +
"");
}
if (isAllGranted) {
mListener.allGranted();
}
else {
ActivityCompat.requestPermissions(activity, needPermissions, REQUEST_CODE_REQUEST_PERMISSION);
}
}
public void onRequestPermissionResult(
final Activity activity,
int requestCode, String[] permissions,
int[] grantResults) {
if (requestCode == REQUEST_CODE_REQUEST_PERMISSION) {
boolean allGrant =
true;
for (
int i =
0; i < grantResults.length; i++) {
if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
allGrant =
false;
}
Log.d(
"PermissionTool",
"onRequestPermissionResult(): " + grantResults[i] +
"permission" + permissions[i]);
}
if (allGrant) {
mListener.allGranted();
}
else {
Toast.makeText(activity,
"部分所需权限未开启,将影响功能正常使用,请开启权限!", Toast.LENGTH_LONG).show();
startAppSettings(activity);
}
}
}
public void startAppSettings(Activity activity) {
Intent intent =
new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
intent.setData(Uri.parse(PACKAGE_URL_SCHEME + activity.getPackageName()));
activity.startActivityForResult(intent, REQUEST_CODE_REQUEST_SETTING);
}
}
}
这里将权限管理的方法和回调都抽取到一个类中了。直接调用方法和回调就可以了。
值得注意的是:在自己写的APP有一些页面没有这些授权是无法使用的。但是用户又禁止了权限。而用户可能不知道要去哪里开启这个权限,就可以通过打开当前应用的设置页面来引导用户开启。startAppSettings(Activity activity)
使用:
package cn.xiaolongonly.myapplication;
import android.Manifest;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
private String[] requestPermissions = {
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION};
private PermissionUtil.PermissionTool permissionTool;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (Build.VERSION.SDK_INT >=
23) {
permissionTool =
new PermissionUtil.PermissionTool(
new PermissionUtil.PermissionListener() {
@Override
public void allGranted() {
initView();
}
});
permissionTool.checkAndRequestPermission(MainActivity.
this, requestPermissions);
}
else {
initView();
}
}
private void initView() {
}
@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull
int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
permissionTool.onRequestPermissionResult(
this, requestCode, permissions, grantResults);
}
}
总结对于Android6.0运行时权限暂时就知道怎么配置了,如果有不足的地方欢迎指出。 demo下载
新功能
这边顺便讲一下在manifest清单文件中多了
<uses-permission-sdk-23 android:name="android.permission.ACCESS_FINE_LOCATION"/>
这样的权限设置,这个跟应用动态授权没有什么关系。 在谷歌官方文档 是这么解释的
指明应用需要特定权限,但仅当应用在 API 级别 23 或更高版本的设备上运行时才需要。如果设备运行的是 API 级别 22 或更低版本,则应用没有指定的权限。
当您更新应用以包含需要其他权限的新功能时,此元素很有用。如果用户在运行 API 级别 22 或更低版本的设备更新应用,系统在安装时会提示用户授予在该更新中声明的所有新权限。如果某个新功能无关紧要,您可能想同时在这些设备上停用该功能,以便用户不需要授予额外权限即可更新应用。如果使用 <uses-permission-sdk-23> 元素而不使用 <uses-permission>,则仅当应用在支持运行时权限模式(用户在应用运行时向其授予权限)的平台上运行时才可请求权限。
如需了解有关权限的详细信息,请参阅简介的权限部分和单独的系统权限 API 指南。在 android.Manifest.permission 上可以找到基础平台定义的权限列表。
也就是说如果使用的是来注册的话这个权限就只能运行时动态赋予。 而版本低于SDK23的Android设备不支持动态设置权限。所以低于SDK23的应用是不能有这个权限的。
好消息好消息
谷歌Developers中国网站今天发布了。
英文四级的我感到非常happy。
毕竟看英文文档都要借助翻译一个单词一个单词查还是很辛苦的,现在文档都有官方的中文版本了。
手动撒花!!!!手动撒花!!!!手动撒花!!!!