1、应用场景
最近在做银行的项目,经过测试中心测试后提出需求,对于隐私权限需要满足以下两点:
1. 隐私权限的申请应该依托于具体的业务场景,在不需要某个隐私权限的场景内不得申请该权限,非业务需要的隐私权限不得阻碍正常的业务流程。
2. 需要明确告知用户具体的隐私权限用途,提供用户可以取消权限授权的途径。
针对以上两点我对应用隐私权限的申请进行了整合以及封装,支持在项目中最少量的代码调用,当然这里并不是封装成jar包,用兴趣的同学可以自己封装。
2、实际效果
这里我在权限申请之前告知用户具体的权限用途,文字描述可以根据实际的业务场景来描述,弹窗效果参照了网上的一位兄弟写的,链接如下https://github.com/haiyuKing/PermissionDialog
3、权限申请
1. PermissionDialogUtil类
这个类就是权限申请前的弹窗类,具体说明权限用途,文章最后我会提供源码,这里我们只看一下核心的代码
/**
* 初始化权限列表
* @param filter
* @param type
*/
private void initItem(ColorMatrixColorFilter filter,int type){
//初始化权限列表区域
View itemView = View.inflate(mContext, R.layout.permission_list_item, null);
String[] permissionTitles = mContext.getResources().getStringArray(R.array.permission_title);
String[] permissionInfos = mContext.getResources().getStringArray(R.array.permission_info);
String title = permissionTitles[type];
String info = permissionInfos[type];
((ImageView) itemView.findViewById(R.id.item_img)).setImageResource(mContext.getResources().obtainTypedArray(R.array.permission_icon).getResourceId(type, 0));
((ImageView) itemView.findViewById(R.id.item_img)).setColorFilter(filter);
((TextView) itemView.findViewById(R.id.item_title)).setText(title);
((TextView) itemView.findViewById(R.id.item_info)).setText(info);
mListLayout.addView(itemView);
}
````
这里就是权限名称以及相应描述和图片的创建。具体内容我们声明在attrs.xml文件中,内容如下:
````c
<!-- 权限图标【可根据实际情况修改】注意位置要和下边title、info相对应 -->
<integer-array name="permission_icon">
<item>@drawable/permission_ic_storage</item>
<item>@drawable/permission_ic_camera</item>
<item>@drawable/permission_ic_location</item>
<item>@drawable/permission_ic_phone</item>
<item>@drawable/permission_ic_phone</item>
<item>@drawable/permission_ic_micro_phone</item>
<item>@drawable/permission_ic_calendar</item>
<item>@drawable/permission_ic_contacts</item>
<item>@drawable/permission_ic_sms</item>
<item>@drawable/permission_ic_sensors</item>
</integer-array>
<!-- 权限标题【可根据实际情况修改 -->
<string-array name="permission_title">
<item>文件存储</item>
<item>照相机</item>
<item>位置信息</item>
<item>本机识别码</item>
<item>拨打电话</item>
<item>录制声音</item>
<item>日程信息</item>
<item>通讯录</item>
<item>短信信息</item>
<item>传感器</item>
</string-array>
<!-- 权限说明(这里应该是根据项目的实际情况,简要说明下该权限主要用于什么场景或者功能)【可根据实际情况修改】 -->
<string-array name="permission_info">
<item>允许应用读取、写入外部存储,应用于版本更新、相册功能</item>
<item>允许应用访问摄像头进行拍照,应用于个人头像功能</item>
<item>允许应用获取定位信息</item>
<item>允许应用获取本机识别码,应用于账号和手机的绑定关系,该权限为必须权限</item>
<item>允许应用内拨打电话,应用于联系客服</item>
<item>允许应用通过手机或耳机的麦克风录制声音</item>
<item>允许应用读取用户的日程信息</item>
<item>允许应用访问联系人通讯录信息</item>
<item>允许应用获取短信信息</item>
<item>允许应用获取传感器信息</item>
</string-array>
注意标题、图像、描述位置是一一对应的,这里权限的位置在后边我们会用到。
2. PermissionUtils类
public static void checkPermission(final Activity activity, final OnPermissionListener grantedListener, final String... permissions) {
boolean havePermission = true;
final ArrayList<Integer> types = new ArrayList<Integer>();
for (String permission : permissions) {
switch (permission) {
case Manifest.permission.WRITE_EXTERNAL_STORAGE:
case Manifest.permission.READ_EXTERNAL_STORAGE:
if (!types.contains(PermissionType.STORAGE)) {
types.add(PermissionType.STORAGE);
}
break;
case Manifest.permission.CAMERA:
if (!types.contains(PermissionType.CAMERA)) {
types.add(PermissionType.CAMERA);
}
break;
case Manifest.permission.READ_PHONE_STATE:
if (!types.contains(PermissionType.PHONE)) {
types.add(PermissionType.PHONE);
}
break;
case Manifest.permission.CALL_PHONE:
if (!types.contains(PermissionType.CALLPHONE)) {
types.add(PermissionType.CALLPHONE);
}
break;
case Manifest.permission.ACCESS_COARSE_LOCATION:
if (!types.contains(PermissionType.LOCATION)) {
types.add(PermissionType.LOCATION);
}
break;
case Manifest.permission.READ_SMS:
case Manifest.permission.SEND_SMS:
if (!types.contains(PermissionType.SMS)) {
types.add(PermissionType.SMS);
}
break;
default:
break;
}
if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
havePermission = false;
}
}
if (!havePermission) {
PermissionDialogUtil permissionDialog = new PermissionDialogUtil(activity, types);
permissionDialog.setOnSureClickListener(new PermissionDialogUtil.OnSureClickListener() {
@Override
public void onSureClick() {
types.clear();
RxPermissions rxPermission = new RxPermissions((FragmentActivity) activity);
rxPermission
.requestEachCombined(permissions)
.subscribe(new Consumer<Permission>() {
@Override
public void accept(Permission permission) throws Exception {
if (permission.granted) {
grantedListener.onGranted();
} else if (permission.shouldShowRequestPermissionRationale) {
grantedListener.onRefused();
} else {
DialogUtils.showDotAskDialog(activity, new DialogUtils.OnDialogClickListener() {
@Override
public void confirm() {
goSystemStetting(activity);
}
@Override
public void cancel() {
}
});
}
}
});
}
});
permissionDialog.show();
} else {
grantedListener.onGranted();
}
}
````
在checkPermission方法中,我们比对传递过来的权限,只有需要申请的权限才会显示在弹窗中,PermissionType是一个枚举类如下:
````
public interface PermissionType {
@IntDef({STORAGE, CAMERA, LOCATION, PHONE, CALLPHONE, SMS})
@Retention(RetentionPolicy.SOURCE)
@interface Type {
}
int STORAGE = 0;
int CAMERA = 1;
int LOCATION = 2;
int PHONE = 3;
int CALLPHONE = 4;
int SMS = 8;
}
注意声明的枚举后边的数字要等同于上边attrs中权限的实际位置,例如sms在attrs中就处于第八个,当然这个顺序可以自己更改,只要能对应上即可。
权限的申请流程我使用了RxPermissions的requestEachCombined方法,这个方法会将申请的权限当成一组权限,只有全部允许permission.granted才会为true,permission.shouldShowRequestPermissionRationale方法可以用来判断用户是否设置了不在提醒按钮,对于这种权限我的处理方法是下次申请直接跳转到设置页面中,让用户自行开启。DialogUtils.showDotAskDialog弹窗就是用来实现这个功能,内容简单不做介绍,源码中有。
3. 具体调用
我们只需要在需要权限申请的位置调用
PermissionUtils.checkPermission(this, new OnPermissionListener() {
@Override
public void onGranted() {
}
@Override
public void onRefused() {
Toast.makeText(getActivity(), "用户拒绝", Toast.LENGTH_SHORT).show();
}
}, Manifest.permission.CALL_PHONE, Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.READ_SMS);
在onGranted()方法中处理允许流程,onRefused()处理拒绝流程,如果是在Fragment中调用我们只需要将传入的this换成getActivity()。具体需要申请的权限个数不固定,也可以传入一个。
4、权限管理
对于应用中需要的隐私权限我们需要对他们进行管理,效果参照支付宝。
实现也很简单,我们只需要在PermissionUtils类中增加一个checkPermission()方法,这个方法是用来判断权限是否申请。
public static boolean checkPermission(final Activity activity, String... permissions) {
for (String permission : permissions) {
if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
return false;
}
}
return true;
}
然后我们在权限管理页面的onResume()方法中进行状态的判断即可。
@Override
public void onResume() {
super.onResume();
if (PermissionUtils.checkPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE)) {
ps_storage.setTvRightStyle(getResources().getString(R.string.tv_right), getResources().getColor(R.color.title_text2));
} else {
ps_storage.setTvRightStyle(getResources().getString(R.string.tv_right_no), getResources().getColor(R.color.link_color));
}
if (PermissionUtils.checkPermission(getActivity(), Manifest.permission.CAMERA)) {
ps_camera.setTvRightStyle(getResources().getString(R.string.tv_right), getResources().getColor(R.color.title_text2));
} else {
ps_camera.setTvRightStyle(getResources().getString(R.string.tv_right_no), getResources().getColor(R.color.link_color));
}
if (PermissionUtils.checkPermission(getActivity(), Manifest.permission.ACCESS_COARSE_LOCATION)) {
ps_location.setTvRightStyle(getResources().getString(R.string.tv_right), getResources().getColor(R.color.title_text2));
} else {
ps_location.setTvRightStyle(getResources().getString(R.string.tv_right_no), getResources().getColor(R.color.link_color));
}
if (PermissionUtils.checkPermission(getActivity(), Manifest.permission.CALL_PHONE)) {
ps_phone.setTvRightStyle(getResources().getString(R.string.tv_right), getResources().getColor(R.color.title_text2));
} else {
ps_phone.setTvRightStyle(getResources().getString(R.string.tv_right_no), getResources().getColor(R.color.link_color));
}
if (PermissionUtils.checkPermission(getActivity(), Manifest.permission.READ_SMS)) {
ps_sms.setTvRightStyle(getResources().getString(R.string.tv_right), getResources().getColor(R.color.title_text2));
} else {
ps_sms.setTvRightStyle(getResources().getString(R.string.tv_right_no), getResources().getColor(R.color.link_color));
}
}
跳转手机设置中心的方法如下:
public void goSystemStetting() {
Intent intent = getAppDetailSettingIntent();
startActivity(intent);
}
private Intent getAppDetailSettingIntent() {
Intent localIntent = new Intent();
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
localIntent.setData(Uri.fromParts("package", getActivity().getPackageName(), null));
} else if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.FROYO) {
localIntent.setAction(Intent.ACTION_VIEW);
localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails");
localIntent.putExtra("com.android.settings.ApplicationPkgName", getActivity().getPackageName());
}
return localIntent;
}