权限申请库:AndPermission(二)

QuibblerQuibbler 2022-07-07 约 19 分钟 1999 次阅读

权限申请库:AndPermission(二)

在权限申请库:AndPermission(一)中介绍了阿里严振杰所写的权限申请库:AndPermission,虽然很不靠谱被喷的不少,但仍推荐阅读此库的源码。它的源码比较容易阅读和理解,适合Android初学者学习,帮助理解Android的权限机制。

随着Google对Android的不断升级完善,越来越重视对用户隐私的保护,现在Android框架的权限机制已经固定且稳定可靠,用原生框架足够满足开发需求,无需再借助三方的开源库。

废话不多说,直接看源码。从with()入口方法开始,该方法有多个重载,可以接收Context、Fragment、Activity等多种类型作为参数,这样的设计借鉴了Glide。

public static Option with(Context context) {
        return new Boot(getContextSource(context));
    }
    
    public static Option with(Fragment fragment) {
        return new Boot(new XFragmentSource(fragment));
    }
    
    public static Option with(android.app.Fragment fragment) {
        return new Boot(new FragmentSource(fragment));
    }
    
    public static Option with(Activity activity) {
        return new Boot(new ActivitySource(activity));
    }

返回Option接口类型,该接口定义了若干方法,也是AndPermission库的几个核心功能。

public interface Option {
        /**
         * Handle runtime permissions.
         */
        RuntimeOption runtime();
        
        /**
         * Handle request package install permission.
         */
        InstallRequest install();
        
        /**
         * Handle overlay permission.
         */
        OverlayRequest overlay();
        
        /**
         * Handle notification permission.
         */
        NotifyOption notification();
        
        /**
         * Handle system setting.
         */
        Setting setting();
    }

Option接口唯一实现类是Boot,本篇主要阅读权限申请相关的代码,接着借助runtime()方法返回的Runtime实例进行权限申请操作:

AndPermission.with(this)
            .runtime()
            .permission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
            .onGranted(new Action<List<String>>() {
                @Override
                public void onAction(List<String> data) {
                    //权限允许
                }
            })
            .onDenied(new Action<List<String>>() {
                @Override
                public void onAction(List<String> data) {
                    //权限拒绝
                }
            })
            .rationale(new Rationale<List<String>>() {
                @Override
                public void showRationale(Context context, List<String> data, RequestExecutor executor) {
                    executor.execute();
                }
            })
            .start();

上面的主要流程如下图所示(本文流程图均用PlantUML绘制):

通过Runtime的permission(String... permissions)方法进行权限申请,传入需要申请的权限,方法接收不定长String参数,并且返回一个PermissionRequest对象:

@Override
    public PermissionRequest permission(@NonNull String... permissions) {
        checkPermissions(permissions);
        return FACTORY.create(mSource).permission(permissions);
    }

FACTORY是用于创建PermissionRequest的工厂,其基本类型是PermissionRequestFactory,工厂的两个实现类分别是:MRequestFactory和LRequestFactory。

public interface PermissionRequestFactory {
        /**
         * Create permission request.
         */
        PermissionRequest create(Source source);
    }

这里巧妙的利用了多态设计,在静态代码块中根据Android版本初始化:SDK大于等于23,也就是Android 6.0及以上,使用MRequestFactory;低于23即Android 6.0以下,使用LRequestFactory。这也是Android权限在各版本中最大的区别,详见另一篇《Android Permission 权限总结》。

private static final PermissionRequestFactory FACTORY;
    
    static {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            FACTORY = new MRequestFactory();
        } else {
            FACTORY = new LRequestFactory();
        }
    }

现在Android版本绝大多数以Android 6.0及以上为主,占比95.6%以上。本篇仍以Android 6.0以上的权限申请流程为主,Android 6.0以下的权限申请直接过。

这里通过MRequestFactory创建MRequest,最后调用start()方法开启权限请求,这部分流程如下:

接下来,由RequestExecutor执行权限申请任务:

@Override
    public void run() {
        Context context = mRequest.getSource().getContext();
        mMessenger = new Messenger(context, this);
        mMessenger.register(getName());
        Intent intent = new Intent();
        intent.setAction(AndPermission.bridgeAction(context, null));
        intent.setPackage(context.getPackageName());
        context.bindService(intent, mConnection, Service.BIND_AUTO_CREATE);
    }

这一步操作,目的是为了绑定BridgeService,并且构造action标识此次权限申请:{package}.andpermission.bridge。

private static final String ACTION_BRIDGE_SUFFIX = ".andpermission.bridge";
    
    public static String bridgeAction(Context context, String suffix) {
        return context.getPackageName() + ACTION_BRIDGE_SUFFIX + (TextUtils.isEmpty(suffix) ? "" : "." + suffix);
    }

