Android 6.0 適配——管理運行時權限

說起來這是第一次在簡書寫文章,由于最近項目使用的機器是Android 6.0的機器,一些危險的權限就需要我們在運行時自己來動態的申請了,所以今天就抽空寫下運行時權限管理。
前言

眾所周知,在API23之前,只要在AndroidManifest.xml中注冊過的權限,程序運行時都會自動獲取到。但是到了23及更高,危險的權限就需要我們自己來動態的申請了,而此時用戶也就有了拒絕我們需要的權限的權力,這當然會導致我們程序的運行不正常,甚至是造成程序的崩潰。所以我們就需要盡可能的提示用戶同意我們的權限申請。
好了,講了一堆廢話,今天就以申請"打電話"權限這個例子來講一下在實際項目中,我們應該怎么去申請權限。

必知

系統權限分為幾個保護級別。需要了解的兩個最重要保護級別是正常權限和危險權限。首先,你必須得知道哪些是需要動態申請的權限,也就是我前面所提到的危險權限,并且危險權限也分組,所以到底哪些是危險權限呢?想知道其實很簡單,官方文檔早就告訴了我們,相信有的童鞋早已看到過,這里貼一下地址:https://developer.android.com/guide/topics/security/permissions.html
看不了的童鞋也沒事,我這里直接把頁面截出來了:

權限.png

準備

這里以PHONE權限組的CALL_PHONE權限舉例講下怎么申請。
首先在AndroidManifest.xml中加入我們想要的權限:

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

必須要強調一下,如果申請的權限在AndroidManifest.xml中并沒有注冊的話,是不會彈出權限申請窗口的。:)
然后看下布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.AppCompatButton xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/button_activity_main"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="@string/permission_request"
    tools:context=".BaseActivity" />

嗯,是不是XML中的一股清流,這樣就完啦?完了...
呵呵,這里就是為了演示,我就用了1個Button,點擊時去申請權限,實際中肯定是在進入應用時或者使用到相關功能前就得申請了。
至此,準備工作完成。

開始

由于項目中肯定會有很多地方需要申請權限,所以這里我把權限管理的重復的邏輯寫在了基類之中。
可能沒有使用過6.0及更高版本手機的童鞋并不知道這個窗口是怎樣的,在這里我還是截了出來:


權限申請窗口.png

哦,這里我用的是Nexus 6P,想入手的童鞋千萬別買,2K屏,一天3充。
先把需要申請的權限寫成常量,這里就是Manifest.permission.CALL_PHONE這個權限:

    public static final String PERMISSION_CALL_PHONE = Manifest.permission.CALL_PHONE;

然后看下判斷權限是否已經被用戶授予的函數:

    public boolean hasPermission(String... permissions) {
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
        }
        return true;
    }

核心函數就是ContextCompat.checkSelfPermission();,從字面意思大家也能看出來是什么函數。參數是我們需要驗證的權限,值得注意的是參數類型是String...,所以我們既可以傳入一個String,也可以傳入String[],也就是說我們可以傳入一組權限。當它返回true時表示權限已經被授予,反之就表示權限未被授予,這個時候我們就需要去主動的彈出權限申請的窗口了,requestCode在接下來會用到:

    public void requestPermission(int requestCode, String... permissions) {
        ActivityCompat.requestPermissions(activity, permissions, requestCode);
    }

在基類中我還寫了三個函數,分別是權限授予成功時執行的函數、權限授予失敗時執行的函數以及讓自定義的對話框消失的函數,這個對話框不是系統自身彈出的對話框。

    public abstract void requestPermissionSuccess();

    public abstract void requestPermissionFail();

    public abstract void dismissDialog();

前面說到requestPermission()是用來請求權限的,其中有一個requestCode,這個requestCode就是用來判斷是否執行requestPermissionSuccess()和requestPermissionFail()的。
在基類中我們重寫onRequestPermissionsResult(),它就是requestPermission()回調的函數,可以看到我們根據grantResults[0]的值來執行成功或失敗的函數。

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case Constants.REQUEST_CODE_PERMISSION:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    requestPermissionSuccess();
                } else {
                    requestPermissionFail();
                }
                break;
            default:
                break;
        }
    }
接著

子類繼承基類之后,看看子類需要做什么:
點擊Button之后會啟動callPhone(),這個函數的作用就是判斷權限是否被授予了,如果true,執行requestPermissionSuccess(),如果false,繼續請求授予,也就是繼續彈出權限申請窗口:

    private void callPhone() {
        if (hasPermission(PERMISSION_CALL_PHONE)) {
            requestPermissionSuccess();
        } else {
            requestPermission(Constants.REQUEST_CODE_PERMISSION, PERMISSION_CALL_PHONE);
        }
    }

