這可能是最精簡的Android6.0運行時權限處理,百行代碼的工具類,支持Rationale,附:各種權限詳細處理

0x00:前言

對于Android6.0運行時權限的處理方式網上有很多,包括注解,RxJava等等。一直沒有正面提到我關心的問題--如果我不在Activity或者Fragment里面,需要運行時權限該怎么去做?導致我開始一直以為運行時權限的處理必需要在Activity或者Fragment之中。

那么:
我有一個錄音的自定義控件在很多頁面需要使用怎么辦?
我有一個聯系人列表,要在adapter里面撥打電話怎么辦?
我有一個定位的工具類要在多個頁面使用怎么辦?
等等...
之前我還問過一些同行,他說用回調,回調到Activity或者Fragment,我當時覺得是一種解決方案,但是卻很麻煩,如果有多個頁面使用,那不是要處理很多次。

直到某一天在github上看到一個分享了簡單的工具類MPermissionUtils ,一下子解決了我的疑惑,雖然他也沒有明確給出答案,但是我從他的使用上卻恍然大悟,原來是一開始我就理解錯了。我們只需要把系統回調方法onRequestPermissionsResult放到BaseActivity里面,當然你所有的用到權限的Activity必需繼承自BaseActivity,將處理結果通過工具類調出來,加一個自定義的回調到請求的發起處即可。

因為你要用到運行時權限的地方總要依賴于Activity的存在,如果不再Activity里面或者當前代碼獲取不到Activity,那就傳過去,一切的處理結果都會回到你發起請求所在的Activity。

那么一不做二不休,我們這時候有沒有考慮Fragment里面的處理其實是多余的,我們可不可以都放到Activity里面來處理。于是就化繁為簡產生了我的XPermissionUtils

0x01:代碼實現

public class XPermissionUtils {

    private static int mRequestCode = -1;
    private static OnPermissionListener mOnPermissionListener;

    public interface OnPermissionListener {

        void onPermissionGranted();

        void onPermissionDenied(String[] deniedPermissions, boolean alwaysDenied);
    }

    @TargetApi(Build.VERSION_CODES.M)
    public static void requestPermissionsAgain(@NonNull Context context, @NonNull String[] permissions,
        @NonNull int requestCode) {
        if (context instanceof Activity) {
            ((Activity) context).requestPermissions(permissions, requestCode);
        } else {
            throw new IllegalArgumentException("Context must be an Activity");
        }
    }

    @TargetApi(Build.VERSION_CODES.M)
    public static void requestPermissions(@NonNull Context context, @NonNull int requestCode,
        @NonNull String[] permissions, OnPermissionListener listener) {
        mRequestCode = requestCode;
        mOnPermissionListener = listener;
        String[] deniedPermissions = getDeniedPermissions(context, permissions);
        if (deniedPermissions.length > 0) {
            requestPermissionsAgain(context, permissions, requestCode);
        } else {
            if (mOnPermissionListener != null) mOnPermissionListener.onPermissionGranted();
        }
    }

    /**
     * 請求權限結果,對應Activity中onRequestPermissionsResult()方法。
     */
    public static void onRequestPermissionsResult(@NonNull Activity context, int requestCode,
        @NonNull String[] permissions, int[] grantResults) {
        if (mRequestCode != -1 && requestCode == mRequestCode) {
            if (mOnPermissionListener != null) {
                String[] deniedPermissions = getDeniedPermissions(context, permissions);
                if (deniedPermissions.length > 0) {
                    boolean alwaysDenied = hasAlwaysDeniedPermission(context, permissions);
                    mOnPermissionListener.onPermissionDenied(deniedPermissions, alwaysDenied);
                } else {
                    mOnPermissionListener.onPermissionGranted();
                }
            }
        }
    }

