Android6.0權(quán)限適配

Code4Android .jpg

源碼傳送門

前言

現(xiàn)在談?wù)揂ndroid權(quán)限適配可能有點(diǎn)沒必要扶踊,因?yàn)榫W(wǎng)上關(guān)于權(quán)限適配的文章很多收津,搜一下Android6.0權(quán)限適配關(guān)鍵詞能搜到一堆文章歹颓,而且很多寫的還很不錯(cuò)铜秆。不過自己想了想還是總結(jié)一下奢米,因?yàn)槟切┪恼露际莿e人的抓韩,不是自己的,之前一直想總結(jié)一下鬓长,但是一直沒做谒拴,今天就簡單記錄一下,方便以后查閱涉波,也對Android6.0的權(quán)限機(jī)制再次進(jìn)行一次全面的認(rèn)識英上。
從Android M開始,用戶開始在應(yīng)用運(yùn)行時(shí)向其授予權(quán)限啤覆,而不是在應(yīng)用安裝時(shí)授予苍日。這樣更友好的讓用戶選擇,當(dāng)真正需要權(quán)限的時(shí)候再去申請權(quán)限窗声,而不是Android M之前在安裝時(shí)一下子去申請相恃。

正常權(quán)限

正常權(quán)限不會直接給用戶隱私權(quán)帶來風(fēng)險(xiǎn)。如果您的應(yīng)用在其清單中列出了正常權(quán)限笨觅,系統(tǒng)將自動授予該權(quán)限拦耐。而不需要我們?nèi)フ埱髾?quán)限见剩。

ACCESS_LOCATION_EXTRA_COMMANDS  
ACCESS_NETWORK_STATE
ACCESS_NOTIFICATION_POLICY
ACCESS_WIFI_STATE
BLUETOOTH
BLUETOOTH_ADMIN
BROADCAST_STICKY
CHANGE_NETWORK_STATE
CHANGE_WIFI_MULTICAST_STATE
CHANGE_WIFI_STATE
DISABLE_KEYGUARD
EXPAND_STATUS_BAR
GET_PACKAGE_SIZE
INSTALL_SHORTCUT
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
REQUEST_INSTALL_PACKAGES
SET_ALARM
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
UNINSTALL_SHORTCUT
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS

危險(xiǎn)權(quán)限

危險(xiǎn)權(quán)限涵蓋應(yīng)用需要涉及用戶隱私信息的數(shù)據(jù)或資源杀糯,或者可能對用戶存儲的數(shù)據(jù)或其他應(yīng)用的操作產(chǎn)生影響的區(qū)域固翰。例如,能夠讀取用戶的聯(lián)系人屬于危險(xiǎn)權(quán)限犯戏。如果應(yīng)用聲明其需要危險(xiǎn)權(quán)限种吸,則用戶必須明確向應(yīng)用授予該權(quán)限坚俗。

在危險(xiǎn)權(quán)限中,我們需要了解一個(gè)權(quán)限組的概念剧董,所有危險(xiǎn)的 Android 系統(tǒng)權(quán)限都屬于權(quán)限組,如果應(yīng)用請求其清單中列出的危險(xiǎn)權(quán)限,而應(yīng)用目前在權(quán)限組中沒有擁有任何權(quán)限,則系統(tǒng)會向用戶顯示一個(gè)對話框宠蚂,描述應(yīng)用要訪問的權(quán)限組。對話框不描述該組內(nèi)的具體權(quán)限呀癣。

例如我們需要讀取獲取手機(jī)卡imsi沼沈,此時(shí)需要請求權(quán)限READ_PHONE_STATE页衙,發(fā)現(xiàn)此時(shí)提示框也展示了請求打電話權(quán)限响巢。(系統(tǒng)只告訴用戶應(yīng)用需要的權(quán)限組伏穆,而不告知具體權(quán)限)其實(shí)READ_PHONE_STATE和打電話權(quán)限CALL_PHONE都屬于一個(gè)權(quán)限組PHONE烟瞧,如果我們此時(shí)允許了權(quán)限诗鸭,那么下次再其他地方使用了打電話權(quán)限時(shí)系統(tǒng)將立即授予該權(quán)限。

