权限申请库:AndPermission(二)

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

权限申请库: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

1.8k
OkHttp线程池和连接池
开源

OkHttp线程池和连接池

OkHttp线程池和连接池了解了OkHttp的网络请求流程以及拦截器实现原理,再关注OkHttp中两个重要的:OkHttp的线程池和连接池。1、OkHttp线程池在OkHttp网络请求流程一文中,我们分析了OkHttp异步和同步请求流程。请求最后都在Dispatcher中分发调度处理,最后被ExecutorService执行。1.1、DispatcherDispatcher中执行任务的执行器是ex

5.2k
优美的开源动效库:Lottie
开源

优美的开源动效库:Lottie

优美的开源动效库:Lottie1、强大的动效LottieLottie是一个适用于Android,iOS,Web和Windows的库,它可以使用Bodymovin解析以json格式导出的Adobe After Effects动画,并在移动设备和Web上原生渲染它们!GitHub:https://github.com/airbnb/lottie-androidLottie官网:http://airbn

4.3k