    /**
     * 獲取請求權限中需要授權的權限
     */
    private static String[] getDeniedPermissions(@NonNull Context context, @NonNull String[] permissions) {
        List<String> deniedPermissions = new ArrayList();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_DENIED) {
                deniedPermissions.add(permission);
            }
        }
        return deniedPermissions.toArray(new String[deniedPermissions.size()]);
    }

    /**
     * 是否徹底拒絕了某項權限
     */
    private static boolean hasAlwaysDeniedPermission(@NonNull Context context, @NonNull String... deniedPermissions) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) return false;
        boolean rationale;
        for (String permission : deniedPermissions) {
            rationale = ActivityCompat.shouldShowRequestPermissionRationale((Activity) context, permission);
            if (!rationale) return true;
        }
        return false;
    }
}

0x02:實現思路

在最開始的時候本人的實現沒有支持shouldShowRequestPermissionRationale,是本人的疏忽。因為開始我用的小米手機沒有這個功能,后來發現有的手機支持有的不支持。顧名思義這個方法的意思是否需要給用戶申請該權限的提示,當用戶拒絕權限之后如果沒有勾選不再提示,下次申請權限的時候可以加一個自定義的彈窗提示,用戶點繼續驗證可以再次驗證權限。
大致實現思路如下:

Flow Chart.png

注意:
1>判斷是否需要提示方法shouldShowRequestPermissionRationale,只要有一個權限需要提示就返回true
2>判斷是否徹底禁止權限方法hasAlwaysDeniedPermission,只要有一個徹底禁止就返回true
3>為了節省代碼在發起請求與請求結果中用了同樣的方法獲取未授權的權限

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

此外在請求結果的時候還可以用另外的方法獲取,結果是一樣的

private static String[] getDeniedPermissions(String[] permissions, int[] grantResults) {
        List<String> deniedPermissions = new ArrayList<>();
        for (int i = 0; i < permissions.length; i++) {
            if (grantResults[i] != PackageManager.PERMISSION_GRANTED) {
                deniedPermissions.add(permissions[i]);
            }
        }
        return deniedPermissions.toArray(new String[deniedPermissions.size()]);
    }

0x03:使用方式

以打開相機為例

1、首先AndroidManifest中配置必要的權限

<uses-permission android:name="android.permission.CAMERA"/>

2、在基類中加上回調方法

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

3、調用工具類方法

XPermissionUtils.requestPermissions(Context context, int requestCode, String[] permissions, OnPermissionListener listener)

這里主要注意這個Context必需是一個Activity
如果在Activity中可以傳this;
如果在Fragment中傳getActivity();
如果在View中傳getContext();
等等.....

private void doOpenCamera() {
        XPermissionUtils.requestPermissions(this, RequestCode.CAMERA, new String[] { Manifest.permission.CAMERA },
            new XPermissionUtils.OnPermissionListener() {
                @Override
                public void onPermissionGranted() {
                    if (PermissionHelper.isCameraEnable()) {
                        Toast.makeText(MainActivity.this, "打開相機操作", Toast.LENGTH_LONG).show();
                    } else {
                        DialogUtil.showPermissionManagerDialog(MainActivity.this, "相機");
                    }
                }

                @Override
                public void onPermissionDenied(final String[] deniedPermissions, boolean alwaysDenied) {
                    Toast.makeText(context, "獲取相機權限失敗", Toast.LENGTH_SHORT).show();
                    if (alwaysDenied) { // 拒絕后不再詢問 -> 提示跳轉到設置
                        DialogUtil.showPermissionManagerDialog(MainActivity.this, "相機");
                    } else {    // 拒絕 -> 提示此公告的意義,并可再次嘗試獲取權限
                        new AlertDialog.Builder(context).setTitle("溫馨提示")
                            .setMessage("我們需要相機權限才能正常使用該功能")
                            .setNegativeButton("取消", null)
                            .setPositiveButton("驗證權限", new DialogInterface.OnClickListener() {
                                @RequiresApi(api = Build.VERSION_CODES.M)
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    XPermissionUtils.requestPermissionsAgain(context, deniedPermissions,
                                        RequestCode.CAMERA);
                                }
                            })
                            .show();
                    }
                }
            });
    }

4、一次申請多個權限