這里寫圖片描述

注:任何權(quán)限都可屬于一個(gè)權(quán)限組参滴,包括正常權(quán)限和應(yīng)用定義的權(quán)限强岸。但權(quán)限組僅當(dāng)權(quán)限危險(xiǎn)時(shí)才影響用戶體驗(yàn)±猓可以忽略正常權(quán)限的權(quán)限組蝌箍。

權(quán)限組 權(quán)限
CALENDAR READ_CALENDAR
WRITE_CALENDAR
CAMERA CAMERA
CONTACTS READ_CONTACTS
WRITE_CONTACTS
GET_ACCOUNTS
LOCATION ACCESS_FINE_LOCATION
ACCESS_COARSE_LOCATION
MICROPHONE RECORD_AUDIO
PHONE READ_PHONE_STATE
CALL_PHONE
READ_CALL_LOG
WRITE_CALL_LOG
ADD_VOICEMAIL
USE_SIP
PROCESS_OUTGOING_CALLS
SENSORS BODY_SENSORS
SMS SEND_SMS
RECEIVE_SMS
READ_SMS
RECEIVE_WAP_PUSH
RECEIVE_MMS

權(quán)限請求API

    /**
     * 確定權(quán)限是否已經(jīng)被授予
     * @param permission 被檢測權(quán)限的名字.
     * @return {@link android.content.pm.PackageManager#PERMISSION_GRANTED} 如果權(quán)限被授予,
     * {@link android.content.pm.PackageManager#PERMISSION_DENIED} 如果權(quán)限被拒絕返回值.
     */
    public static int checkSelfPermission(@NonNull Context context, @NonNull String permission) 



    /**
     * 是否顯示自定義UI提示用戶
     * 華為手機(jī)測試 第一次使用時(shí)返回false
     * 如果拒絕返回true
     * 如果拒絕并點(diǎn)擊不在提醒返回false
     * 已經(jīng)同意過權(quán)限青灼,但在設(shè)置拒絕此時(shí)返回true
     * 沒有同意過權(quán)限,在設(shè)置中開啟并拒絕權(quán)限返回false
     * @param activity   請求權(quán)限Activity.
     * @param permission 需要請求的權(quán)限.
     * @return 是否顯示自定義對話框提示用戶.
     */
    public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
                                                               @NonNull String permission)




    /**
     * 給應(yīng)用申請權(quán)限妓盲,申請的權(quán)限必須在manifest文件注冊杂拨,正常權(quán)限在安裝時(shí)自動被授權(quán),不需要使用此方法請求權(quán)限
     * 請求之后會彈出系統(tǒng)提示框悯衬,供我們選擇是拒絕還是允許弹沽,點(diǎn)擊后
     * android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult(
     * int, String[], int[])} 方法將會被回調(diào),
     * @param activity 請求權(quán)限的Activity.
     * @param permissions 需要請求的權(quán)限.
     * @param requestCode 指定一個(gè)請求碼甚亭,用于區(qū)別返回結(jié)果
     *
     */
    public static void requestPermissions(final @NonNull Activity activity,
                                          final @NonNull String[] permissions, final int requestCode)



    /**
     * 調(diào)用requestPermissions方法請求權(quán)限的回調(diào)
     *需要注意的是可能請求的權(quán)限與用戶互動中斷;正在這種情況下回調(diào)將接收一個(gè)空的permissions和grantResults數(shù)組
     * @param permissions 請求的權(quán)限. 不為null,長度可能為0.
     * @param grantResults 請求權(quán)限的結(jié)果PERMISSION_GRANTED表示權(quán)限被允許抠艾,PERMISSION_DENIED表示權(quán)限被拒絕
     */
    void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                    @NonNull int[] grantResults)