授予成功時還需要判斷到底有沒有被授予權限,否則代碼會警告我們判斷是否被授予權限。然后當然就是打電話,這里不厚道地打向了10086,大家可別學我啊:):

    @Override
    public void requestPermissionSuccess() {
        if (ActivityCompat.checkSelfPermission(context, PERMISSION_CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
            return;
        }
        Intent intent = new Intent(Intent.ACTION_CALL);
        Uri uri = Uri.parse("tel:" + "10086");
        intent.setData(uri);
        startActivity(intent);//打電話
        ToastUtil.toastLong(context, getString(R.string.permission_success));
    }

在這里失敗時分兩種情況:第一種是用戶會不斷的拒絕,此時首先我們在用戶拒絕之后彈出一個對話框,這個對話框的作用就是提示用戶我們為什么需要這個權限,ad就是AlertDialog,AlertDialog的用法這里不討論,用戶點擊確定時我們會再次彈出權限申請窗口:

    @Override
    public void requestPermissionFail() {
        showDialog();
        ToastUtil.toastLong(context, getString(R.string.permission_fail));
    }

    @Override
    public void dismissDialog() {
        if (ad != null) {
            ad.dismiss();
        }
    }

    private void showDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(context);
        builder.setMessage(getString(R.string.permission_dialog_message));
        builder.setPositiveButton(getString(R.string.permission_dialog_confirm), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                requestPermission(Constants.REQUEST_CODE_PERMISSION, PERMISSION_CALL_PHONE);
            }
        });
        ad = builder.show();
    }
自定義對話框.png

第二種情況就是用戶勾選了"不再詢問"并且拒絕的情況。在原生的系統下,第一次彈出來的權限申請窗口是看不到"不再詢問"選項的,但是第二次彈出來就會看到:


不再詢問

這個時候問題就來了,假如用戶勾選了"不再詢問",又拒絕了怎么辦?我們是不是永遠不能申請到權限了呢?這個時候當然還是有救命稻草的,讓我們回到基類,還是看onRequestPermissionsResult()。

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
            case Constants.REQUEST_CODE_PERMISSION:
                if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    requestPermissionSuccess();
                } else {
                    requestPermissionFail();
                }
                for (int i = 0; i < grantResults.length; i++) {
                    if (grantResults[i] == PackageManager.PERMISSION_DENIED) {//判斷用戶是否勾選了不再詢問
                        if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permissions[i])) {
                            ToastUtil.toastLong(context, getString(R.string.permission_settings));
                            dismissDialog();
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            Uri uri = Uri.fromParts("package", getPackageName(), null);
                            intent.setData(uri);
                            startActivityForResult(intent, Constants.REQUEST_CODE_SETTINGS);
                        }
                    }
                }
                break;
            default:
                break;
        }
    }

增加的代碼作用就是判斷用戶是否勾選了不再詢問,如果勾選了,首先讓我們自定義的對話框消失,其次就跳轉到應用信息頁面,并且提示用戶手動去權限中開啟權限。

                for (int i = 0; i < grantResults.length; i++) {
                    if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
                        if (!ActivityCompat.shouldShowRequestPermissionRationale(activity, permissions[i])) {
                            ToastUtil.toastLong(context, getString(R.string.permission_settings));
                            dismissDialog();
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                            Uri uri = Uri.fromParts("package", getPackageName(), null);
                            intent.setData(uri);
                            startActivityForResult(intent, Constants.REQUEST_CODE_SETTINGS);
                        }
                    }
                }

相信大家一定注意到了這里用的是startActivityForResult(),這是因為跳轉到應用信息頁面之后,是可以使用返回鍵回到之前的Activity的,這個時候用戶是有可能什么都沒做就返回的。所以這個時候我們還需要在基類的onActivityResult()中判斷是否被授予了權限,在這里我在未被授予時直接強關了應用:):

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (hasPermission(PERMISSION_CALL_PHONE)) {
            requestPermissionSuccess();
        } else {
            application.finishAllActivity();
        }
    }
應用信息

應用權限
結尾

此時如果用戶一直不同意授予權限的話,他看到的情況就是每次進入應用都會自動跳轉到應用信息頁面:),到這里流程就差不多了,相信大家也能看得懂,不過寫著寫著發現自己也有點亂,所以最后我還是準備了Demo給大家下載,沒錯,我就是來騙star的 :) ...看著Demo大家一定能明白運行時權限管理是怎樣的一個流程,好,有時間我再寫點文章分享,再見。

提示

1.同一權限組的權限,只要申請其中一個,同組的其余權限就不用再申請了。
2.如果將App的targetSdkVersion設置為23以下,就可以規避危險權限的檢查,也就是在安裝的時候就賦予該App所申請的所有權限,但是不建議這么做,畢竟人要往前看,不是么。

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

推薦閱讀更多精彩內容