SoulPermission-Android一句话权限适配的更优解决方案,给后辈的一点建议

}

String permissionDesc = permission.getPermissionNameDesc();

new AlertDialog.Builder(activity)

.setTitle(“提示”)

.setMessage(permissionDesc + “异常,请前往设置->权限管理,打开” + permissionDesc + “。”)

.setPositiveButton(“去设置”, new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialogInterface, int i) {

//去设置页

SoulPermission.getInstance().goPermissionSettings();

}

}).create().show();

}

}

复制代码

我们再写一个选择联系人的方法:

/**

  • 选择联系人

*/

public static void chooseContact(final Activity activity, final int requestCode) {

SoulPermission.getInstance().checkAndRequestPermission(Manifest.permission.READ_CONTACTS,

new CheckPermissionAdapter() {

@Override

public void onPermissionOk(Permission permission) {

activity.startActivityForResult(new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI), requestCode);

}

});

}

复制代码

在Activity中也是一行解决问题:

findViewById(R.id.bt_choose_contact).setOnClickListener(new View.OnClickListener() {

@Override

public void onClick(View view) {

UtilsWithPermission.chooseContact(AfterActivity.this, REQUEST_CODE_CONTACT);

}

});

复制代码

代码细节请参考demo,我们再来看看效果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

主要功能的源码分析:

优雅的避掉onPermissionResult:

适配权限最大的痛点在于:项目业务页面繁多,如果你想实现“真运行时权限”的话就需要在业务的Activity或者Fragment中去重写权限请求回调方法,斟酌一番并且在参考了下RxPermission中对权限请求的处理,我决定用同样的方式—用一个没有界面的Fragment去完成我们权限请求的操作,下面贴上部分代码:

首先定义一个接口,用于封装权限请求的结果

public interface RequestPermissionListener {

/**

  • 得到权限检查结果

  • @param permissions 封装权限的数组

*/

void onPermissionResult(Permission[] permissions);

}

复制代码

然后是我们的Fragment:

public class PermissionSupportFragment extends Fragment implements IPermissionActions {

/**

  • 内部维护requestCode

*/

private static final int REQUEST_CODE = 11;

/**

  • 传入的回调

*/

private RequestPermissionListener listener;

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

//当状态发生改变,比如设备旋转时候,Fragment不会被销毁

setRetainInstance(true);

}

/**

  • 外部请求的最终调用方法

  • @param permissions 权限

  • @param listener 回调

*/

@TargetApi(M)

@Override

public void requestPermissions(String[] permissions, RequestPermissionListener listener) {

requestPermissions(permissions, REQUEST_CODE);

this.listener = listener;

}

@TargetApi(M)

@Override

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

Permission[] permissionResults = new Permission[permissions.length];

//拿到授权结果以后对结果做一些封装

if (requestCode == REQUEST_CODE) {

for (int i = 0; i < permissions.length; ++i) {

Permission permission = new Permission(permissions[i], grantResults[i], this.shouldShowRequestPermissionRationale(permissions[i]));

permissionResults[i] = permission;

}

}

if (listener != null && getActivity() != null && !getActivity().isDestroyed()) {

listener.onPermissionResult(permissionResults);

}

}

}

复制代码

其中Permission是我们的权限名称、授予结果、是否需要给用于一个解释的包装类:

public class Permission {

private static final String TAG = Permission.class.getSimpleName();

/**

  • 权限名称

*/

public String permissionName;

/**

  • 授予结果

*/

public int grantResult;

/**

  • 是否需要给用户一个解释

*/

public boolean shouldRationale;

/**

  • 权限是否已经被授予

*/

public boolean isGranted() {

return grantResult == PackageManager.PERMISSION_GRANTED;

}

//。。。

}

复制代码

至此,我们已经利用自己实现的一个没有界面的Fragment封装了运行时权限相关的请求、RequestCode的维护、以及onPermissionResult的回调、在我们真正调用的时候代码是这样的:

/**

  • @param activity 栈顶 Activity

  • @param permissionsToRequest 待请求的权限

  • @param listener 回调

*/