需要注意的是截驮,對于如果在Activity中請求權(quán)限則可使用上面API ActivityCompat類苔严,如果在Frament請求權(quán)限則每瞒,需要使用Fragment類中的對應(yīng)方法,否則回調(diào)會有問題暇唾。

簡單封裝

 /**
     * 判斷是否具備所有權(quán)限
     *
     * @param permissions 所有權(quán)限
     * @return true 具有所有權(quán)限  false沒有具有所有權(quán)限促脉,此時(shí)包含未授予的權(quán)限
     */
    public static boolean isHasPermissions(String... permissions) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
            return true;
        for (String permission : permissions) {
            if (!isHasPermission(permission))
                return false;
        }
        return true;
    }

    /**
     * 判斷該權(quán)限是否已經(jīng)被授予
     *
     * @param permission
     * @return true 已經(jīng)授予該權(quán)限 ,false未授予該權(quán)限
     */
    private static boolean isHasPermission(String permission) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
            return true;
        return ContextCompat.checkSelfPermission(MyApplication.getAppContext(), permission) == PackageManager.PERMISSION_GRANTED;
    }
    
    /**
     * 請求權(quán)限,經(jīng)測試發(fā)現(xiàn)TabActivity管理Activity時(shí)策州,在Activity中請求權(quán)限時(shí)需要傳入父Activity對象瘸味,即TabActivity對象
     * 并在TabActivity管理Activity中重寫onRequestPermissionsResult并分發(fā)到子Activity,否則回調(diào)不執(zhí)行  够挂。TabActivity回調(diào)中  調(diào)用getLocalActivityManager().getCurrentActivity().onRequestPermissionsResult(requestCode, permissions, grantResults);分發(fā)到子Activity

     * 
     *
     * @param object      Activity or Fragment
     * @param requestCode 請求碼
     * @param permissions 請求權(quán)限
     */
    public static void requestPermissions(Object object, int requestCode, String... permissions) {
        ArrayList<String> arrayList = new ArrayList<>();
        for (String permission : permissions) {
            if (!isHasPermissions(permission)) {
                arrayList.add(permission);
            }
        }
        if (arrayList.size() > 0) {
            if (object instanceof Activity) {
                Activity activity = (Activity) object;
                Activity activity1 = activity.getParent() != null && activity.getParent() instanceof TabActivity ? activity.getParent() : activity;
                ActivityCompat.requestPermissions(activity1, arrayList.toArray(new String[]{}), requestCode);
            } else if (object instanceof Fragment) {
                Fragment fragment = (Fragment) object;
                //當(dāng)Fragment嵌套Fragment時(shí)使用getParentFragment(),然后在父Fragment進(jìn)行分發(fā)旁仿,否則回調(diào)不執(zhí)行
                Fragment fragment1 = fragment.getParentFragment() != null ? fragment.getParentFragment() : fragment;
                fragment1.requestPermissions(arrayList.toArray(new String[]{}), requestCode);
            } else {
                throw new RuntimeException("the object must be Activity or Fragment");
            }
        }
    }

