android6.0動態(tài)權(quán)限與封裝

1. 目標(biāo)

此文章的最終目標(biāo):讓童鞋們了解6.0動態(tài)權(quán)限的邏輯耕拷,以及學(xué)會封裝動態(tài)權(quán)限申請,實現(xiàn)'一句話申請權(quán)限'

2. 前言

在android6.0之前嗦随,開發(fā)者無需在java代碼里進(jìn)行危險權(quán)限的申請,只需在AndroidManifest.xml文件里進(jìn)行權(quán)限聲明即可敬尺,但從android6.0開始枚尼,google為了用戶的安全考慮,加入了動態(tài)權(quán)限機制砂吞,危險權(quán)限必須在代碼里申請署恍,當(dāng)然,AndroidManifest.xml文件里一樣還是要進(jìn)行聲明蜻直,別忘了這一點哦盯质。(ps:加入了動態(tài)權(quán)限,雖然對開發(fā)者而言是麻煩了一些概而,但是對整體的android環(huán)境有一定的改善呼巷,流氓軟件不能像以前那樣流氓了。)

3. 什么是危險權(quán)限

危險權(quán)限赎瑰,也可以理解成較敏感權(quán)限王悍,有哪些呢,請看下表:

group:android.permission-group.CONTACTS
  permission:android.permission.WRITE_CONTACTS
  permission:android.permission.GET_ACCOUNTS
  permission:android.permission.READ_CONTACTS

group:android.permission-group.PHONE
  permission:android.permission.READ_CALL_LOG
  permission:android.permission.READ_PHONE_STATE
  permission:android.permission.CALL_PHONE
  permission:android.permission.WRITE_CALL_LOG
  permission:android.permission.USE_SIP
  permission:android.permission.PROCESS_OUTGOING_CALLS
  permission:com.android.voicemail.permission.ADD_VOICEMAIL

group:android.permission-group.CALENDAR
  permission:android.permission.READ_CALENDAR
  permission:android.permission.WRITE_CALENDAR

group:android.permission-group.CAMERA
  permission:android.permission.CAMERA

group:android.permission-group.SENSORS
  permission:android.permission.BODY_SENSORS

group:android.permission-group.LOCATION
  permission:android.permission.ACCESS_FINE_LOCATION
  permission:android.permission.ACCESS_COARSE_LOCATION

group:android.permission-group.STORAGE
  permission:android.permission.READ_EXTERNAL_STORAGE
  permission:android.permission.WRITE_EXTERNAL_STORAGE

group:android.permission-group.MICROPHONE
  permission:android.permission.RECORD_AUDIO

group:android.permission-group.SMS
  permission:android.permission.READ_SMS
  permission:android.permission.RECEIVE_WAP_PUSH
  permission:android.permission.RECEIVE_MMS
  permission:android.permission.RECEIVE_SMS
  permission:android.permission.SEND_SMS
  permission:android.permission.READ_CELL_BROADCASTS

很明顯可以看到餐曼,一個group下面帶有一個或多個permission压储,這是什么情況呢?
其實是這樣的晋辆,google把相類似的權(quán)限都分到了對應(yīng)的組里渠脉,這樣有什么好處? 僅僅是為了好看、方便記憶瓶佳?

不不芋膘,好處是:屬于同一組的權(quán)限不用重復(fù)授權(quán)。例如霸饲,如果用戶之前已經(jīng)授權(quán)過WRITE_CONTACTS權(quán)限的話为朋,當(dāng)你申請獲取READ_CONTACTS的時候,不需要等待用戶同意授權(quán)厚脉,而是直接返回授權(quán)成功习寸。

當(dāng)然,既然是以分組的形式進(jìn)行授權(quán)傻工,那么系統(tǒng)的授權(quán)dialog上面提示的也會是具體的組名霞溪,而不是單個權(quán)限名孵滞。

4. 怎么申請危險權(quán)限

4.1 先看看相關(guān)的API:

// 第二個參數(shù)可以從`Manifest.permission`里取值
// 第三個參數(shù)會在`onRequestPermissionsResult`方法里回調(diào)回來
ActivityCompat.requestPermissions(final Activity activity,
            final String[] permissions, final int requestCode)

調(diào)用此方法后,如果targetSdkVersion版本小于26鸯匹,或者用戶已經(jīng)授權(quán)過對應(yīng)組的權(quán)限坊饶,會直接回調(diào)activity的onRequestPermissionsResult方法,否則會彈出系統(tǒng)對話框詢問用戶是否允許該權(quán)限組殴蓬,當(dāng)用戶點了拒絕或者同意權(quán)限匿级,也都同意會回調(diào)activity的onRequestPermissionsResult方法。

4.2 那么染厅,重點邏輯處理似乎就是在onRequestPermissionsResult方法里了痘绎。

  • 先來看看該方法的參數(shù):
// 1. requestCode即調(diào)用ActivityCompat.requestPermissions時填的requestCode。
// 2. permissions即調(diào)用ActivityCompat.requestPermissions時填的permissions肖粮。
// 注意如果targetSdkVersion版本小于26孤页,則permissions是空數(shù)組
// 3. grantResults即權(quán)限授權(quán)結(jié)果。
// 注意如果targetSdkVersion版本小于26尿赚,則grantResults是空數(shù)組
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
  • 所以散庶,只要grantResults數(shù)組的size > 0 并且 其中一個為“拒絕授權(quán)”蕉堰,則為有權(quán)限被拒絕了
for (int i = 0; i < grantResults.length; i++) {
    if (grantResults[i] == PackageManager.PERMISSION_DENIED) {// 拒絕權(quán)限
        // 有權(quán)限被決絕凌净,執(zhí)行拒絕權(quán)限后的邏輯,例如退出界面
        // xxx
        return;
    }
}
// 執(zhí)行同意權(quán)限后的邏輯
// xxx
  • 應(yīng)用權(quán)限設(shè)置界面跳轉(zhuǎn)提醒
    權(quán)限拒絕有兩種情況:1. 不勾選“不再提醒”屋讶; 2.勾選“不再提醒”冰寻。
    如果用戶勾選了“不再提醒”,那么再次申請權(quán)限的時候皿渗,系統(tǒng)不會彈出授權(quán)dialog斩芭,此時我們的app應(yīng)該給予提示,否則用戶很有可能會感覺莫名其妙:怎么進(jìn)來這個界面后又退出來了乐疆。

那么問題來了划乖,怎么判斷有沒有勾選“不再提醒”,android提供了一個API:

// 1:用戶拒絕了該權(quán)限挤土,沒有勾選"不再提醒"琴庵,此方法將返回true。
// 2:用戶拒絕了該權(quán)限仰美,有勾選"不再提醒"迷殿,此方法將返回 false。
// 3:如果用戶同意了權(quán)限咖杂,此方法返回false
boolean flag = ActivityCompat.shouldShowRequestPermissionRationale(Activity , String[]])

所以庆寺,只要在判斷出是拒絕權(quán)限的地方,判斷出shouldShowRequestPermissionRationale返回false诉字,即為需要提醒跳轉(zhuǎn)懦尝。下面貼下代碼:

// 循環(huán)判斷權(quán)限知纷,只要有一個拒絕了,則回調(diào)onReject()陵霉。 全部允許時才回調(diào)onAllow()
for (int i = 0; i < grantResults.length; i++) {
    if (grantResults[i] == PackageManager.PERMISSION_DENIED) {// 拒絕權(quán)限
        // 對于 ActivityCompat.shouldShowRequestPermissionRationale
        // 1:用戶拒絕了該權(quán)限屈扎,沒有勾選"不再提醒",此方法將返回true撩匕。
        // 2:用戶拒絕了該權(quán)限鹰晨,有勾選"不再提醒",此方法將返回 false止毕。
        // 3:如果用戶同意了權(quán)限模蜡,此方法返回false
        if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {
            // 拒絕選了"不再提醒",一般提示跳轉(zhuǎn)到權(quán)限設(shè)置頁面扁凛,
            // 并在dialog cancel的時候忍疾,執(zhí)行onReject();
            showTipDialog(permissions[i]);
        } else {
            // 有權(quán)限被決絕,執(zhí)行拒絕權(quán)限后的邏輯
            onReject();
        }
        return;
     }
}
// 執(zhí)行同意權(quán)限后的邏輯
onAllow();

