自从Android6.0之后,运行时权限申请就成为了一大痛点!动态申请吧,说它难吧,它又不难,说它不难吧,申请起来贼复杂;不申请吧,还要给你整闪退。java
其实如今有不少权限申请框架了,那么为何我还在造轮子呢?其实缘由很简单,多是年纪大了,开始喜欢简约风了,能少写一行的我绝对不想多写一个字母!说到底仍是以为现有的不少框架,用起来不是那么顺手,配置相对复杂,因此萌生了再整一个一行代码实现权限申请的想法。git
在工做之余,打算将一些经常使用的逻辑页面,模块,功能点作成library库,这样当有类似需求的时候,能够作到插拔式开发!如今系列中有如下内容github
具体的实现demo已经放到Github了,效果图什么的也在上面,上面也有写好的demo apk提供下载尝试,若是你以为有用,记得star哦!哈哈哈,就是这么不要脸~~~app
Github地址框架
权限申请无非三板斧,第一:检查是否已受权,第二:发起受权申请,第三:处理受权结果!第一步和第二步是固定的操做,只不过咱们能够将繁琐的步骤封装起来。ide
第三步才是很差处理的核心,正常来讲,当咱们发起权限申请以后,必需要重写onRequestPermissionsResult方法来对受权结果进行处理,这就致使了不管你怎么封装,仍是必须侵入到每一个Activity的onRequestPermissionsResult中去,这就意味着无论怎样,你都至少要在两个地方写上对应的代码才能实现受权功能。可是做为调用者来讲,我但愿的操做只有两个,一,提供我须要受权的权限,二,回调给我受权结果。工具
如何实现经过回调的方式反馈给调用者受权结果呢?最开始想法是写一个透明宽高各1像素大小的Activity,将权限申请和受权结果的操做都放在里面处理,处理完以后关闭此Activity,对于用户来讲其实也是无感知的。可是有一个最大的问题就是,当在这个Activity处理完onRequestPermissionsResult以后,如何才能经过回调的方式告知调用方呢?因为打开新的Activity以后,调用方Activity和处理逻辑的Activity是两个不一样的页面,在不借助其余技术的状况下,是很差实现相似接口回调的方式进行通信的,因此摒弃了这个方案。oop
后来想到,既然Activity不能胜任这份工做,能够尝试一下它的兄弟Fragment,Fragment中也能够发起受权申请并处理受权结果,最重要的是,Fragment是依附于Activity的,因此咱们能够在同一个Activity下进行权限的申请和处理,而且不会污染到宿主Activity的逻辑,美滋滋。优化
配置并申请权限ui
对于调用者来讲,他须要作的就是配置他须要申请的权限列表。因此咱们须要提供一个入口给他。
FanPermissionUtils.with(MainActivity.this)
//添加全部你须要申请的权限
.addPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.addPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
.addPermissions(Manifest.permission.CALL_PHONE)
.addPermissions(Manifest.permission.ACCESS_WIFI_STATE)
.addPermissions(Manifest.permission.CAMERA)
...
复制代码
咱们经过FanPermissionUtils.with方法构造出一个FanPermissionUtils工具类,使用链式方法让用户配置他须要申请的全部权限。FanPermissionUtils的内部维护了权限列表,存储全部须要受权的权限。
调用者配置好权限以后,调用startCheckPermission开始进行权限申请。
FanPermissionUtils.with(MainActivity.this)
//添加全部你须要申请的权限
.addPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.addPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
.addPermissions(Manifest.permission.CALL_PHONE)
.addPermissions(Manifest.permission.ACCESS_WIFI_STATE)
.addPermissions(Manifest.permission.CAMERA)
//开始受权
.startCheckPermission();
复制代码
此时,咱们会构造一个没有页面的FanPermissionFragment来处理受权相关的逻辑,并将构造出的Fragment添加到宿主Activity中。
//构造FanPermissionFragment并将本身添加到宿主Activity中
FanPermissionFragment.newInstance(permissions).start(mContext);
/** * 开始请求 */
public void start(Activity activity) {
if (activity != null) {
mContext = activity;
if (Looper.getMainLooper() != Looper.myLooper()) {
return;
}
activity.getFragmentManager().beginTransaction().add(this, activity.getClass().getName()).commit();
}
}
复制代码
一旦FanPermissionFragment被构造,就会对调用方提供的权限列表进行逐一排查,罗列出还未受权的权限列表,并对这些未受权的权限发起二次权限申请。
//记录未受权的权限
List<String> deniedPermissions = new ArrayList<>();
for (String permission : permissions) {
int check = ContextCompat.checkSelfPermission(getActivity(), permission);
if (check == PackageManager.PERMISSION_GRANTED) {
//受权经过了已经 do nothing
} else {
deniedPermissions.add(permission);
}
}
if (deniedPermissions.size() != 0) {
//有权限没有经过
requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), PERMISSION_REQUEST_CODE);
} else {
//受权所有经过
}
复制代码
这样咱们就完成了第一步和第二步。
对受权结果进行处理
对于调用者来讲,最佳的体验就是回调的方式反馈受权结果。先定义一个回调接口。
public interface FanPermissionListener {
/* * 受权所有经过 */
void permissionRequestSuccess();
/* * 受权未经过 * @param grantedPermissions 已经过的权限 * @param deniedPermissions 拒绝的权限 * @param forceDeniedPermissions 永久拒绝的权限(也就是用户点击了再也不提醒的那些权限) */
void permissionRequestFail(String[] grantedPermissions, String[] deniedPermissions, String[] forceDeniedPermissions);
}
复制代码
调用者在构造FanPermissionUtils的时候,设置回调监听便可。
FanPermissionUtils.with(MainActivity.this)
//添加权限申请回调监听 若是申请失败 会返回已申请成功的权限列表,用户拒绝的权限列表和用户点击了再也不提醒的永久拒绝的权限列表
.setPermissionsCheckListener(new FanPermissionListener() {
@Override
public void permissionRequestSuccess() {
//全部权限受权成功才会回调这里
}
@Override
public void permissionRequestFail(String[] grantedPermissions, String[] deniedPermissions, String[] forceDeniedPermissions) {
//当有权限没有被受权就会回调这里
}
});
复制代码
咱们在FanPermissionUtils中会保存调用者设置的回调接口,而后在FanPermissionFragment被构造的时候将这个回调接口传给FanPermissionFragment。
FanPermissionFragment.newInstance(permissions).setPermissionCheckListener(listener).start(mContext);
复制代码
而后在FanPermissionFragment中去处理申请受权以后的受权回调操做。将受权回调的结果经过调用者设置的回调监听返回给调用者。来实现侵入性最小的权限申请方案。
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
//记录点击了再也不提醒的未受权权限
List<String> forceDeniedPermissions = new ArrayList<>();
//记录点击了普通的未受权权限
List<String> normalDeniedPermissions = new ArrayList<>();
List<String> grantedPermissions = new ArrayList<>();
for (int i = 0; i < grantResults.length; i++) {
int grantResult = grantResults[i];
String permission = permissions[i];
if (grantResult == PackageManager.PERMISSION_GRANTED) {
//受权经过
grantedPermissions.add(permission);
} else {
//受权拒绝
if (!ActivityCompat.shouldShowRequestPermissionRationale(mContext, permission)) {
//用户勾选了再也不提示
forceDeniedPermissions.add(permission);
} else {
//用户仅仅点击了拒绝 未勾选再也不提示
normalDeniedPermissions.add(permission);
}
}
}
if (forceDeniedPermissions.size() == 0 && normalDeniedPermissions.size() == 0) {
//所有受权经过
requestPermissionsSuccess();
} else {
//受权未经过
for (String permission : this.permissions) {
if (grantedPermissions.contains(permission)
|| normalDeniedPermissions.contains(permission)
|| forceDeniedPermissions.contains(permission)) {
} else {
//若是三者都不包含他 包名这个权限不是隐私权限 直接给就完事了 因此要放到已受权的权限列表里面去
grantedPermissions.add(permission);
}
}
requestPermissionsFail(grantedPermissions.toArray(new String[grantedPermissions.size()]),
normalDeniedPermissions.toArray(new String[normalDeniedPermissions.size()]),
forceDeniedPermissions.toArray(new String[forceDeniedPermissions.size()]));
}
}
}
//受权所有经过
private void requestPermissionsSuccess() {
if (permissionCheckListener != null) {
permissionCheckListener.permissionRequestSuccess();
}
mContext.getFragmentManager().beginTransaction().remove(this).commit();
}
//受权未经过
private void requestPermissionsFail(String[] grantedPermissions, String[] deniedPermissions, String[] forceDeniedPermissions) {
if (permissionCheckListener != null) {
permissionCheckListener.permissionRequestFail(grantedPermissions, deniedPermissions, forceDeniedPermissions);
}
mContext.getFragmentManager().beginTransaction().remove(this).commit();
}
复制代码
此外还有一种需求,当用户不受权,就不给用。这种状况下咱们须要在申请权限的时候,当用户点击拒绝的时候,一直无限申请权限,直到用户点击容许为止,对,就是这么流氓!
细心的同窗在上面的代码中已经看到了一些逻辑,对应的就是另一种一种状况,用户在你连续弹出几回申请权限的弹窗以后,会选择勾选再也不提醒,这个时候你再去申请权限的时候,系统会直接回调给你拒绝,而且不会弹出受权弹窗,这个时候咱们须要调用ActivityCompat.shouldShowRequestPermissionRationale(mContext, permission)方法来判断下用户是否勾选了再也不提醒的按钮,若是用户勾选了,那么咱们就须要指引用户去设置页面手动受权,由于咱们在app内部已经无能为力了。
if (normalDeniedPermissions.size() != 0) {
//还有普通拒绝的权限能够弹窗
requestPermission();
} else {
//全部没有经过的权限都是用户点击了再也不提示的 我擦 这里原本是想把未受权的全部权限的名称列出来展现的 后来想一想以为配置有点麻烦
new AlertDialog.Builder(mContext)
.setTitle(mContext.getString(R.string.permissions_check_warn))
.setMessage(checkConfig == null ? forceDeniedPermissionTips : checkConfig.getForceDeniedPermissionTips())
.setCancelable(false)
.setPositiveButton(mContext.getString(R.string.permissions_check_ok), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
openSettingPage();
}
}).show();
}
复制代码
以前不是说一行代码实现吗?下面就是整个申请权限的功能,的确只有一个结尾的分号,我说一行不算吹牛吧!~~~
FanPermissionUtils.with(MainActivity.this)
//添加全部你须要申请的权限
.addPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.addPermissions(Manifest.permission.ACCESS_FINE_LOCATION)
.addPermissions(Manifest.permission.CALL_PHONE)
.addPermissions(Manifest.permission.ACCESS_WIFI_STATE)
.addPermissions(Manifest.permission.CAMERA)
//添加权限申请回调监听 若是会返回已申请成功的权限列表,用户拒绝的权限列表和用户点击了再也不提醒的永久列表
.setPermissionsCheckListener(new FanPermissionListener() {
@Override
public void permissionRequestSuccess() {
//全部权限受权成功才会回调这里
((TextVfindViewById(R.id.tv_result)).setText("受权结果\n\n全部权限都受权成;
Toast.makeText(MainActivity.this, "全部权限都受权Toast.LENGTH_SHORT).show();
@Override
public void permissionRequestFail(String[] grantedPermissions, StrideniedPermissions, String[] forceDeniedPermissions) {
//当有权限没有被受权就会回调这里
StringBuilder result = new StringBuilder("受权结果\n\n受权失败\n\n");
result.append("受权经过的权限:\n");
for (String grantedPermission : grantedPermissions) {
result.append(grantedPermission + "\n");
}
result.append("\n临时拒绝的权限:\n");
for (String deniedPermission : deniedPermissions) {
result.append(deniedPermission + "\n");
}
result.append("\n永久拒绝的权限:\n");
for (String forceDeniedPermission : forceDeniedPermissions) {
result.append(forceDeniedPermission + "\n");
}
((TextView) findViewById(R.id.tv_result)).setText(result);
Toast.makeText(MainActivity.this, "受权失败", Toast.LENGTH_SHORT).show();
}
})
//开始受权
.startCheckPermission();
复制代码
简书首页,连接是 www.jianshu.com/u/123f97613…
掘金首页,连接是 juejin.im/user/5838d5…
Github首页,连接是 github.com/MZCretin
CSDN首页,连接是 blog.csdn.net/u010998327
我是Cretin,一个可爱的小男孩。