如果想展示自定義UI友好的提示用戶申請?jiān)摍?quán)限的原因,則需要使用shouldShowRequestPermissionRationale方法孽糖,簡要封裝如下

 public static boolean shouldShowRequestPermissionRationale(@NonNull Object object, String... permissions) {
        for (String permission : permissions) {
            if (object instanceof Activity) {
                if (ActivityCompat.shouldShowRequestPermissionRationale((Activity) object, permission)) {
                    return true;
                }
            } else if (object instanceof Fragment) {
                if(((Fragment) object).shouldShowRequestPermissionRationale(permission)){
                    return true;
                }
            } else {
                throw new RuntimeException("the object must be Activity or Fragment");
            }


        }
        return false;
    }

    /**
     * 二次申請權(quán)限時(shí)枯冈,彈出自定義提示對話框
     *
     * @param activity
     * @param message
     * @param iPermissionRequest
     * @see com.example.xh.ui.BaiduLocationFragment 可以查看該類onRequestPermissionsResult方法當(dāng)選擇永不提醒時(shí)的處理辦法。
     */
    public static void showDialog(Activity activity, String message, final IPermissionRequest iPermissionRequest) {
        new AlertDialog.Builder(activity)
                .setPositiveButton("允許", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(@NonNull DialogInterface dialog, int which) {
                        iPermissionRequest.agree();
                        dialog.dismiss();
                    }
                })
                .setNegativeButton("拒絕", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(@NonNull DialogInterface dialog, int which) {
                        iPermissionRequest.refuse();
                        dialog.dismiss();
                    }
                })
                .setCancelable(false)
                .setMessage(message)
                .show();
    }

彈出對話框后办悟,點(diǎn)擊了拒絕或者允許后尘奏,給一個(gè)回調(diào),方便進(jìn)行不同的處理病蛉,當(dāng)然如果統(tǒng)一處理的話炫加,就不需要寫接口,直接在上述點(diǎn)擊允許的時(shí)候請求權(quán)限铺然,點(diǎn)擊不允許的時(shí)候俗孝,顯示一個(gè)Toast再次做下權(quán)限拒絕提示。當(dāng)然也可在onRequestPermissionsResult中進(jìn)行判斷魄健,當(dāng)選中永不提醒后給用戶一個(gè)友好跳轉(zhuǎn)到權(quán)限設(shè)置界面驹针。
接口方法

public interface IPermissionRequest {
    void agree();
    void refuse();
}

特殊權(quán)限

有許多權(quán)限其行為方式與正常權(quán)限及危險(xiǎn)權(quán)限都不同。SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 特別敏感诀艰,因此大多數(shù)應(yīng)用不應(yīng)該使用它們柬甥。如果某應(yīng)用需要其中一種權(quán)限,必須在清單中聲明該權(quán)限其垄,并且發(fā)送請求用戶授權(quán)的 intent(注意特殊權(quán)限和危險(xiǎn)權(quán)限請求方式不一樣)苛蒲。系統(tǒng)將向用戶顯示詳細(xì)管理屏幕,以響應(yīng)該 intent绿满。

請求WRITE_SETTINGS權(quán)限

  /**
     * 測試請求WRITE_SETTINGS權(quán)限
     */
    @OnClick(R.id.request_write_setting)
    @TargetApi(android.os.Build.VERSION_CODES.M)
    public void requestWriteSetting() {
        if (!Settings.System.canWrite(this)) {
            Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, requestCodeWriteSetting);
        } else {
            Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 已經(jīng)被授權(quán)", Toast.LENGTH_SHORT).show();
        }
    }
    
    
    @TargetApi(Build.VERSION_CODES.M)
    private void showToast() {
        if (Settings.System.canWrite(this)) {
            //檢查返回結(jié)果
            Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 被授權(quán)", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 沒有被授權(quán)", Toast.LENGTH_SHORT).show();
        }
    }

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (requestCode == requestCodeWriteSetting) {
            showToast();
        }else if(requestCode==requestCodeAlertWindow){
            showToastAlerterWindow();
        }
    }

請求SYSTEM_ALERT_WINDOW權(quán)限

 /**
     * 測試請求SYSTEM_ALERT_WINDOW權(quán)限
     */
    @OnClick(R.id.request_alert_window)
    @TargetApi(android.os.Build.VERSION_CODES.M)
    public void requestAlertWindow() {
        if (!Settings.canDrawOverlays(this)) {
            Intent intent = new Intent(Settings. ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, requestCodeAlertWindow);
        } else {
            Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 已經(jīng)被授權(quán)", Toast.LENGTH_SHORT).show();
        }
    }
 @TargetApi(Build.VERSION_CODES.M)
    private void showToastAlerterWindow() {
        if (Settings.System.canWrite(this)) {
            //檢查返回結(jié)果
            Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 被授權(quán)", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 沒有被授權(quán)", Toast.LENGTH_SHORT).show();
        }
    }