5. 動態(tài)請求封裝谨朝,實現(xiàn)'一句話申請權(quán)限'

其實原理很簡單卤妒,就是用一個PermissionBaseActivity父類來處理權(quán)限的申請和判斷,然后利用‘延遲實現(xiàn)’的思想字币,通過回調(diào)的方式则披,把具體的同意或拒絕授權(quán)后的業(yè)務(wù)邏輯交給子類來實現(xiàn)。還是直接貼代碼吧:

public class PermissionBaseActivity extends AppCompatActivity {
    protected final String TAG = getClass().getSimpleName().replace("Activity", "Act");
    protected TipDialog tipDialog;
    private SparseArray<OnPermissionResultListener> listenerMap = new SparseArray<>();

    /**
     * 權(quán)限請求結(jié)果監(jiān)聽者
     */
    public interface OnPermissionResultListener {
        /**
         * 權(quán)限被允許
         */
        void onAllow();

        /**
         * 權(quán)限被拒絕
         */
        void onReject();
    }

    /**
     * 鏡像權(quán)限申請
     * @param onPermissionResultListener 申請權(quán)限結(jié)果回調(diào)
     */
    public void checkPermissions(final String[] permissions, OnPermissionResultListener onPermissionResultListener) {
        if (Build.VERSION.SDK_INT < 23 || permissions.length == 0) {// android6.0已下不需要申請洗出,直接為"同意"
            if (onPermissionResultListener != null)
                onPermissionResultListener.onAllow();
        } else {
            int size = listenerMap.size();
            if (onPermissionResultListener != null) {
                listenerMap.put(size, onPermissionResultListener);
            }
            ActivityCompat.requestPermissions(this, permissions, size);
        }
    }

    /**
     * 顯示提示"跳轉(zhuǎn)到應(yīng)用權(quán)限設(shè)置界面"的dialog
     * @param permission 具體的某個權(quán)限士复,用于展示dialog的內(nèi)容文字。
     */
    private void showTipDialog(String permission, final OnPermissionResultListener onPermissionResultListener) {
        if (tipDialog == null) {
            tipDialog = new TipDialog(this);
        }
        // 確定按鈕
        tipDialog.findViewById(R.id.btn_confirm).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tipDialog.cancel();
                toAppDetailSetting();
            }
        });
        // 取消按鈕
        tipDialog.findViewById(R.id.btn_cancel).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tipDialog.cancel();
            }
        });

        tipDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
            @Override
            public void onCancel(DialogInterface dialog) {
                onPermissionResultListener.onReject();
            }
        });
        tipDialog.setTipText(PermissionUtil.getTip(permission));
        tipDialog.show();
    }

    /**
     * 跳轉(zhuǎn)系統(tǒng)的App應(yīng)用詳情頁
     */
    protected void toAppDetailSetting() {
        Intent localIntent = new Intent();
        localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        localIntent.setAction("android.settings.APPLICATION_DETAILS_SETTINGS");
        localIntent.setData(Uri.fromParts("package", getPackageName(), null));
        startActivity(localIntent);
    }

    @Override
    protected void onDestroy() {
        if (tipDialog != null) {
            tipDialog.cancel();
            tipDialog = null;
        }
        listenerMap.clear();
        listenerMap = null;
        super.onDestroy();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        OnPermissionResultListener onPermissionResultListener = listenerMap.get(requestCode);
        if (onPermissionResultListener != null) {
            listenerMap.remove(requestCode);
            // 循環(huán)判斷權(quán)限翩活,只要有一個拒絕了阱洪,則回調(diào)onReject()。 全部允許時才回調(diào)onAllow()
            for (int i = 0; i < grantResults.length; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_DENIED) {// 拒絕權(quán)限
                    // 對于 ActivityCompat.shouldShowRequestPermissionRationale
                    // 1:用戶拒絕了該權(quán)限菠镇,沒有勾選"不再提醒"冗荸,此方法將返回true。
                    // 2:用戶拒絕了該權(quán)限利耍,有勾選"不再提醒"蚌本,此方法將返回 false。
                    // 3:如果用戶同意了權(quán)限堂竟,此方法返回false
                    if (!ActivityCompat.shouldShowRequestPermissionRationale(this, permissions[i])) {
                        // 拒絕選了"不再提醒"魂毁,一般提示跳轉(zhuǎn)到權(quán)限設(shè)置頁面
                        showTipDialog(permissions[i], onPermissionResultListener);
                    } else {
                        onPermissionResultListener.onReject();
                    }
                    return;
                }
            }
            onPermissionResultListener.onAllow();
        }
    }
}