当BridgeService绑定后,在onServiceConnected()回调中执行下面的流程,通过IBinder执行Service中的权限申请方法:

private void executeCurrent(IBridge iBridge) throws RemoteException {
        switch (mRequest.getType()) {
            ...
            case BridgeRequest.TYPE_PERMISSION: {
                iBridge.requestPermission(getName(), mRequest.getPermissions());
                break;
            }
            ...
        }
    }

在BridgeService中实现了IBridge接口中的全部方法:

private Stub mStub = new Stub() {
        private Source mSource = new ContextSource(BridgeService.this);
        public void requestAppDetails(String suffix) throws RemoteException {
            BridgeActivity.requestAppDetails(this.mSource, suffix);
        }
        public void requestPermission(String suffix, String[] permissions) throws RemoteException {
            BridgeActivity.requestPermission(this.mSource, suffix, permissions);
        }
        ...
    };

可以看到方法中全部都是调用BridgeActivity的静态方法,这些静态方法定义在BridgeActivity类中,申请权限会启动BridgeActivity,AndPermission正是借助BridgeActivity完成权限的申请(整个库是这样的设计思路)。当然这个Activity是不可见的,如同Glide通过给Fragment/Activity插入一个不可见的Fragment,通过监听该Fragment的生命周期来实现对应的请求管理。

/**
     * Request for permissions.
     */
    static void requestPermission(Source source, String suffix, String[] permissions) {
        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_PERMISSION);
        intent.putExtra(KEY_PERMISSIONS, permissions);
        intent.putExtra(KEY_ACTION_SUFFIX, suffix);
        source.startActivity(intent);
    }

在BridgeActivity的onCreate(Bundle savedInstanceState)方法,根据传入的参数判别执行哪一项操作。权限申请最终还是通过Activity的requestPermissions()方法完成。

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (savedInstanceState != null) return;
        Intent intent = getIntent();
        int operation = intent.getIntExtra(KEY_TYPE, -1);
        mActionSuffix = intent.getStringExtra(KEY_ACTION_SUFFIX);
        switch (operation) {
            case BridgeRequest.TYPE_APP_DETAILS: {
                Intent appDetailsIntent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                appDetailsIntent.setData(Uri.fromParts("package", getPackageName(), null));
                startActivityForResult(appDetailsIntent, BridgeRequest.TYPE_APP_DETAILS);
                break;
            }
            case BridgeRequest.TYPE_PERMISSION: {
                String[] permissions = intent.getStringArrayExtra(KEY_PERMISSIONS);
                requestPermissions(permissions, BridgeRequest.TYPE_PERMISSION);
                break;
            }
            ...
        }
    }

并且在onRequestPermissionsResult(...)回执权限申请结果,此部分流程图如下:

@Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Messenger.send(this, mActionSuffix);
        finish();
    }

最后通过Messenger(继承自BroadcastReceiver),将权限申请结果从BridgeActivity通过广播传递给权限申请者MRequest。这里的action和申请权限时传入的一样。

public static void send(Context context, String suffix) {
        Intent broadcast = new Intent(AndPermission.bridgeAction(context, suffix));
        context.sendBroadcast(broadcast);
    }

再经过层层回调到onAction(T data)将结果传递给开发者,后面这部分流程如下:


相关推荐

Airbnb开源项目
精选开源

Airbnb开源项目

Airbnb Engineering & Data ScienceAirflow Use Apache Airflow (incubating) to author workflows as directed acyclic graphs (DAGs) of tasks12,263Airpal Web UI for PrestoDB2,502Aerosolve A machine learning

2.3k
WilliamChart:轻量美观的 Android 图表库实战
精选开源

WilliamChart:轻量美观的 Android 图表库实战

WilliamChart:轻量美观的 Android 图表库实战数据可视化是运动、健康、金融、统计类 App 的刚需。MPAndroidChart 功能最全但偏重复杂,WilliamChart 走的则是"轻量 + 美观 + Kotlin 原生"路线——v3 用 Kotlin 完全重写,API 极简,一行 chart.animate(dataSet) 就能把数据画成带动画的折线/柱状/环形图,开箱即

175
sherpa解析:开源语音技术全家桶
精选开源

sherpa解析:开源语音技术全家桶

想在手机、树莓派甚至 RISC-V 开发板上离线跑语音识别,五年前还是个需要啃 Kaldi 源码的硬核课题。k2-fsa 组织的 sherpa 把这件事变成了"下载预训练模型、装个包、跑示例"的三步操作。这个以 Apache-2.0 协议开源的项目自我定位是"新一代 Kaldi 的语音转文本框架",它专注端到端模型的推理部署:transducer 与 CTC 两类架构,C++ 与 Python 双…

44