Android6.0之后,谷歌对Android权限策略做出了重大调整,将权限分为了普通权限和危险权限,对于危险权限不仅需要在 AndroidManifest.xml
中注册,还需要动态申请。这一改变,极大地保护了用户的隐私与安全,但是却给开发者增加了一些麻烦。对于Android6.0权限策略的具体变化,这里不做过多描述,直接上代码体验下对我们平时开发的影响,下面以拨打电话为例
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_request); findViewById(R.id.callBtn).setOnClickListener(v -> { call(); }); } private void call() { Intent intent = new Intent(); intent.setAction(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:1008611")); startActivity(intent); } 复制代码
可以看出Android6.0之前的操作完全符合我们的逻辑,点击按钮,直接调用方法就可以拨打电话了
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_request); findViewById(R.id.callBtn).setOnClickListener(v -> { //检测是否具有权限 if (ActivityCompat.checkSelfPermission(OriginalMethodActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { //没有就去申请 ActivityCompat.requestPermissions(OriginalMethodActivity.this, new String[]{Manifest.permission.CALL_PHONE}, 1000); } else { call(); } }); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == 1000) { //检测用户授权结果 if(grantResults[0]==PackageManager.PERMISSION_GRANTED){ call(); }else { Toast.makeText(this,"权限被拒绝",Toast.LENGTH_SHORT).show(); } } } 复制代码
同样的功能,Android6.0之后需要的代码量比之前多的不是一星半点,我们需要用 ActivityCompat.checkSelfPermission()
检查是否具有权限,如果没有还需要调用 ActivityCompat.requestPermissions()
进行权限申请,最后在 onRequestPermissionsResult()
回调中进行处理,虽然这些处理都是流程化的,没有任何技术含量,但是在实际应用中我们避无可避,于是我们实际项目中或许就对它有了一些封装。
基于BaseActivity封装运行时权限这里不做过多阐述,网上对于这方面封装的也比较多,不外乎是将上面流程化的步骤封装在基类,然后在 onRequestPermissionsResult()
中将回调分发到子类。个人不是很喜欢这种,基类封装的东西本来就多了,这些能剔除去就尽量剔除去,毕竟它还是个孩子,放过它吧。
RxPermissions 使用比较简单,大大优化了我们的代码
new RxPermissions(this).request(Manifest.permission.CALL_PHONE) .subscribe(aBoolean -> { if (aBoolean != null && aBoolean.booleanValue()) { call(); } }); 复制代码
短短的几行代码就完成了需求,仿佛又回到了10年前。到这里,关于Android6.0权限处理基本方式就介绍完了,接下来进入正题,我们自己来实现一个权限申请框架,为什么要自己重复造轮子呢,GitHub上面开源的那么多?对于这个问题,每个人都有自己的见解,在我看来,别人的轮子再圆也始终是别人的,不重复造轮子的前提是会造轮子。毕竟遨游在代码的海洋里,我们怎么能只甘心做一个“掉包侠”呢。
AspectJ
, AspectJ
是对 AOP
的实现,篇幅原因,关于它们的详细使用及原理这里不做讲解。 SmartPermission
,为啥叫Smart,可能是我不够Smart吧。建好之后回到我们的目的————一行代码完成请求,先看看我们最初的写法 protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_request); findViewById(R.id.callBtn).setOnClickListener(v -> { call(); }); } private void call() { Intent intent = new Intent(); intent.setAction(Intent.ACTION_CALL); intent.setData(Uri.parse("tel:1008611")); startActivity(intent); } 复制代码
将这一行代码放在那里才能实现需求呢?还记得 butterknife
的用法(仅用法,非原理)吗,我们将 @OnClick(R.id.xxx)
放在某个方法上面便可实现id为xxx控件的点击,这里我们也模仿他的处理,在 call()
方法上面放一个注解,先新建一个注解 SmartPermission
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface SmartPermission { String[] value(); } 复制代码
由于需要同时申请多个权限,所以这里使用 String[]
。
2. 接下来就要使用到 AspectJ
了,对它不熟悉的可以先去了解下,由于使用 AspectJ
需要配置很多,这里使用沪江的开源库 gradle_plugin_android_aspectjx
,我们先按照他的引入说明进行相关配置在项目根目录的build.gradle里依赖AspectJX
dependencies { classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.0' } 复制代码
在app项目的build.gradle里应用插件
apply plugin: 'com.hujiang.android-aspectjx' 复制代码
SmartPermissionAspect
@Aspect public class SmartPermissionAspect { @Pointcut("execution(@com.lantian.smartpermission.annotation.SmartPermission * *(..))") public void checkPermission() { } @Around("checkPermission()") public void check(ProceedingJoinPoint point) { } } 复制代码
接下来我们的工作就是补全 check()
方法,从参数point中我们可以获取 SmartPermission
注解修饰方法的详细信息,首先,我们要获取需要申请的权限列表
MethodSignature signature = (MethodSignature) point.getSignature(); SmartPermission annotation = signature.getMethod().getAnnotation(SmartPermission.class); String[] permissions = annotation.value(); 复制代码
有了权限列表后,我们需要先进行处理一下,如果权限列表为空,或者这些权限都是被允许通过的,直接执行原方法就行了
if (permissions == null || permissions.length == 0) { proceed(args, point, new String[]{}, new String[]{}, new String[]{}); return; } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { proceed(args, point, new String[]{}, new String[]{}, permissions); return; } //权限过滤,只申请被拒绝了的 final List<String> deniedPermissions = new ArrayList<>(); for (String permission : permissions) { if (ContextCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) { deniedPermissions.add(permission); } } if (deniedPermissions.size() == 0) { proceed(args, point, new String[]{}, new String[]{}, permissions); return; } //TODO 权限申请 复制代码
onRequestPermissionsResult
中将结果回调回去,先写一个接口,方便处理结果 public interface PermissionRequestCallback { /** * 所有申请的权限都被允许 */ void onGranted(); /** * 没有全部通过 (grantedPermissions+deniedPermissions+dontAskAgainPermissions)=一共申请的权限 * @param grantedPermissions 通过了的权限 * @param deniedPermissions 被拒绝的权限(不包括不再被询问的) * @param dontAskAgainPermissions 不再询问的权限 */ void onDenied(String[] grantedPermissions, String[] deniedPermissions, String[] dontAskAgainPermissions); } 复制代码
然后编写我们的核心Fragment,我们在权限处理完成后将这些结果再处理一下,分为通过的,拒绝了的和不再询问的3类
public class SmartSupportFragment extends Fragment { private PermissionRequestCallback callback; public void setPermissionRequestCallback(PermissionRequestCallback callback) { this.callback = callback; } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (permissions.length == 0 && grantResults.length == 0) { return; } List<String> grantedPermissions = new ArrayList<>(); List<String> deniedPermissions = new ArrayList<>(); List<String> dontAskAgainPermissions = new ArrayList<>(); for (int i = 0; i < permissions.length; i++) { String permission = permissions[i]; if (grantResults.length <= i || grantResults[i] != PackageManager.PERMISSION_GRANTED) { if (ActivityCompat.shouldShowRequestPermissionRationale(getActivity(), permission)) { //被拒绝的权限(仅仅是被拒绝,可以再次申请) deniedPermissions.add(permission); } else { //Don’t ask again,即使再次申请,也不会弹出提示,需要进入设置页面,手动开启权限 dontAskAgainPermissions.add(permission); } } else { grantedPermissions.add(permission); } } if (callback != null) { //没有权限被拒绝 if (permissions.length == grantedPermissions.size()) { callback.onGranted(); } else { callback.onDenied(grantedPermissions.toArray(new String[grantedPermissions.size()]), deniedPermissions.toArray(new String[deniedPermissions.size()]), dontAskAgainPermissions.toArray(new String[dontAskAgainPermissions.size()])); } } } } 复制代码
SmartPermissionAspect
类中,在第3步的后面开始申请权限 private void supportPermissions(final Activity activity, final Object[] args, final ProceedingJoinPoint point, List<String> deniedPermissions, final String[] allPermissions) { FragmentManager fm = ((FragmentActivity) activity).getSupportFragmentManager(); SmartSupportFragment smartSupportFragment = new SmartSupportFragment(); smartSupportFragment.setPermissionRequestCallback(new PermissionRequestCallback() { @Override public void onGranted() { proceed(args, point, new String[]{}, new String[]{}, allPermissions); } public void onDenied(String[] grantedPermissions, String[] deniedPermissions, String[] dontAskAgainPermissions) { String[] gs = getGrantedPermissions(deniedPermissions, dontAskAgainPermissions, allPermissions); proceed(args, point, deniedPermissions, dontAskAgainPermissions, gs); } }); fm.beginTransaction().add(smartSupportFragment, TAG_FRAGMENT_SUPPORT).commitAllowingStateLoss(); fm.executePendingTransactions(); smartSupportFragment.requestPermissions(deniedPermissions.toArray(new String[deniedPermissions.size()]), 65535); } 复制代码
这里获取FragmentManager需要用到activity,那么这个activity如何获取呢,我们可以获取当前最顶部正在显示的Activity,所以需要先编写一个工具类,记录打开的Activity
public class SmartPermissionUtil { private static SmartPermissionUtil instance; private List<Activity> activities = new ArrayList<>(); public static SmartPermissionUtil getInstance() { if (instance == null) { synchronized (SmartPermissionUtil.class) { if (instance == null) { instance = new SmartPermissionUtil(); } } } return instance; } public Activity getTopActivity() { if (activities == null) { throw new RuntimeException("Activity是空的"); } else { for (int i = activities.size() - 1; i >= 0; i++) { Activity activity = activities.get(i); if (!activity.isDestroyed()) { return activity; } } throw new RuntimeException("Activity是空的"); } } public void init(Application context) { context.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { public void onActivityCreated(Activity activity, Bundle savedInstanceState) { if (!activities.contains(activity)) { activities.add(activity); } } public void onActivityDestroyed(Activity activity) { activities.remove(activity); } //省略其他方法 } } 复制代码
这里调用 registerActivityLifecycleCallbacks()
方法需要context,这个context我们可以让开发者在 Application#onCreate()
中进行初始化,但是这样无疑又多了一行代码,这里我们可以使用ContentProvider来进行初始化
public class SmartProvider extends ContentProvider { public boolean onCreate() { SmartPermissionUtil.getInstance().init((Application) getContext()); return false; } //省略其他方法 } <provider android:authorities="com.lantian.smartpermission.provider" android:multiprocess="true" android:exported="false" android:name=".provider.SmartProvider"/> 复制代码
public void proceed(Object[] args, ProceedingJoinPoint point, String[] deniedPermissions, String[] dontAskAgainPermissions, String[] grantedPermissions) { boolean allGranted = deniedPermissions.length == 0 && dontAskAgainPermissions.length == 0;//是否全部允许 if (allGranted) { point.proceed(args); } else { //TODO } } 复制代码
public class SmartPermissionResult { private boolean allGranted; //被拒绝的权限(不包括不再被询问的) private String[] deniedPermissions; //不再询问的权限 private String[] dontAskAgainPermissions; //通过了的权限 private String[] grantedPermissions; } 复制代码
然后在第6步方法中将权限申请结果塞到 SmartPermissionResult
中去
public void proceed(Object[] args, ProceedingJoinPoint point,String[] deniedPermissions, String[] dontAskAgainPermissions, String[] grantedPermissions) { SmartPermissionResult smartPermissionResult = null; if (args != null && args.length != 0) { for (Object arg : args) { if (arg instanceof SmartPermissionResult) { smartPermissionResult = (SmartPermissionResult) arg; break; } } } boolean allGranted = deniedPermissions.length == 0 && dontAskAgainPermissions.length == 0;//是否全部允许 if (smartPermissionResult != null) { smartPermissionResult.setAllGranted(allGranted); smartPermissionResult.setGrantedPermissions(grantedPermissions); smartPermissionResult.setDeniedPermissions(deniedPermissions); smartPermissionResult.setDontAskAgainPermissions(dontAskAgainPermissions); point.proceed(args); } else { if (allGranted) { point.proceed(args); } else { } } } 复制代码
最后我们就可以像 View.getLocationInWindow(location)
一样,修改下我们的 call()方法
,新增一个 SmartPermissionResult
参数,用来接收权限申请的结果
@SmartPermission({Manifest.permission.CALL_PHONE,Manifest.permission.READ_EXTERNAL_STORAGE,Manifest.permission.CAMERA}) private void call(String arg1, SmartPermissionResult smartPermissionResult, String arg2) { if (smartPermissionResult.isAllGranted()) { Log.i(TAG, "参数=" + arg1 + "---" + arg2); } else { Log.i(TAG, "不再询问的权限" + Arrays.toString(smartPermissionResult.getDontAskAgainPermissions()) + ""); Log.i(TAG, "允许的权限" + Arrays.toString(smartPermissionResult.getGrantedPermissions()) + ""); Log.i(TAG, "被拒绝权限" + Arrays.toString(smartPermissionResult.getDeniedPermissions()) + ""); } } 复制代码
build.gradle
配置 classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
smartpermission
的 build.gradle
配置 apply plugin: 'com.github.dcendents.android-maven'
和 group='com.github.username'
smartpermission
了 //Activity#requestPermissions()方法中mHasCurrentPermissionsRequest为true时直接返回,此时permissions大小为0 if (mHasCurrentPermissionsRequest) { Log.w(TAG, "Can request only one set of permissions at a time"); // Dispatch the callback with empty arrays which means a cancellation. onRequestPermissionsResult(requestCode, new String[0], new int[0]); return; } 复制代码