其中用了一個listenerMap來維護(hù)多個請求,避免如果同時有多個權(quán)限申請請求出嘹,onRequestPermissionsResult會亂套的席楚。
同時用此SparseArray的size作為requestCode,方便處理完授權(quán)判斷后税稼,從map里移除listener烦秩,減少內(nèi)存占用垮斯。

  • 子類使用:
checkPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, new OnPermissionResultListener() {
    @Override
    public void onAllow() {
        Log.d(TAG, "onAllow: ");
    }

    @Override
    public void onReject() {
        Log.d(TAG, "onReject: ");
    }
});

是不是很簡潔咧,理解原理后只祠,稍微封裝下就好兜蠕,也不用去引用什么第三方框架了。

6. 額外說一下抛寝,責(zé)任單一原則

大家都知道熊杨,寫android app的時候,基本上都有一個BaseActivity盗舰,但是有些童鞋會把很多邏輯都一起寫在BaseActivity這個類里晶府,感覺像大雜燴一樣,很不美觀钻趋,后期有問題了或者需要優(yōu)化了川陆,定位不好定位。

建議蛮位,遵循責(zé)任單一原則:
權(quán)限相關(guān)的邏輯就寫在PermissionBaseActivity里较沪。
Mvp相關(guān)的綁定邏輯,就寫在MvpBaseActivity里失仁。等等
最后再去處理繼承關(guān)系就好了尸曼。


覺得有用同學(xué),點贊鼓勵下唄陶因。 個人公眾號『Grade桂』骡苞,歡迎關(guān)注

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垂蜗,一起剝皮案震驚了整個濱河市楷扬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贴见,老刑警劉巖烘苹,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異片部,居然都是意外死亡镣衡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進(jìn)店門档悠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來廊鸥,“玉大人,你說我怎么就攤上這事辖所《杷担” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵缘回,是天一觀的道長吆视。 經(jīng)常有香客問我典挑,道長,這世上最難降的妖魔是什么啦吧? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任您觉,我火速辦了婚禮,結(jié)果婚禮上授滓,老公的妹妹穿的比我還像新娘琳水。我一直安慰自己,他們只是感情好般堆,可當(dāng)我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布炫刷。 她就那樣靜靜地躺著,像睡著了一般郁妈。 火紅的嫁衣襯著肌膚如雪浑玛。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天噩咪,我揣著相機與錄音顾彰,去河邊找鬼。 笑死胃碾,一個胖子當(dāng)著我的面吹牛涨享,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播仆百,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼厕隧,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了俄周?” 一聲冷哼從身側(cè)響起吁讨,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎峦朗,沒想到半個月后建丧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡波势,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年翎朱,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片尺铣。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡拴曲,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出凛忿,到底是詐尸還是另有隱情澈灼,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布侄非,位于F島的核電站蕉汪,受9級特大地震影響流译,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜者疤,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一福澡、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧驹马,春花似錦革砸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至泳姐,卻和暖如春效拭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背胖秒。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工缎患, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阎肝。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓挤渔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親风题。 傳聞我的和親對象是個殘疾皇子判导,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容