用戶可能部分拒絕,因此在onPermissionDenied(String[] deniedPermissions)回調中返回了請求結果中所有被拒絕的權限,用戶可用于比對判斷出哪些權限被拒絕,給用戶明確的提示

    private void doMorePermission() {
        XPermissionUtils.requestPermissions(this, RequestCode.MORE,
            new String[] { Manifest.permission.WRITE_CONTACTS, Manifest.permission.READ_SMS },
            new XPermissionUtils.OnPermissionListener() {
                @Override
                public void onPermissionGranted() {
                    Toast.makeText(context, "獲取聯系人,短信權限成功", Toast.LENGTH_SHORT).show();
                }

                @Override
                public void onPermissionDenied(String[] deniedPermissions, boolean alwaysDenied) {
                    StringBuilder sBuilder = new StringBuilder();
                    for (String deniedPermission : deniedPermissions) {
                        if (deniedPermission.equals(Manifest.permission.WRITE_CONTACTS)) {
                            sBuilder.append("聯系人");
                            sBuilder.append(",");
                        }
                        if (deniedPermission.equals(Manifest.permission.READ_SMS)) {
                            sBuilder.append("短信");
                            sBuilder.append(",");
                        }
                    }
                    if (sBuilder.length() > 0) {
                        sBuilder.deleteCharAt(sBuilder.length() - 1);
                    }
                    Toast.makeText(context, "獲取" + sBuilder.toString() + "權限失敗", Toast.LENGTH_SHORT).show();
                    if (alwaysDenied) {
                        DialogUtil.showPermissionManagerDialog(MainActivity.this, sBuilder.toString());
                    }
                }
            });
    }

0x04:各種運行時權限處理詳談

其實在6.0之前已經存在運行時權限,只不過沒有明確提出這個概念,在6.0之前,獲取位置、讀取通訊錄、拍照、錄音等都是需要在操作的時候去獲取權限的。那么這些權限的區別是6.0以后需要我們去寫請求獲取權限的代碼,而之前是當代碼執行到需要權限的地方就會彈出提示框。
那么針對不同的權限可能有不同的處理方式,下面簡單列舉,如果需要看代碼可以在源碼的Demo中查看

1、撥打電話

撥打電話在某些手機上(如小米)拒絕之后是每次申請都有提示的,因為他顯示的是“拒絕一次”。撥打電話其實如果不是產品要求直接撥出去可以使用調轉到撥號頁面實現的,這個不需要權限:

 Intent intent = new Intent(Intent.ACTION_DIAL);
        Uri data = Uri.parse("tel:10010");
        intent.setData(data);
        startActivity(intent);

2、錄音

(1)錄音權限在6.0之前是無法判斷是否獲取權限的,只能通過非常規的方法獲取,詳見項目Demo
(2)長按按鈕錄音,在第一次獲取權限的時候需要特殊處理,彈出獲取權限的提示框之后手指已經離開,不能進行錄音的操作。

3、打開相機

相機權限在6.0之前同樣也是無法判斷是否獲取權限的,只能通過非常規的方法獲取,詳見項目Demo

4、獲取位置

(1)首先手機需要開啟位置服務,如果沒有開啟,那么即使app開啟獲取位置權限也是獲取不到的
(2)在6.0以下沒有辦法判斷是否開啟位置權限,可以根據具體使用場景進行判斷。
(3)(使用系統Api)要注意在室內如果選擇Gps定位會獲取不到位置,這里可以參考Demo中LocationUtils的實現思路。
(4)使用百度或者高德地圖可能不適用,因為他自己已經帶有請求權限的處理,貌似不需要系統權限也能定位,沒有深入研究。

5、獲取外部存儲

這個在有些手機上比較特殊,比如打開圖庫這樣的功能,在小米手機上就不需要運行時權限,華為就需要,這個還是需要在使用的時候主動請求一下。

0x05 特別鳴謝

MPermissionUtils
PermissionGen
AndPermission

如有不足,歡迎指正。最后附上源碼地址
XPermissionUtils

轉載請注明出處http://www.lxweimin.com/p/4a60b064a0ab

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容