【原始碼閱讀】AndPermission原始碼閱讀

歡子發表於2019-05-09

前言

許可權是絕大多數App必不可少的部分,不管你仍在用原生的方式,還是其他的開源庫,AndPermission絕對是值得學習的一個開源庫,今天,我們就來學習下它的設計思想。

AndPermission

思路

許可權庫的思路大體上都如下圖所示,也玩不出太複雜的花樣。

image.png

使用

  • 1.新增引用 implementation 'com.yanzhenjie.permission:support:2.0.1'
  • 2.請求許可權
        AndPermission.with(this)
            .runtime()
            .permission(permissions)
            .rationale(new RuntimeRationale())
            .onGranted(new Action<List<String>>() {
                @Override
                public void onAction(List<String> permissions) {
                    toast(R.string.successfully);
                }
            })
            .onDenied(new Action<List<String>>() {
                @Override
                public void onAction(@NonNull List<String> permissions) {
                    toast(R.string.failure);
                    if (AndPermission.hasAlwaysDeniedPermission(MainActivity.this, permissions)) {
                        showSettingDialog(MainActivity.this, permissions);
                    }
                }
            })
            .start();
複製程式碼
  • 3.安裝應用
    private void installPackage() {
        AndPermission.with(this)
            .install()
            .file(new File(Environment.getExternalStorageDirectory(), "android.apk"))
            .rationale(new InstallRationale())
            .onGranted(new Action<File>() {
                @Override
                public void onAction(File data) {
                    // Installing.
                    toast(R.string.successfully);
                }
            })
            .onDenied(new Action<File>() {
                @Override
                public void onAction(File data) {
                    // The user refused to install.
                    toast(R.string.failure);
                }
            })
            .start();
    }
複製程式碼
  • 4.應用上顯示(懸浮窗)
    private void requestPermissionForAlertWindow() {
        AndPermission.with(this).overlay().rationale(new OverlayRationale()).onGranted(new Action<Void>() {
            @Override
            public void onAction(Void data) {
                toast(R.string.successfully);
                showAlertWindow();
            }
        }).onDenied(new Action<Void>() {
            @Override
            public void onAction(Void data) {
                toast(R.string.failure);
            }
        }).start();
    }
複製程式碼
  • 5.修改系統設定
    private void requestWriteSystemSetting() {
        AndPermission.with(this).setting().write().rationale(new WriteSettingRationale()).onGranted(new Action<Void>() {
            @Override
            public void onAction(Void data) {
                toast(R.string.successfully);
            }
        }).onDenied(new Action<Void>() {
            @Override
            public void onAction(Void data) {
                toast(R.string.failure);
            }
        }).start();
    }
複製程式碼
  • 6.通知
    private void requestNotificationListener() {
        AndPermission.with(this)
            .notification()
            .listener()
            .rationale(new NotifyListenerRationale())
            .onGranted(new Action<Void>() {
                @Override
                public void onAction(Void data) {
                    toast(R.string.successfully);
                }
            })
            .onDenied(new Action<Void>() {
                @Override
                public void onAction(Void data) {
                    toast(R.string.failure);
                }
            })
            .start();
    }
複製程式碼

原始碼分析

  • 1.with方法
public static Option with(Context context) {
        return new Boot(getContextSource(context));
    }
public static Option with(Fragment fragment) {
        return new Boot(new SupportFragmentSource(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));
    }
複製程式碼

with方法可以傳入ContextFragmentandroid.app.Fragment fragmentActivity,返回的都是Option物件,而它是個介面,我們來看看Option的實現類Boot

public class Boot implements Option {
    public Boot(Source source) {
        this.mSource = source;
    }
    @Override
    public RuntimeOption runtime() {
        return new Runtime(mSource);
    }
    @Override
    public InstallRequest install() {
        return INSTALL_REQUEST_FACTORY.create(mSource);
    }
    @Override
    public OverlayRequest overlay() {
        return OVERLAY_REQUEST_FACTORY.create(mSource);
    }
    @Override
    public NotifyOption notification() {
        return new Notify(mSource);
    }
    @Override
    public Setting setting() {
        return new Setting(mSource);
    }
}
複製程式碼

可以看到,這裡對不同的Request做了封裝,對以後的擴充套件非常有利,這也是AndPermission的亮點之一。

之前傳入的ContextFragmentandroid.app.Fragment fragmentActivity隻影響的startActivitystartActivityForResultisShowRationalePermission方法

Source相關程式碼

    public abstract void startActivity(Intent intent);

    public abstract void startActivityForResult(Intent intent, int requestCode);

    public abstract boolean isShowRationalePermission(String permission);
複製程式碼

ActivitySource相關程式碼

public class ActivitySource extends Source {

    @Override
    public void startActivity(Intent intent) {
        mActivity.startActivity(intent);
    }
    @Override
    public void startActivityForResult(Intent intent, int requestCode) {
        mActivity.startActivityForResult(intent, requestCode);
    }
    @Override
    public boolean isShowRationalePermission(String permission) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false;
        return mActivity.shouldShowRequestPermissionRationale(permission);
    }
}
複製程式碼
  • 2.permission
    @Override
    public PermissionRequest permission(@NonNull String... permissions) {
        //是否在`manifest.xml`中註冊
        checkPermissions(permissions);
        return FACTORY.create(mSource).permission(permissions);
    }
複製程式碼

這裡首先對傳入的許可權做了檢查,是否在manifest.xml中註冊,然後呼叫FACTORY.create建立了一個PermissionRequest

    static {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            FACTORY = new MRequestFactory();
        } else {
            FACTORY = new LRequestFactory();
        }
    }
    public interface PermissionRequestFactory {
        PermissionRequest create(Source source);
    }