WRITE_SETTINGS權(quán)限設(shè)置界面
SYSTEM_ALERT_WINDOW權(quán)限設(shè)置界面

注意:權(quán)限必須在清單文件中聲明臂外,否則進(jìn)入上面界面時(shí)開關(guān)是不可點(diǎn)擊的灰色。

打開權(quán)限設(shè)置界面

在上面危險(xiǎn)權(quán)限申請中喇颁,如果用戶拒絕了權(quán)限漏健,并且選中永不提醒,那么下次請求權(quán)限時(shí)直接執(zhí)行onRequestPermissionsResult回調(diào)橘霎,并且返回狀態(tài)是權(quán)限被拒絕狀態(tài)蔫浆,那么若想授予權(quán)限,必須去手機(jī)的權(quán)限管理中設(shè)置姐叁,如果用戶去手機(jī)里找是不是很麻煩瓦盛,況且一步人不知道設(shè)置權(quán)限的地方在哪,那么為了程序的體驗(yàn)更好外潜,我們可以在我們的應(yīng)用中引導(dǎo)用戶跳轉(zhuǎn)到設(shè)置權(quán)限的界面原环。實(shí)現(xiàn)代碼如下

    /**
     * 打開應(yīng)用權(quán)限設(shè)置界面
     */
    @OnClick(R.id.open_permission_setting)
    public void requestOpenPermissionSetting() {
        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
        // Uri uri = Uri.fromParts("package", getPackageName(), null);
        Uri uri1=Uri.parse("package:" + getPackageName());
        intent.setData(uri1);
        startActivity(intent);
    }

介紹到此就結(jié)束了,水平有限若有問題請指出处窥,Hava a wonderful day.

最后放幾篇感覺不錯(cuò)的文章:

Android6.0權(quán)限適配之WRITE_EXTERNAL_STORAGE(SD卡寫入)

谷歌文檔 在運(yùn)行時(shí)請求權(quán)限

谷歌文檔 系統(tǒng)權(quán)限

Android權(quán)限機(jī)制與適配經(jīng)驗(yàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末嘱吗,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子滔驾,更是在濱河造成了極大的恐慌谒麦,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嵌灰,死亡現(xiàn)場離奇詭異弄匕,居然都是意外死亡沽瞭,警方通過查閱死者的電腦和手機(jī)迁匠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門驹溃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人豌鹤,你說我怎么就攤上這事亡哄。” “怎么了布疙?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵愿卸,是天一觀的道長截型。 經(jīng)常有香客問我趴荸,道長,這世上最難降的妖魔是什么宦焦? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任发钝,我火速辦了婚禮波闹,結(jié)果婚禮上酝豪,老公的妹妹穿的比我還像新娘精堕。我一直安慰自己,他們只是感情好锄码,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痛悯,像睡著了一般。 火紅的嫁衣襯著肌膚如雪重窟。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天扭仁,我揣著相機(jī)與錄音厅翔,去河邊找鬼乖坠。 笑死刀闷,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的甸昏。 我是一名探鬼主播,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼施蜜,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了缸沃?” 一聲冷哼從身側(cè)響起恰起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤和泌,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后武氓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體仇箱,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年忠烛,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了权逗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片美尸。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斟薇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出堪滨,到底是詐尸還是另有隱情,我是刑警寧澤袱箱,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站发笔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏了讨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一量蕊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧残炮,春花似錦、人聲如沸泉瞻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鞭达,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間畴蹭,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工叨襟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人糊闽。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像提澎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子虱朵,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

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