private void requestRuntimePermission(final Activity activity, final Permission[] permissionsToRequest, final CheckRequestPermissionsListener listener) {

new PermissionRequester(activity)

.withPermission(permissionsToRequest)

.request(new RequestPermissionListener() {

@Override

public void onPermissionResult(Permission[] permissions) {

List refusedListAfterRequest = new LinkedList<>();

for (Permission requestResult : permissions) {

if (!requestResult.isGranted()) {

refusedListAfterRequest.add(requestResult);

}

}

if (refusedListAfterRequest.size() == 0) {

listener.onAllPermissionOk(permissionsToRequest);

} else {

listener.onPermissionDenied(PermissionTools.convert(refusedListAfterRequest));

}

}

});

}

复制代码

其中PermissionRequester也就是一个简单的构建者模式,其中包含了对Activity的类型判断,根据Activity类型去确定Fragment的实现:如果是FragmentActivity的实例,则使用Support包中的Fragment,否则用默认的Fragment,这样就兼容了有些应用的项目的基类不是AppComponentActivity(FragmentActivity)的情形,当然,原则上最低支持4.0,即默认Fragment的支持版本。

class PermissionFragmentFactory {

private static final String FRAGMENT_TAG = “permission_fragment_tag”;

static IPermissionActions create(Activity activity) {

IPermissionActions action;

if (activity instanceof FragmentActivity) {

FragmentManager supportFragmentManager = ((FragmentActivity) activity).getSupportFragmentManager();

PermissionSupportFragment permissionSupportFragment = (PermissionSupportFragment) supportFragmentManager.findFragmentByTag(FRAGMENT_TAG);

if (null == permissionSupportFragment) {

permissionSupportFragment = new PermissionSupportFragment();

supportFragmentManager.beginTransaction()

.add(permissionSupportFragment, FRAGMENT_TAG)

.commitNowAllowingStateLoss();

}

action = permissionSupportFragment;

} else {

android.app.FragmentManager fragmentManager = activity.getFragmentManager();

PermissionFragment permissionFragment = (PermissionFragment) fragmentManager.findFragmentByTag(FRAGMENT_TAG);

if (null == permissionFragment) {

permissionFragment = new PermissionFragment();

activity.getFragmentManager().beginTransaction()

.add(permissionFragment, FRAGMENT_TAG)

.commitAllowingStateLoss();

}

action = permissionFragment;

}

return action;

}

}

复制代码

至此,整个请求链已经很像最外层暴露的CheckAndRequestPermission方法了,就差一个Activity了,那么参数Activity怎么来呢?我们继续想办法。

再舍去Activity:

当然是使用Application中的ActivityLifecycleCallbacks,使用它的 registerActivityLifecycleCallbacks,感知Activity声明周期变化,获取到当前应用栈顶的Activity,这样我们就不需要自己手动传入了。

public class PermissionActivityLifecycle implements Application.ActivityLifecycleCallbacks {

WeakReference topActWeakReference;

@Override

public void onActivityCreated(Activity activity, Bundle savedInstanceState) {

//原则上只需要onResume,兼容如果在onCreate的时候做权限申请保证此时有Activity对象

topActWeakReference = new WeakReference<>(activity);

}

//…

@Override

public void onActivityResumed(Activity activity) {

topActWeakReference = new WeakReference<>(activity);

}

//…

}

复制代码

注册它仅仅需要一个Application:

/**

  • @param context Application

*/

private void registerLifecycle(Application context) {

if (null != lifecycle) {

context.unregisterActivityLifecycleCallbacks(lifecycle);

}

lifecycle = new PermissionActivityLifecycle();

context.registerActivityLifecycleCallbacks(lifecycle);

}

复制代码

这样一来,只要调用了初始化方法registerLifecycle,我们就能提供提供栈顶Activity了

/**

  • 获取栈顶Activity

  • @return 当前应用栈顶Activity

  • @throws InitException 初始化失败

  • @throws ContainerStatusException Activity状态异常

*/

private Activity getContainer() {

// may auto init failed

if (null == lifecycle || null == lifecycle.topActWeakReference) {

throw new InitException();

}

// activity status error

if (null == lifecycle.topActWeakReference.get() || lifecycle.topActWeakReference.get().isFinishing()) {

throw new ContainerStatusException();

}

return lifecycle.topActWeakReference.get();

}

