「Android6.0權限適配| 掘金技術徵文 」

源碼傳送門

前言

如今談論Android權限適配可能有點不必,由於網上關於權限適配的文章不少,搜一下Android6.0權限適配關鍵詞能搜到一堆文章,並且不少寫的還很不錯。不過本身想了想仍是總結一下,由於那些文章都是別人的,不是本身的,以前一直想總結一下,可是一直沒作,今天就簡單記錄一下,方便之後查閱,也對Android6.0的權限機制再次進行一次全面的認識。
從Android M開始,用戶開始在應用運行時向其授予權限,而不是在應用安裝時授予。這樣更友好的讓用戶選擇,當真正須要權限的時候再去申請權限,而不是Android M以前在安裝時一會兒去申請。javascript

正常權限

正常權限不會直接給用戶隱私權帶來風險。若是您的應用在其清單中列出了正常權限,系統將自動授予該權限。而不須要咱們去請求權限。html

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複製代碼

危險權限

危險權限涵蓋應用須要涉及用戶隱私信息的數據或資源,或者可能對用戶存儲的數據或其餘應用的操做產生影響的區域。例如,可以讀取用戶的聯繫人屬於危險權限。若是應用聲明其須要危險權限,則用戶必須明確嚮應用授予該權限。java

在危險權限中,咱們須要瞭解一個權限組的概念,全部危險的 Android 系統權限都屬於權限組,若是應用請求其清單中列出的危險權限,而應用目前在權限組中沒有擁有任何權限,則系統會向用戶顯示一個對話框,描述應用要訪問的權限組。對話框不描述該組內的具體權限。android

例如咱們須要讀取獲取手機卡imsi,此時須要請求權限READ_PHONE_STATE,發現此時提示框也展現了請求打電話權限。(系統只告訴用戶應用須要的權限組,而不告知具體權限)其實READ_PHONE_STATE和打電話權限CALL_PHONE都屬於一個權限組PHONE,若是咱們此時容許了權限,那麼下次再其餘地方使用了打電話權限時系統將當即授予該權限。
git

這裏寫圖片描述

注:任何權限均可屬於一個權限組,包括正常權限和應用定義的權限。但權限組僅當權限危險時才影響用戶體驗。能夠忽略正常權限的權限組。

權限組 權限
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

權限請求API

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



    /** * 是否顯示自定義UI提示用戶 * 華爲手機測試 第一次使用時返回false * 若是拒絕返回true * 若是拒絕並點擊不在提醒返回false * 已經贊成過權限,但在設置拒絕此時返回true * 沒有贊成過權限,在設置中開啓並拒絕權限返回false * @param activity 請求權限Activity. * @param permission 須要請求的權限. * @return 是否顯示自定義對話框提示用戶. */
    public static boolean shouldShowRequestPermissionRationale(@NonNull Activity activity,
                                                               @NonNull String permission)




    /** * 給應用申請權限,申請的權限必須在manifest文件註冊,正常權限在安裝時自動被受權,不須要使用此方法請求權限 * 請求以後會彈出系統提示框,供咱們選擇是拒絕仍是容許,點擊後 * android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback#onRequestPermissionsResult( * int, String[], int[])} 方法將會被回調, * @param activity 請求權限的Activity. * @param permissions 須要請求的權限. * @param requestCode 指定一個請求碼,用於區別返回結果 * */
    public static void requestPermissions(final @NonNull Activity activity,
                                          final @NonNull String[] permissions, final int requestCode)



    /** * 調用requestPermissions方法請求權限的回調 *須要注意的是可能請求的權限與用戶互動中斷;正在這種狀況下回調將接收一個空的permissions和grantResults數組 * @param permissions 請求的權限. 不爲null,長度可能爲0. * @param grantResults 請求權限的結果PERMISSION_GRANTED表示權限被容許,PERMISSION_DENIED表示權限被拒絕 */
    void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                    @NonNull int[] grantResults)複製代碼

須要注意的是,對於若是在Activity中請求權限則可以使用上面API ActivityCompat類,若是在Frament請求權限則,須要使用Fragment類中的對應方法,不然回調會有問題。github

簡單封裝

