一個(gè)類快速實(shí)現(xiàn) Android 6.0 運(yùn)行時(shí)權(quán)限適配

前言

現(xiàn)在來談 Android 6.0 運(yùn)行時(shí)權(quán)限適配,可以說是很過時(shí)了,可是為什么還要寫呢?
一是試用了目前 GitHub 上排名比較靠前的開源項(xiàng)目,確實(shí)都很棒,但是在易用性還是難以令人滿意,便萌生了自己擼一個(gè)的想法。
二是看了下目前國(guó)內(nèi)主流的應(yīng)用,發(fā)現(xiàn)很多都還沒有適配 Android 6.0 ,因此覺得這篇文章還有它的意義。

使用

既然上面說到了易用性,那我們先來看看使用方法
在需要申請(qǐng)權(quán)限的地方調(diào)用

PermissionReq.with(this) // Activity or Fragment
        .permissions(Manifest.permission.CAMERA,
                Manifest.permission.WRITE_EXTERNAL_STORAGE) // 需要申請(qǐng)的權(quán)限
        .result(new PermissionReq.Result() { // 申請(qǐng)結(jié)果回調(diào)
            @Override
            public void onGranted() { // 申請(qǐng)成功
                // do something
            }
            @Override
            public void onDenied() { // 申請(qǐng)失敗
                // do something
            }
        })
        .request();

在 Activity 基類和 Fragment 基類中添加以下代碼

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    PermissionReq.onRequestPermissionsResult(requestCode, permissions, grantResults);
}

API 設(shè)計(jì)采用了比較流行的流式調(diào)用,不知道大家看了是什么感覺,我覺得使用起來比較簡(jiǎn)單,而且不會(huì)破壞原來的代碼結(jié)構(gòu)。
說到這里,我多說一句,現(xiàn)在有很多開源框架都是使用注解的方式來回調(diào)申請(qǐng)結(jié)果的,我覺得用這種方式雖然代碼層次變少了,但是可讀性變差了,而且可能會(huì)破壞原來代碼結(jié)構(gòu)。

這里還有個(gè)亮點(diǎn)不知道大家注意到?jīng)],我們沒有用到 RequestCode ,那 RequestCode 哪里去了呢,在接下來的內(nèi)容中我會(huì)告訴大家。
這也是我最喜歡的地方,不需要在每個(gè)申請(qǐng)權(quán)限的地方定義一個(gè) RequestCode ,更不用擔(dān)心 RequestCode 會(huì)重復(fù),因?yàn)?PermissionReq 已經(jīng)幫大家處理好了。

源碼解析

看完使用方式后我們來看下內(nèi)部實(shí)現(xiàn),我們按照流程來看

首先我們要檢查 App 注冊(cè)了哪些權(quán)限,如果要申請(qǐng)的權(quán)限壓根就沒有在 Manifest 中注冊(cè),那么肯定會(huì)失敗的

initManifestPermission(activity);
for (String permission : mPermissions) {
    if (!sManifestPermissionSet.contains(permission)) {
        if (mResult != null) {
            mResult.onDenied();
        }
        return;
    }
}

private static Set<String> sManifestPermissionSet;
private static synchronized void initManifestPermission(Context context) {
    if (sManifestPermissionSet == null) {
        sManifestPermissionSet = new HashSet<>();
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
            String[] permissions = packageInfo.requestedPermissions;
            Collections.addAll(sManifestPermissionSet, permissions);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
    }
}

為了線程安全我們添加了 synchronized 修飾符。

如果要申請(qǐng)的權(quán)限已經(jīng)在 Manifest 中注冊(cè)了,我們接下來就要區(qū)分下系統(tǒng)版本了,如果系統(tǒng)版本低于 26 直接返回成功,否則才需要申請(qǐng)權(quán)限
這段代碼比較簡(jiǎn)單,我就不貼了

如果系統(tǒng)版本 >= 26 ,那么才開始我們真正的申請(qǐng)流程
檢查要申請(qǐng)的權(quán)限是否已經(jīng)被允許,如果已經(jīng)被允許,那么就沒必要再申請(qǐng)了

List<String> deniedPermissionList = getDeniedPermissions(activity, mPermissions);
if (deniedPermissionList.isEmpty()) {
    if (mResult != null) {
        mResult.onGranted();
    }
    return;
}

private static List<String> getDeniedPermissions(Context context, String[] permissions) {
    List<String> deniedPermissionList = new ArrayList<>();
    for (String permission : permissions) {
        if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
            deniedPermissionList.add(permission);
        }
    }
    return deniedPermissionList;
}

如果要申請(qǐng)的權(quán)限沒有全部被允許,那么我們就需要向系統(tǒng)發(fā)送申請(qǐng)了
生成 RequestCode

int requestCode = genRequestCode();

private static AtomicInteger sRequestCode = new AtomicInteger(0);
private static int genRequestCode() {
    return sRequestCode.incrementAndGet();
}

還記得上面說我們?cè)谑褂脮r(shí)不需要定義 RequestCode 嗎,至此,RequestCode 終于浮出水面
我們?cè)趦?nèi)部使用一個(gè)靜態(tài)自增的 AtomicInteger 作為 RequestCode ,保證 RequestCode 不會(huì)重復(fù),使用 AtomicInteger 而不直接使用 int 是為了線程安全