复制代码

结合起来回到我们之前申请权限的方法(省略了日志打印和线程的判断,如果需要再细看源码):

private void requestPermissions(final Permissions permissions, final CheckRequestPermissionsListener listener) {

//check container status

final Activity activity;

try {

activity = getContainer();

} catch (Exception e) {

//activity status error do not request

return;

}

//…

//finally request

requestRuntimePermission(activity, permissions.getPermissions(), listener);

}

复制代码

至此,我们已经能脱离Activity和Fragment,也无需重写onPermissionResult了,只需要一个ApplicationContext初始化即可。

能否更简便一点?

最后避掉Application(免初始化):

我们可以自定义ContentProvider来完成库的初始化,我们可以参考Lifecycle组件的初始化:

//lifeCycle定义的初始化Provider

public class LifecycleRuntimeTrojanProvider extends ContentProvider {

@Override

public boolean onCreate() {

LifecycleDispatcher.init(getContext());

ProcessLifecycleOwner.init(getContext());

return true;

}

}

复制代码

和它的Manifest文件:

<provider

android:name=“android.arch.lifecycle.LifecycleRuntimeTrojanProvider”

android:authorities=“${applicationId}.lifecycle-trojan”

android:exported=“false”

android:multiprocess=“true” />

复制代码

参照它的实现给我们提供了一个很好的思路,我们可以自定义Provider去初始化一些库或者其他的内容,现在我们写一个自己的initContentProvider:

public class InitProvider extends ContentProvider {

@Override

public boolean onCreate() {

//初始化我们的库

SoulPermission.getInstance().autoInit((Application) getContext());

return true;

}

//…

}

复制代码

在库的AndroidManifest文件中声明:

<provider android:authorities=“${applicationId}.permission.provider”

android:name=“.permission.InitProvider”

android:multiprocess=“true”

android:exported=“false”/>

复制代码

至于为什么这个Context就是Application,我们可以参考ActivityThread中的对ContentProvider的初始化:

public void handleInstallProvider(ProviderInfo info) {

//即我们的应用的Application

installContentProviders(mInitialApplication, Arrays.asList(info));

}

复制代码

至此,我们权限申请流程就跟Activity、Fragment、乃至Context都没有关系了。

去除if&else、涵盖版本判断:

虽然我们完成了对运行时权限的申请流程,但是毕竟只针对6.0以上机型,如果上面流程还想一句话完成的话,那我们还得兼容老的机型,so,我们需要做在方法内做一个版本判断:

首先判断系统版本

public static boolean isOldPermissionSystem(Context context) {

int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;

return android.os.Build.VERSION.SDK_INT < M || targetSdkVersion < M;

}

复制代码

然后是检查权限:

6.0以上当然是走系统Api:

class RunTimePermissionChecker implements PermissionChecker {

private String permission;

private Context context;

RunTimePermissionChecker(Context context, String permission) {

this.permission = permission;

this.context = context;

}

@TargetApi(M)

@Override

public boolean check() {

int checkResult = ContextCompat.checkSelfPermission(context, permission);

return checkResult == PackageManager.PERMISSION_GRANTED;

}

}

复制代码

6.0以下、4.4以上通过AppOps反射获取(为了保证一致性,把权限名称参数在check方法中做了映射,把权限的String参数映射成checkOp的整形参数):

