通用核心代码库-code: 适合入门不久的同学学习借鉴编程思想,也可以直接拿来使用。
它包含了我从业几年以来积累的、最常用的封装类和基础框架等等,几乎每个项目都可以引入它,实现快速开发。
code的github地址:https://github.com/chenyugui/code
好了,开始介绍今天的主角: PermissionBaseActivity
1. 目标
虽然android6.0时代已经过去很久了,但是动态权限申请作为每个APP都需要用的功能,所以把它纳入「核心代码库」里了,今天还是继续讲一讲吧,主要是让新手同学学习学习其封装思想,不用依赖第三方框架,学会自己封装,也可以实现'一句话申请权限'
。
2. 前言
在android6.0之前,开发者无需在java代码里进行危险权限
的申请,只需在AndroidManifest.xml文件里进行权限声明即可,但从android6.0开始,google为了用户的安全考虑,加入了动态权限机制,危险权限
必须在代码里申请,在手机界面上会弹窗提示用户是否允许相应权限,当然,AndroidManifest.xml文件里一样还是要进行声明,别忘了这一点哦。(ps:加入了动态权限,虽然对开发者而言是麻烦了一些,但是对整体的android环境有一定的改善,流氓软件不能像以前那样流氓了。)
3. 什么是危险权限
危险权限,也可以理解成较敏感权限,有哪些呢,请看下表:
group:android.permission-group.CONTACTS
permission:android.permission.WRITE_CONTACTS
permission:android.permission.GET_ACCOUNTS
permission:android.permission.READ_CONTACTS
group:android.permission-group.PHONE
permission:android.permission.READ_CALL_LOG
permission:android.permission.READ_PHONE_STATE
permission:android.permission.CALL_PHONE
permission:android.permission.WRITE_CALL_LOG
permission:android.permission.USE_SIP
permission:android.permission.PROCESS_OUTGOING_CALLS
permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR
permission:android.permission.READ_CALENDAR
permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA
permission:android.permission.CAMERA
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS
permission:android.permission.READ_SMS
permission:android.permission.RECEIVE_WAP_PUSH
permission:android.permission.RECEIVE_MMS
permission:android.permission.RECEIVE_SMS
permission:android.permission.SEND_SMS
permission:android.permission.READ_CELL_BROADCASTS
很明显可以看到,一个group下面带有一个或多个permission,这是什么情况呢?
其实是这样的,google把相类似的权限都分到了对应的组里,这样有什么好处? 仅仅是为了好看、方便记忆?
不不,好处是:属于同一组的权限不用重复授权。例如,如果用户之前已经授权过WRITE_CONTACTS
权限的话,当你申请获取READ_CONTACTS
的时候,不需要等待用户同意授权,而是直接返回授权成功。
当然,既然是以分组的形式进行授权,那么系统的授权dialog
上面提示的也会是具体的组名,而不是单个权限名。
4. 怎么申请危险权限
4.1 先看看相关的API:
// 第二个参数可以从`Manifest.permission`里取值
// 第三个参数会在`onRequestPermissionsResult`方法里回调回来
ActivityCompat.requestPermissions(final Activity activity,
final String[] permissions, final int requestCode)
调用此方法后,如果系统版本版本小于android6.0、或者用户已经授权过对应组的权限、或者用户已经拒绝过该组权限并且选择了“不再提示”,都会直接回调activity的onRequestPermissionsResult
方法,否则会弹出系统对话框询问用户是否允许该权限组,当用户点了拒绝或者同意权限,也都同样会回调activity的onRequestPermissionsResult
方法。
4.2 那么,重点逻辑处理似乎就是在onRequestPermissionsResult
方法里了。
- 先来看看该方法的参数:
// 1. requestCode即调用ActivityCompat.requestPermissions时填的requestCode。
// 2. permissions即调用ActivityCompat.requestPermissions时填的permissions。
// 注意如果targetSdkVersion版本小于26,则permissions是空数组
// 3. grantResults即存储着权限授权结果。
// 注意如果系统版本小于android6.0,则grantResults是空数组
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
- 所以,只要grantResults数组的size > 0 并且 其中一个为“拒绝授权”,则为有权限被拒绝了
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {// 拒绝权限
// 有权限被决绝,执行拒绝权限后的逻辑,例如退出界面
// xxx
return;
}
}
// 执行同意权限后的逻辑
// xxx
- 应用权限设置界面跳转提醒
权限拒绝有两种情况:1. 不勾选“不再提醒”; 2.勾选“不再提醒”。
如果用户勾选了“不再提醒”,那么再次申请权限的时候,系统不会弹出授权dialog,此时我们的app应该给予提示,否则用户很有可能会感觉莫名其妙:怎么进来这个界面后又退出来了。
那么问题来了,怎么判断有没有勾选“不再提醒”
,android提供了一个API:
// 1:用户拒绝了该权限,没有勾选"不再提醒",此方法将返回true。
// 2:用户拒绝了该权限,有勾选"不再提醒",此方法将返回 false。
// 3:如果用户同意了权限,此方法返回false
boolean flag = ActivityCompat.shouldShowRequestPermissionRationale(Activity , String[]])
所以,只要在判断出是拒绝权限的地方,判断出shouldShowRequestPermissionRationale返回false,即为需要提醒跳转。下面贴下代码:
// 循环判断权限,只要有一个拒绝了,则回调onReject()。 全部允许时才回调onAllow()
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {// 拒绝权限
// 对于 ActivityCompat.shouldShowRequestPermissionRationale
// 1:用户拒绝了该权限,没有勾选"不再提醒",此方法将返回true。
// 2:用户拒绝了该权限,有勾选"不再提醒",此方法将返回 false。
// 3:如果用户同意了权限,此方法返回false
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {
// 拒绝选了"不再提醒",一般提示跳转到权限设置页面,
// 并在dialog cancel的时候,执行onReject();
showTipDialog(permissions[i]);
} else {
// 有权限被决绝,执行拒绝权限后的逻辑
onReject();
}
return;
}
}
// 执行同意权限后的逻辑
onAllow();
5. 动态请求封装,实现'一句话申请权限'
其实原理很简单,就是用一个PermissionBaseActivity父类来处理权限的申请和判断,然后利用‘延迟实现’的思想,通过回调的方式,把具体的同意或拒绝授权后的业务逻辑交给子类来实现。还是直接贴代码吧:
public class PermissionBaseActivity extends AppCompatActivity implements CancelAble {
protected final String TAG = getClass().getSimpleName().replace("Activity", "Act");
private SparseArray listenerMap;
protected TipDialogCreator tipDialogCreator;
private int requestCode = 1;
@Override
public void toCancel() {
finish();
}
/**
* 权限请求结果监听者
*/
public interface OnPermissionResultListener {
/**
* 权限被允许
*/
void onAllow();
/**
* 权限被拒绝
*/
void onReject();
}
/**
* 镜像权限申请
*
* @param onPermissionResultListener 申请权限结果回调
*/
public void checkPermissions(final String[] permissions, OnPermissionResultListener onPermissionResultListener) {
if (Build.VERSION.SDK_INT < 23 || permissions.length == 0) {
// android6.0以下不需要申请,直接为"同意"
if (onPermissionResultListener != null) {
onPermissionResultListener.onAllow();
}
} else {
if (onPermissionResultListener != null) {
if (listenerMap == null) {
listenerMap = new SparseArray<>();
}
listenerMap.put(requestCode, onPermissionResultListener);
}
ActivityCompat.requestPermissions(this, permissions, requestCode);
requestCode++;
}
}
/**
* 跳转系统的App应用详情页
*/
protected void toAppDetailSetting() {
Intent localIntent = new Intent();
localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
localIntent.setData(Uri.fromParts("package", getPackageName(), null));
startActivity(localIntent);
}
@Override
protected void onDestroy() {
if (listenerMap != null) {
listenerMap.clear();
}
listenerMap = null;
super.onDestroy();
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (listenerMap == null) {
return;
}
final OnPermissionResultListener onPermissionResultListener = listenerMap.get(requestCode);
if (onPermissionResultListener == null) {
return;
}
listenerMap.remove(requestCode);
// 循环判断权限,只要有一个拒绝了,则回调onReject()。 全部允许时才回调onAllow()
for (int i = 0; i < grantResults.length; i++) {
if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
// 拒绝被拒绝权限
// 对于 ActivityCompat.shouldShowRequestPermissionRationale
// 1:用户拒绝了该权限,没有勾选"不再提醒",此方法将返回true。
// 2:用户拒绝了该权限,有勾选"不再提醒",此方法将返回 false。
// 3:如果用户同意了权限,此方法返回false
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {
// 拒绝选了"不再提醒",一般提示跳转到权限设置页面
if (tipDialogCreator == null) {
tipDialogCreator = new TipDialogCreator(this, this);
}
tipDialogCreator.showTipDialog(PermissionUtil.getTip(permissions[i]), new TipDialog.TipClickCallBack() {
@Override
public void onConfirm() {
toAppDetailSetting();
onPermissionResultListener.onReject();
}
@Override
public void onCancel() {
onPermissionResultListener.onReject();
}
});
} else {
onPermissionResultListener.onReject();
}
return;
}
}
onPermissionResultListener.onAllow();
}
}
其中用了一个listenerMap
来维护多个请求,避免如果同时有多个权限申请请求,onRequestPermissionsResult会乱套的。
处理完授权判断后,从map里移除listener,减少内存占用。
如果是要在Fragment界面进行申请权限,咱们也封装了一个PermissionBaseFragment
,代码很简单:
public class PermissionBaseFragment extends Fragment {
public void checkPermissions(final String[] permissions, PermissionBaseActivity.OnPermissionResultListener onPermissionResultListener) {
Activity activity = getActivity();
if (activity instanceof PermissionBaseActivity) {
((PermissionBaseActivity) activity).checkPermissions(permissions, onPermissionResultListener);
} else {
throw new ClassCastException("Want to use checkPermissions, The fragment's Activity must extends PermissionBaseActivity");
}
}
}
- 子类使用:
checkPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, new OnPermissionResultListener() {
@Override
public void onAllow() {
Log.d(TAG, "onAllow: ");
}
@Override
public void onReject() {
Log.d(TAG, "onReject: ");
}
});
是不是很简洁咧,理解原理后,稍微封装下就好,也不用去引用什么第三方框架了。
6. 额外说一下,责任单一原则
大家都知道,写android app的时候,基本上都有一个BaseActivity,但是有些同学会把很多逻辑都一起写在BaseActivity这个类里,感觉像大杂烩一样,很不美观,后期有问题了或者需要优化了,定位不好定位。
建议,遵循责任单一原则:
权限相关的逻辑就写在PermissionBaseActivity里;
Mvp相关的绑定逻辑,就写在MvpBaseActivity里,等等。
最后再去处理继承关系就好了。
↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
PS: 分享不易,觉得有用的同学,点个赞鼓励一下呗。可以关注公众号「Grade桂」,持续分享技术信息和职场经验