向系統(tǒng)發(fā)送申請(qǐng)

String[] deniedPermissions = deniedPermissionList.toArray(new String[deniedPermissionList.size()]);
requestPermissions(mObject, deniedPermissions, requestCode);
sResultArray.put(requestCode, mResult);

@TargetApi(Build.VERSION_CODES.M)
private static void requestPermissions(Object object, String[] permissions, int requestCode) {
    if (object instanceof Activity) {
        ((Activity) object).requestPermissions(permissions, requestCode);
    } else if (object instanceof Fragment) {
        ((Fragment) object).requestPermissions(permissions, requestCode);
    }
}

申請(qǐng)時(shí)區(qū)分來源,申請(qǐng)后把 Result 放入 Array 中保存起來,等待申請(qǐng)結(jié)果到達(dá)

申請(qǐng)結(jié)果到達(dá)后通知申請(qǐng)者

public static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    Result result = sResultArray.get(requestCode);
    if (result == null) {
        return;
    }
    sResultArray.remove(requestCode);
    for (int grantResult : grantResults) {
        if (grantResult != PackageManager.PERMISSION_GRANTED) {
            result.onDenied();
            return;
        }
    }
    result.onGranted();
}

到這里我們的申請(qǐng)權(quán)限流程已經(jīng)走完了,源碼也看完了。

完整代碼

為了方便大家使用,我貼一下完整代碼

public class PermissionReq {
    private static AtomicInteger sRequestCode = new AtomicInteger(0);
    private static SparseArray<Result> sResultArray = new SparseArray<>();
    private static Set<String> sManifestPermissionSet;

    public interface Result {
        void onGranted();

        void onDenied();
    }

    private Object mObject;
    private String[] mPermissions;
    private Result mResult;

    private PermissionReq(Object object) {
        mObject = object;
    }

    public static PermissionReq with(@NonNull Activity activity) {
        return new PermissionReq(activity);
    }

    public static PermissionReq with(@NonNull Fragment fragment) {
        return new PermissionReq(fragment);
    }

    public PermissionReq permissions(@NonNull String... permissions) {
        mPermissions = permissions;
        return this;
    }

    public PermissionReq result(@Nullable Result result) {
        mResult = result;
        return this;
    }

    public void request() {
        Activity activity = getActivity(mObject);
        if (activity == null) {
            throw new IllegalArgumentException(mObject.getClass().getName() + " is not supported");
        }

        initManifestPermission(activity);
        for (String permission : mPermissions) {
            if (!sManifestPermissionSet.contains(permission)) {
                if (mResult != null) {
                    mResult.onDenied();
                }
                return;
            }
        }

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            if (mResult != null) {
                mResult.onGranted();
            }
            return;
        }

        List<String> deniedPermissionList = getDeniedPermissions(activity, mPermissions);
        if (deniedPermissionList.isEmpty()) {
            if (mResult != null) {
                mResult.onGranted();
            }
            return;
        }

        int requestCode = genRequestCode();
        String[] deniedPermissions = deniedPermissionList.toArray(new String[deniedPermissionList.size()]);
        requestPermissions(mObject, deniedPermissions, requestCode);
        sResultArray.put(requestCode, mResult);
    }

    public static void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        Result result = sResultArray.get(requestCode);

        if (result == null) {
            return;
        }

        sResultArray.remove(requestCode);

        for (int grantResult : grantResults) {
            if (grantResult != PackageManager.PERMISSION_GRANTED) {
                result.onDenied();
                return;
            }
        }
        result.onGranted();
    }

    @TargetApi(Build.VERSION_CODES.M)
    private static void requestPermissions(Object object, String[] permissions, int requestCode) {
        if (object instanceof Activity) {
            ((Activity) object).requestPermissions(permissions, requestCode);
        } else if (object instanceof Fragment) {
            ((Fragment) object).requestPermissions(permissions, requestCode);
        }
    }

    private static List<String> getDeniedPermissions(Context context, String[] permissions) {
        List<String> deniedPermissionList = new ArrayList<>();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                deniedPermissionList.add(permission);
            }
        }
        return deniedPermissionList;
    }

    private static synchronized void initManifestPermission(Context context) {
        if (sManifestPermissionSet == null) {
            sManifestPermissionSet = new HashSet<>();
            try {
                PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_PERMISSIONS);
                String[] permissions = packageInfo.requestedPermissions;
                Collections.addAll(sManifestPermissionSet, permissions);
            } catch (PackageManager.NameNotFoundException e) {
                e.printStackTrace();
            }
        }
    }

    private static Activity getActivity(Object object) {
        if (object != null) {
            if (object instanceof Activity) {
                return (Activity) object;
            } else if (object instanceof Fragment) {
                return ((Fragment) object).getActivity();
            }
        }
        return null;
    }

    private static int genRequestCode() {
        return sRequestCode.incrementAndGet();
    }
}

總結(jié)

本文主要介紹了如何快速、簡(jiǎn)單的適配 Android 6.0 運(yùn)行時(shí)權(quán)限,雖然寫的比較晚了,但還是希望能幫到大家。
如果你在閱讀本文時(shí)發(fā)現(xiàn)什么問題或者紕漏,或者你有不同的看法,歡迎指出!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容