複製程式碼
  • 3.rationale相當於是個攔截器,當沒有許可權時會執行。

這個有什麼用呢?比如說,App需要申請全域性懸浮窗許可權,相比直接跳到授權頁,彈個提示框由使用者選擇是否去授權就顯得友好的多。 當showRationale()被回撥後說明沒有許可權,此時開發者必須回撥RequestExecutor#execute()來啟動設定或者RequestExecutor#cancel()來取消啟動設定,否則將不會回撥onGranted()或者onDenied()中的任何一個,也就是說AndPermission將不會有任何響應。

  • 4.onGranted同意授權時呼叫,onDenied拒絕授權時呼叫
  • 5.start開始授權

MRequest相關程式碼如下

    @Override
    public void start() {
        List<String> deniedList = getDeniedPermissions(STANDARD_CHECKER, mSource, mPermissions);
        mDeniedPermissions = deniedList.toArray(new String[deniedList.size()]);
        if (mDeniedPermissions.length > 0) {
            List<String> rationaleList = getRationalePermissions(mSource, mDeniedPermissions);
            if (rationaleList.size() > 0) {
                mRationale.showRationale(mSource.getContext(), rationaleList, this);
            } else {
                execute();
            }
        } else {
            onCallback();
        }
    }
複製程式碼

首先,判斷傳遞進來的許可權有哪些是沒有已授權,如都已授權,直接回撥成功;如有未授權的,先判斷是否有需要攔截的,如沒有,則呼叫execute方法

    public void execute() {
        BridgeRequest request = new BridgeRequest(mSource);
        request.setType(BridgeRequest.TYPE_PERMISSION);
        request.setPermissions(mDeniedPermissions);
        request.setCallback(this);
        RequestManager.get().add(request);
    }
複製程式碼

RequestManager 它的核心是個執行緒池

相關方法

    private RequestManager() {
        this.mQueue = new LinkedBlockingQueue<>();

        new RequestExecutor(mQueue).start();
    }
    public void add(BridgeRequest request) {
        mQueue.add(request);
    }
複製程式碼

RequestExecutor相關方法

    public void run() {
        while (true) {
            synchronized (this) {
                try {
                    mRequest = mQueue.take();
                } catch (InterruptedException e) {
                    continue;
                }
                mMessenger = new Messenger(mRequest.getSource().getContext(), this);
                mMessenger.register();
                executeCurrent();
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    private void executeCurrent() {
            ...省略...
            case BridgeRequest.TYPE_PERMISSION: 
                BridgeActivity.requestPermission(mRequest.getSource(), mRequest.getPermissions());
                break;
            ...省略...
    }
複製程式碼

再來看看 BridgeActivity.requestPermission

    static void requestPermission(Source source, String[] permissions) {
        Intent intent = new Intent(source.getContext(), BridgeActivity.class);
        intent.putExtra(KEY_TYPE, BridgeRequest.TYPE_PERMISSION);
        intent.putExtra(KEY_PERMISSIONS, permissions);
        source.startActivity(intent);
    }
複製程式碼

BridgeActivity相關程式碼

    @Override
    protected void onCreate(Bundle savedInstanceState) {
                 ...省略...
                 String[] permissions = intent.getStringArrayExtra(KEY_PERMISSIONS);
                requestPermissions(permissions, BridgeRequest.TYPE_PERMISSION);
                 ...省略...
     }
複製程式碼

看到這裡大家就比較熟悉了,最後我們在看看授權的回撥處理

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

Messenger相關程式碼

    public static void send(Context context) {
        Intent broadcast = new Intent(ACTION);
        context.sendBroadcast(broadcast);
    }
    @Override
    public void onReceive(Context context, Intent intent) {
        mCallback.onCallback();
    }

複製程式碼

這裡回撥到了RequestExecutor#onCallback方法,而其又回撥到了MRequest#onCallback方法

MRequest相關程式碼

    @Override
    public void onCallback() {
        new AsyncTask<Void, Void, List<String>>() {
            @Override
            protected List<String> doInBackground(Void... voids) {
                return getDeniedPermissions(DOUBLE_CHECKER, mSource, mPermissions);
            }
            @Override
            protected void onPostExecute(List<String> deniedList) {
                if (deniedList.isEmpty()) {
                    callbackSucceed();
                } else {
                    callbackFailed(deniedList);
                }
            }
        }.execute();
    }
複製程式碼

這裡再次對傳入的許可權做檢查,如果沒有未授權,則回撥成功,否則回撥失敗。

  • 6.使用者拒絕授權時提示
                    if (AndPermission.hasAlwaysDeniedPermission(MainActivity.this, permissions)) {
                        showSettingDialog(MainActivity.this, permissions);
                    }
複製程式碼

最終呼叫的邏輯是在SettingPage#start方法,這裡對不同的機型做了適配

    public void start(int requestCode) {
        Intent intent;
        if (MARK.contains("huawei")) {
            intent = huaweiApi(mSource.getContext());
        } else if (MARK.contains("xiaomi")) {
            intent = xiaomiApi(mSource.getContext());
        } else if (MARK.contains("oppo")) {
            intent = oppoApi(mSource.getContext());
        } else if (MARK.contains("vivo")) {
            intent = vivoApi(mSource.getContext());
        } else if (MARK.contains("meizu")) {
            intent = meizuApi(mSource.getContext());
        } else {
            intent = defaultApi(mSource.getContext());
        }
        try {
            mSource.startActivityForResult(intent, requestCode);
        } catch (Exception e) {
            intent = defaultApi(mSource.getContext());
            mSource.startActivityForResult(intent, requestCode);
        }
    }
複製程式碼

結束語

這裡只分析了runtime的設計思想,installoverlaynotification與其類似,就不一一分析了。

相關文章