# snails-permission **Repository Path**: ak-star/snails-permission ## Basic Information - **Project Name**: snails-permission - **Description**: android6.0动态权限申请 - **Primary Language**: Android - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 17 - **Forks**: 6 - **Created**: 2019-05-05 - **Last Updated**: 2024-09-16 ## Categories & Tags **Categories**: android-modules **Tags**: None ## README # snail-permission #### 介绍 android6.0动态权限申请 > 支持在程序任何地方中进行权限申请,例如:activity,fragment,view,service等。 * **注意:** 本库,暂时不支持在非UI线程申请权限 #### 软件架构 * 设计模式:AOP面向切面设计 * 去context,采用ContentProvider实现。 * 依赖三方AOP框架:https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx #### UML时序图 ![输入图片说明](https://gitee.com/uploads/images/2019/0506/103220_76180daf_2666596.png "uml11111.png") #### 安装教程 ###### 1、权限库引入方式,在app模块的build.gradle中引入如下: ``` apply plugin: 'android-aspectjx' dependencies { implementation project(path: ':snails-permission') } //可选配置:include和exclude的规则是去匹配包名,如果找到匹配的包名,则整个jar(即整个库)生效, //这样做主要是考虑到性能的问题。 aspectjx { include 'com.snails.permission', '使用注解所在的包名' } ``` ###### 2、在根目录的build.gradle里面配置如下: ``` dependencies { classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.4' } ``` ###### 3、本项目中在AOP类中用到了反射,如果你的项目中在混淆后导致权限申请失败,将下面的配置加到你的混淆配置中: ``` -keepclasseswithmembers class * { @com.snails.permission.annotation.NeedPermission ; } -keepclasseswithmembers class * { @com.snails.permission.annotation.PermissionCanceled ; } -keepclasseswithmembers class * { @com.snails.permission.annotation.PermissionDenied ; } -keep public class com.snails.permission.InitProvider { *; } ``` #### 使用说明 * 本库,暂时不支持在非UI线程申请权限 ###### 1、权限申请 ``` @NeedPermission(value = { Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, requestCode = 200) private void onLoadAdvertisement() { this.mPresenter.onLoadAdvertisement(); } ``` > @NeedPermission 名称|类型|描述 ---|:--|:--- value|String[]|申请的权限数组 requestCode|int|请求码,是为了区别开同一个Activity中有多个不同的权限请求,默认是0,如果同一个Activity中只有一个权限申请,requestCode可以忽略不写。 ###### 2、权限被取消 * 权限被取消定义:如果用户没有给权限,但也没有选中不再提示,这种情况称为权限被取消 * 注意:声明方法有且只有一个参数,并且类型为```CancelBean```;CancelBean中有requestCode变量,即是我们请求权限时的请求码。 ``` @PermissionCanceled private void callPermissionCanceled(CancelBean model) { Logger.d("callPermissionCanceled 权限被取消"); } ``` ###### 3、权限被拒绝 * 权限被拒绝定义:弹出系统权限弹窗,用户没有给权限,并且选中不再提示,这种情况称为权限被拒绝 * 注意:声明方法有且只有一个参数,并且类型为```DenyBean```;DenyBean中有requestCode变量,即是我们请求权限时的请求码。另外还可以通过denyBean.getDenyList()来拿到被权限被拒绝的List; ``` @PermissionDenied private void callPermissionDenied(DenyBean model) { Logger.d("callPermissionDenied 权限被拒绝"); } ``` ------------- > 案例1 ``` package com.ak.aigo.ui.splash; public class SplashActivity extends BaseMvpActivity implements SplashContract.View { private final int PERMISSION_CODE = 200; @NeedPermission(value = { Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, requestCode = PERMISSION_CODE) private void onLoadAdvertisement() { this.mPresenter.onLoadAdvertisement(); } @PermissionDenied private void callPermissionDenied(DenyBean model) { if (model != null && model.getDenyList() != null) { switch (model.getRequestCode()) { case PERMISSION_CODE: String sPermission = ""; for (int i = 0; i < model.getDenyList().size(); i++) { switch (model.getDenyList().get(i)) { case Manifest.permission.READ_PHONE_STATE: sPermission += "设备信息、"; break; case Manifest.permission.READ_EXTERNAL_STORAGE: if (!sPermission.contains("存储空间")) sPermission += "存储空间、"; break; case Manifest.permission.WRITE_EXTERNAL_STORAGE: if (!sPermission.contains("存储空间")) sPermission += "存储空间、"; break; default: break; } } if (sPermission.endsWith("、")) sPermission = sPermission.substring(0, sPermission.length() - 2); final StringBuilder sBuilder = new StringBuilder("由于无法获取您的"); sBuilder.append(sPermission) .append("权限,玩Android无法正常运行,请开启权限:设置-应用-玩Android-权限") .append(sPermission); showPermissionAlert("玩Android无法正常运行", sBuilder.toString(), "退出", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dialog != null) dialog.dismiss(); ActivityStackManager.instance().exitApp(); } }, "去设置", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { if (dialog != null) dialog.dismiss(); SettingUtil.go2Setting(mCtx); new Handler().postDelayed(()->{ ActivityStackManager.instance().exitApp(); }, 2000); } }); break; default: break; } } } @PermissionCanceled private void callPermissionCanceled(CancelBean model) { if (model != null) { switch (model.getRequestCode()) { case PERMISSION_CODE: showLongToast("由于无法获取您的权限,玩Android无法正常运行,应用将在3s后自动退出"); new Handler().postDelayed(() -> { ActivityStackManager.instance().exitApp(); }, 3000); break; default: break; } } } private void showPermissionAlert(String title, String message, String cancel, DialogInterface.OnClickListener cancelListener, String ok, DialogInterface.OnClickListener okListener) { new AlertDialog.Builder(mCtx).setTitle(title).setMessage(message) .setNegativeButton(cancel, cancelListener) .setPositiveButton(ok, okListener) .setCancelable(false).create().show(); } } ``` ------------- > 案例2 ``` public class PermissionDelegate { public interface IokDelegate { void onNext(int tagCode); } private final int PERMISSION_CODE = 200; private final Context mCtx; private int mTagCode = -1; private IokDelegate iOkDelegate = null; private AlertDialog mAlertDialog = null; public PermissionDelegate(Context ctx) { this.mCtx = ctx; } public PermissionDelegate code(int tagCode) { this.mTagCode = tagCode; return this; } public PermissionDelegate setIokDelegate(IokDelegate delegate) { this.iOkDelegate = delegate; return this; } @NeedPermission(value = { Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, requestCode = PERMISSION_CODE) public void checkPermission() { if (this.iOkDelegate != null) this.iOkDelegate.onNext(this.mTagCode); } public void onDestroy() { dismissAlertDialog(); iOkDelegate = null; } /** * 构建对话框 */ private AlertDialog alertDialog(String title, String message, String cancel, DialogInterface.OnClickListener cancelListener, String ok, DialogInterface.OnClickListener okListener) { final AlertDialog.Builder builder = new AlertDialog.Builder(mCtx) .setMessage(message).setCancelable(false); if (!TextUtils.isEmpty(title)) builder.setTitle(title); if (!TextUtils.isEmpty(ok)) builder.setPositiveButton(ok, okListener); if (!TextUtils.isEmpty(cancel)) builder.setNegativeButton(cancel, cancelListener); return builder.create(); } /** * 显示对话框 */ private void showAlertDialog(String title, String message, String cancel, DialogInterface.OnClickListener cancelListener, String ok, DialogInterface.OnClickListener okListener) { dismissAlertDialog(); mAlertDialog = alertDialog(title, message, cancel, cancelListener, ok, okListener); mAlertDialog.show(); } /** * 关闭对话框 */ private void dismissAlertDialog() { if (mAlertDialog != null) { if (mAlertDialog.isShowing()) mAlertDialog.dismiss(); mAlertDialog = null; } } @PermissionDenied private void callPermissionDenied(DenyBean model) { if (model != null && model.getDenyList() != null) { switch (model.getRequestCode()) { case PERMISSION_CODE: final String sPermission = deniedString(model); final StringBuilder sBuilder = new StringBuilder("由于无法获取您的"); sBuilder.append(sPermission) .append("权限,玩Android无法正常运行,请开启权限:设置-应用-玩Android-权限") .append(sPermission); final String title = "玩Android无法正常运行"; final String message = sBuilder.toString(); showAlertDialog(title, message, "退出", (dialog, which) -> { if (dialog != null) dialog.dismiss(); dismissAlertDialog(); ActivityStackManager.instance().exitApp(); }, "去设置", (dialog, which) -> { if (dialog != null) dialog.dismiss(); dismissAlertDialog(); SettingUtil.go2Setting(mCtx); new Handler().postDelayed(() -> ActivityStackManager.instance().exitApp(), 2000); }); break; default: break; } } } // 权限被拒绝,提示message private String deniedString(DenyBean model) { String sPermission = ""; if (model != null && model.getDenyList() != null) { for (int i = 0; i < model.getDenyList().size(); i++) { switch (model.getDenyList().get(i)) { case Manifest.permission.READ_PHONE_STATE: sPermission += "设备信息、"; break; case Manifest.permission.READ_EXTERNAL_STORAGE: if (!sPermission.contains("存储空间")) sPermission += "存储空间、"; break; case Manifest.permission.WRITE_EXTERNAL_STORAGE: if (!sPermission.contains("存储空间")) sPermission += "存储空间、"; break; default: break; } } if (sPermission.endsWith("、")) sPermission = sPermission.substring(0, sPermission.length() - 2); } return sPermission; } @PermissionCanceled private void callPermissionCanceled(CancelBean model) { if (model != null) { switch (model.getRequestCode()) { case PERMISSION_CODE: new Handler().postDelayed(() -> ActivityStackManager.instance().exitApp(), 3000); final String message = "由于无法获取您的权限,玩Android无法正常运行,应用将在3s后自动退出"; showAlertDialog("", message, "", null, "", null); break; default: break; } } } } ``` ------------- #### 其他配置支持 ###### 1、日志设置 * 设置日志输出状态及获取 ``` SnailsPermission.setDebug(boolean debug): void SnailsPermission.isDebug(): boolean ``` * 设置日志适配器,通过此设置,可以统一app中的日志输出样式,及输出状态。 ``` SnailsPermission.setDebugAdapter(new IDebugAdapter() { @Override public boolean isDebug() { return Logger.debug(); } @Override public void d(String tag, String message) { Logger.d(tag + ": " + message); } @Override public void e(String tag, String message) { Logger.e(tag + ": " + message); } }); ``` ------------- #### 未来计划 采用fragment替代activity实现权限申请依托。 #### 参与贡献 1. Fork 本仓库 2. 新建 Feat_xxx 分支 3. 提交代码 4. 新建 Pull Request #### 码云特技 1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md 2. 码云官方博客 [blog.gitee.com](https://blog.gitee.com) 3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解码云上的优秀开源项目 4. [GVP](https://gitee.com/gvp) 全称是码云最有价值开源项目,是码云综合评定出的优秀开源项目 5. 码云官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help) 6. 码云封面人物是一档用来展示码云会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)