class AppOpsChecker implements PermissionChecker {

private Context context;

private String permission;

AppOpsChecker(Context context, String permission) {

this.context = context;

this.permission = permission;

}

/**

  • 老的通過反射方式檢查權限狀態

  • 结果可能不准确,如果返回false一定未授予

  • 按需在里面添加

  • 如果没匹配上或者异常都默认权限授予

  • @return 检查结果

*/

@Override

public boolean check() {

if (null == permission) {

return true;

}

switch (permission) {

case Manifest.permission.READ_CONTACTS:

return checkOp(4);

case Manifest.permission.WRITE_CONTACTS:

return checkOp(5);

case Manifest.permission.CALL_PHONE:

return checkOp(13);

case Manifest.permission.READ_PHONE_STATE:

return checkOp(51);

case Manifest.permission.CAMERA:

return checkOp(26);

case Manifest.permission.READ_EXTERNAL_STORAGE:

return checkOp(59);

case Manifest.permission.WRITE_EXTERNAL_STORAGE:

return checkOp(60);

case Manifest.permission.ACCESS_FINE_LOCATION:

case Manifest.permission.ACCESS_COARSE_LOCATION:

return checkOp(2);

default:

break;

}

return true;

}

boolean checkOp(int op) {

if (Build.VERSION.SDK_INT < KITKAT) {

return true;

}

try {

AppOpsManager manager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);

Method method = AppOpsManager.class.getDeclaredMethod(“checkOp”, int.class, int.class, String.class);

return 0 == (int) method.invoke(manager, op, Binder.getCallingUid(), context.getPackageName());

} catch (Exception e) {

e.printStackTrace();

}

return true;

}

}

复制代码

和版本判断起来就是这样:

public static PermissionChecker create(Context context, String permission) {

if (PermissionTools.isOldPermissionSystem(context)) {

return new AppOpsChecker(context, permission);

} else {

return new RunTimePermissionChecker(context, permission);

}

}

复制代码

再到我们最终调用的权限检测方法:

private boolean checkPermission(Context context, String permission) {

return CheckerFactory.create(context, permission).check();

}

复制代码

最终我们权限库一行代码从权限检测、权限请求联合起来的操作就是这样:

/**

  • 多个权限的检查与申请

  • 在敏感操作前,先检查权限和请求权限,当完成操作后可做后续的事情

  • @param permissions 多个权限的申请 Permissions.build(Manifest.permission.CALL_PHONE,Manifest.permission.CAMERA)

  • @param listener 请求之后的回调

*/

public void checkAndRequestPermissions(@NonNull Permissions permissions, @NonNull final CheckRequestPermissionsListener listener) {

//首先检查权限

Permission[] checkResult = checkPermissions(permissions.getPermissionsString());

//得到有多少权限被拒绝了

final Permission[] refusedPermissionList = filterRefusedPermissions(checkResult);

if (refusedPermissionList.length > 0) {

//是否可以请求运行时权限,即6.0以上

if (canRequestRunTimePermission()) {

//请求权限,并把listener传下去,也就是我们一开始看请求流程分析中的那个方法

requestPermissions(Permissions.build(refusedPermissionList), listener);

} else {

//无法请求权限,本次操作失败

listener.onPermissionDenied(refusedPermissionList);

}

} else {

//没有权限被拒绝,认为所有权限都ok,回调成功

listener.onAllPermissionOk(checkResult);

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结:

面试是一个不断学习、不断自我提升的过程,有机会还是出去面面,至少能想到查漏补缺效果,而且有些知识点,可能你自以为知道,但让你说,并不一定能说得很好。

有些东西有压力才有动力,而学到的知识点,都是钱(因为技术人员大部分情况是根据你的能力来定级、来发薪水的),技多不压身。

附上我的面试各大专题整理: 面试指南,满满的都是干货,希望对大家有帮助!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

listener.onAllPermissionOk(checkResult);

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-TELT5XPE-1712551615889)]

[外链图片转存中…(img-v7bHiHe8-1712551615889)]

[外链图片转存中…(img-uZAt3lE0-1712551615890)]

[外链图片转存中…(img-pfV9j752-1712551615890)]

[外链图片转存中…(img-lDAR2cB2-1712551615891)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

总结:

面试是一个不断学习、不断自我提升的过程,有机会还是出去面面,至少能想到查漏补缺效果,而且有些知识点,可能你自以为知道,但让你说,并不一定能说得很好。

有些东西有压力才有动力,而学到的知识点,都是钱(因为技术人员大部分情况是根据你的能力来定级、来发薪水的),技多不压身。

附上我的面试各大专题整理: 面试指南,满满的都是干货,希望对大家有帮助!
[外链图片转存中…(img-RmEhjq8E-1712551615891)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 19
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值