这也许是Android一句话权限适配的最优 解决方案(1)

https://github.com/soulqw/SoulPermission/

当使用了SoulPermission以后,最直观上看,我们上面的代码就变成了这样:

public void makeCall() {

SoulPermission.getInstance()

.checkAndRequestPermission(Manifest.permission.CALL_PHONE, new CheckRequestPermissionListener() {

@Override

public void onPermissionOk(Permission permission) {

Utils.makeCall(AfterActivity.this, “10086”);

}

@Override

public void onPermissionDenied(Permission permission) {

Toast.makeText(AfterActivity.this, “本次拨打电话授权失败,请手动去设置页打开权限,或者重试授权权限”, Toast.LENGTH_SHORT).show();

}

});

}

解决问题:

1、解耦Activity和Fragment、不再需要Context、不再需要onPermissionResult

2、内部涵盖版本判断,一行代码解决权限相关操作,无需在调用业务方写权限适配代码,继而实现真正调用时请求的“真运行时权限”

3、接入成本低,零入侵,仅需要在gradle配置一行代码

大致工作流程:

如果我以在Android手机上要做一件事(doSomeThing),那么我最终可以有两个结果:

A:可以做

B:不能做

基于上述流程,那么SoulPermission的大致工作流程如下:

从开始到结束展示了我们上述打电话的流程,A即直接拨打,B即toast提示用户,无法继续后续操作,绿色部分流程即可选部分,即对shouldShowRequestPermissionRationale的处理,那么完整权限流程下来,我们拨打电话的代码就是这么写:

public void makeCall() {

SoulPermission.getInstance()

.checkAndRequestPermission(Manifest.permission.CALL_PHONE, new CheckRequestPermissionListener() {

@Override

public void onPermissionOk(Permission permission) {

Utils.makeCall(AfterActivity.this, “10086”);

}

@Override

public void onPermissionDenied(Permission permission) {

//绿色框中的流程

//用户第一次拒绝了权限且没有勾选"不再提示"的情况下这个值为true,此时告诉用户为什么需要这个权限。

if (permission.shouldRationale) {

new AlertDialog.Builder(AfterActivity.this)

.setTitle(“提示”)

.setMessage(“如果你拒绝了权限,你将无法拨打电话,请点击授予权限”)

.setPositiveButton(“授予”, new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialogInterface, int i) {

//用户确定以后,重新执行请求原始流程

makeCall();

}

}).create().show();

} else {

Toast.makeText(AfterActivity.this, “本次拨打电话授权失败,请手动去设置页打开权限,或者重试授权权限”, Toast.LENGTH_SHORT).show();

}

}

});

}

上述便是其在满足运行时权限下的完整工作流程。

那么关于版本兼容呢?

针对部分手机6.0以下手机,SoulPermission也做了兼容,可以通过AppOpps 检查权限,内部将权限名称做了相应的映射,它的大体流程就是下图:

https://www.jianshu.com/p/a26f0dd024a6

(这个检查结果不一定准确,但是即使不准确,也默认成功(A),保证我们回调能往下走,不会阻塞流程,其他自己实现了权限系统的手机,如vivo,魅族等也是走此方法,最终走他们自己的权限申请流程)

基于对于新老手机版本做了控制,在权限拒绝里面很多处理也是又可以提取的部分,我们可以把回调再次封装一下,进一步减少重复代码:

public abstract class CheckPermissionWithRationaleAdapter implements CheckRequestPermissionListener {

private String rationaleMessage;

private Runnable retryRunnable;

/**

  • @param rationaleMessage 当用户首次拒绝弹框时候,根据权限不同给用户不同的文案解释

  • @param retryRunnable 用户点重新授权的runnable 即重新执行原方法

*/

public CheckPermissionWithRationaleAdapter(String rationaleMessage, Runnable retryRunnable) {

this.rationaleMessage = rationaleMessage;

this.retryRunnable = retryRunnable;

}

@Override

public void onPermissionDenied(Permission permission) {

Activity activity = SoulPermission.getInstance().getTopActivity();

if (null == activity) {

return;

}

//绿色框中的流程

//用户第一次拒绝了权限、并且没有勾选"不再提示"这个值为true,此时告诉用户为什么需要这个权限。

if (permission.shouldRationale) {

new AlertDialog.Builder(activity)

.setTitle(“提示”)

.setMessage(rationaleMessage)

.setPositiveButton(“授予”, new DialogInterface.OnClickListener() {

@Override

public void onClick(DialogInterface dialogInterface, int i) {

//用户确定以后,重新执行请求原始流程

retryRunnable.run();

}

}).create().show();

} else {

//此时请求权限会直接报未授予,需要用户手动去权限设置页,所以弹框引导用户跳转去设置页

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();

}

}

}

然后我们在App所有打电话的入口处做一次调用:

/**

  • 拨打指定电话

*/

public static void makeCall(final Context context, final String phoneNumber) {

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

new CheckPermissionWithRationaleAdapter(“如果你拒绝了权限,你将无法拨打电话,请点击授予权限”,

new Runnable() {

@Override

public void run() {

//retry

makeCall(context, phoneNumber);

}

}) {

@Override

public void onPermissionOk(Permission permission) {

Intent intent = new Intent(Intent.ACTION_CALL);

Uri data = Uri.parse(“tel:” + phoneNumber);

intent.setData(data);

if (!(context instanceof Activity)) {

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

}

context.startActivity(intent);

}

});

}

那么这样下来,在Activity和任何业务页面的调用就只有一行代码了:

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

@Override

public void onClick(View view) {

UtilsWithPermission.makeCall(getActivity(), “10086”);

}

});

其中完全拒绝以后,SoulPermission 提供了跳转到系统权限设置页的方法,我们再来看看效果:

很多时候,其实绿色部分(shouldShowRequestPermissionRationale)其实并不一定必要,反复的弹框用户可能会厌烦,大多数情况,我们这么封装就好:

public abstract class CheckPermissionAdapter implements CheckRequestPermissionListener {

@Override

public void onPermissionDenied(Permission permission) {

//SoulPermission提供栈顶Activity

Activity activity = SoulPermission.getInstance().getTopActivity();

if (null == activity) {

return;

}

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,我们再来看看效果:

主要源码分析
1、优雅的避掉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怎么来呢?

我们继续想办法。

2、再舍去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初始化即可。

3、能否更简便一点?

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

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

http://chaosleong.github.io/2017/05/27/How-Lifecycle-aware-Components-actually-works/

//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的初始化:

总结

现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。

我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。

Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。

如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。

(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
nitContentProvider:

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的初始化:

总结

现在新技术层出不穷,如果每次出新的技术,我们都深入的研究的话,很容易分散精力。新的技术可能很久之后我们才会在工作中用得上,当学的新技术无法学以致用,很容易被我们遗忘,到最后真的需要使用的时候,又要从头来过(虽然上手会更快)。

我觉得身为技术人,针对新技术应该是持拥抱态度的,入了这一行你就应该知道这是一个活到老学到老的行业,所以面对新技术,不要抵触,拥抱变化就好了。

Flutter 明显是一种全新的技术,而对于这个新技术在发布之初,花一个月的时间学习它,成本确实过高。但是周末花一天时间体验一下它的开发流程,了解一下它的优缺点、能干什么或者不能干什么。这个时间,并不是我们不能接受的。

如果有时间,其实通读一遍 Flutter 的文档,是最全面的一次对 Flutter 的了解过程。但是如果我们只有 8 小时的时间,我希望能关注一些最值得关注的点。

(跨平台开发(Flutter)、java基础与原理,自定义view、NDK、架构设计、性能优化、完整商业项目开发等)

[外链图片转存中…(img-6dIzqZcc-1715415699192)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值