/** * 判斷是否具有全部權限 * * @param permissions 全部權限 * @return true 具備全部權限 false沒有具備全部權限,此時包含未授予的權限 */
    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;
    }

    /** * 判斷該權限是否已經被授予 * * @param permission * @return true 已經授予該權限 ,false未授予該權限 */
    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;
    }

    /** * 請求權限,經測試發現TabActivity管理Activity時,在Activity中請求權限時須要傳入父Activity對象,即TabActivity對象 * 並在TabActivity管理Activity中重寫onRequestPermissionsResult並分發到子Activity,不然回調不執行 。TabActivity回調中 調用getLocalActivityManager().getCurrentActivity().onRequestPermissionsResult(requestCode, permissions, grantResults);分發到子Activity * * * @param object Activity or Fragment * @param requestCode 請求碼 * @param permissions 請求權限 */
    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;
                //當Fragment嵌套Fragment時使用getParentFragment(),而後在父Fragment進行分發,不然回調不執行
                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友好的提示用戶申請該權限的緣由,則須要使用shouldShowRequestPermissionRationale方法,簡要封裝以下api

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;
    }

    /** * 二次申請權限時,彈出自定義提示對話框 * * @param activity * @param message * @param iPermissionRequest * @see com.example.xh.ui.BaiduLocationFragment 能夠查看該類onRequestPermissionsResult方法當選擇永不提醒時的處理辦法。 */
    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();
    }複製代碼

彈出對話框後,點擊了拒絕或者容許後,給一個回調,方便進行不一樣的處理,固然若是統一處理的話,就不須要寫接口,直接在上述點擊容許的時候請求權限,點擊不容許的時候,顯示一個Toast再次作下權限拒絕提示。固然也可在onRequestPermissionsResult中進行判斷,當選中永不提醒後給用戶一個友好跳轉到權限設置界面。
接口方法數組

public interface IPermissionRequest {
    void agree();
    void refuse();
}複製代碼

特殊權限

有許多權限其行爲方式與正常權限及危險權限都不一樣。SYSTEM_ALERT_WINDOW 和 WRITE_SETTINGS 特別敏感,所以大多數應用不該該使用它們。若是某應用須要其中一種權限,必須在清單中聲明該權限,而且發送請求用戶受權的 intent(注意特殊權限和危險權限請求方式不同)。系統將向用戶顯示詳細管理屏幕,以響應該 intent。app

請求WRITE_SETTINGS權限

/** * 測試請求WRITE_SETTINGS權限 */
    @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 已經被受權", Toast.LENGTH_SHORT).show();
        }
    }


    @TargetApi(Build.VERSION_CODES.M)
    private void showToast() {
        if (Settings.System.canWrite(this)) {
            //檢查返回結果
            Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 被受權", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(PermissionTestActivity.this, "WRITE_SETTINGS 沒有被受權", 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權限

/** * 測試請求SYSTEM_ALERT_WINDOW權限 */
    @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 已經被受權", Toast.LENGTH_SHORT).show();
        }
    }
 @TargetApi(Build.VERSION_CODES.M)
    private void showToastAlerterWindow() {
        if (Settings.System.canWrite(this)) {
            //檢查返回結果
            Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 被受權", Toast.LENGTH_SHORT).show();
        } else {
            Toast.makeText(PermissionTestActivity.this, "SYSTEM_ALERT_WINDOW 沒有被受權", Toast.LENGTH_SHORT).show();
        }
    }複製代碼

WRITE_SETTINGS權限設置界面

SYSTEM_ALERT_WINDOW權限設置界面

注意:權限必須在清單文件中聲明,不然進入上面界面時開關是不可點擊的灰色。ide

打開權限設置界面

在上面危險權限申請中,若是用戶拒絕了權限,而且選中永不提醒,那麼下次請求權限時直接執行onRequestPermissionsResult回調,而且返回狀態是權限被拒絕狀態,那麼若想授予權限,必須去手機的權限管理中設置,若是用戶去手機裏找是否是很麻煩,何況一步人不知道設置權限的地方在哪,那麼爲了程序的體驗更好,咱們能夠在咱們的應用中引導用戶跳轉到設置權限的界面。實現代碼以下

/** * 打開應用權限設置界面 */
    @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);
    }複製代碼

介紹到此就結束了,水平有限如有問題請指出,Hava a wonderful day.

最後放幾篇感受不錯的文章:

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

谷歌文檔 在運行時請求權限

谷歌文檔 系統權限

Android權限機制與適配經驗

本次徵文活動的連接: juejin.im/post/58d8e9…

相關文章
相關標籤/搜索