權限-動態申請的權限


申明:低級碼農問題解決中的參考和解決后的小結,僅用於個人記錄,能力有限,可能有些錯誤,缺陷不自知,歡迎在評論中指正,謝謝!

從Android 6.0(M版本,api 23)開始,要求動態申請權限。Google將權限分為兩類,一類是普通權限,使用時不需要用戶授權;另一類是危險權限,使用時需要判斷用戶是否對該權限授權。如果沒有授權,需要動態申請。在沒有用戶授權的情況下,直接使用該權限,系統將會拋出異常。我們一般說的應用需要某權限,實際是指用戶是否同意應用使用該權限。

  危險權限有25種,按照權限組可划分如下,:

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        GPS定位
  permission:android.permission.ACCESS_COARSE_LOCATION        wifi,基站定位

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

  如果應用申請某一種權限,得到用戶同意后,那么同組的其他權限,還需要重新申請並同意。拒絕了,也不印象對其他權限的同意。(在meizu X8驗證)

  使用權限,需要在AndroidManifest中注冊。如果沒有注冊,那么判斷權限的方法將返回DENIED,如果去申請該項權限,也不會有權限對話框彈出。

 

常用的方法

  /** 判斷是否具有某一項權限 */

  int checkSelfPermission(String permission)

  /** 申請多個權限 */

  void requestPermissions(@NonNull String[] permissions, int requestCode)

  /** 返回申請的權限的授權情況 */

  void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)

  申請和返回授權中的參數requestCode是對應的,這是用來區分權限申請的場景的。一般情況下,一個Activity只有一種申請單一權限的場景,但也有一個Activity有多個申請單一權限的場景。比如Activity具有多個Fragment,B、C都需要定位權限,在Fragment中檢查和申請權限更好一些,但只能有Activity接收授權情況。所以B、C申請時,傳入不同的requestCode,返回授權結果方法時,可以根據requestCode判斷,是哪個Fragment申請的權限。還有BrowserActivity,不同網頁申請同意權限的情況更多了,如果某個網頁需要特殊處理,也是用requestCode區分。

    protected static boolean checkRequestMultiPermissions(Activity activity, String... permissions) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            ArrayList<String> deniedPermissions = new ArrayList<>();
            // 過濾出需要但是沒有得到授權的權限,放入deniedPermissions
            for(String permission: permissions) {
                int permissionState = activity.checkSelfPermission(permission);
                if (permissionState == PackageManager.PERMISSION_DENIED) {
                    deniedPermissions.add(permission);
                }
            }
            int deniedPermissionCount = deniedPermissions.size();
            if (deniedPermissionCount == 0) {
                return true;
            } else {
                String[] deniedPermissionArray =  deniedPermissions.toArray(new String[0]);
                int deniedPermissionCode = 0;
                for (String permission: deniedPermissionArray) {
                    deniedPermissionCode |= permission2CodeMap.get(permission).intValue();
                }
                activity.requestPermissions(deniedPermissionArray, deniedPermissionCode);
                return false;
            }
        } else {
            return true;
        }
    }

    /** 這個方法運行與Activity中,用戶接收授權情況 */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        ArrayList<String> deniedPermissions = new ArrayList<>();
        for (int i = 0, size = permissions.length; i < size; i++) {
            if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
                deniedPermissions.add(permissions[i]);
            }
        }
        if (deniedPermissions.size() == 0) {
            // 所有權限都被同意
        } else {
            // deniedPermissions是申請但是不被授權的權限
        }
        return;
    } 

個人心得

  關於requestCode,以我在實際項目中遇到的,會定義很多魔鬼數字權限碼,不便於項目的閱讀和維護。比如兩個Activity都需要定位權限,兩個Activity各自定義了權限碼,還是不同的數字;然后就是同一個頁面需要多個權限(比如應用啟動需要存儲、IMEI、傳感器等)的組合,可能產生很多種requestCode,多人維護的項目不敢亂動代碼,導致在不同Activity可以復用的requestCode被定義了多個。所有我有一個方案,用於優化無意義,無限制的定義requestCode,雖不能根本解決,但可以極大程度的緩解。這個方案是為每個權限(指需要動態申請的危險權限,后同)申明一個對應的數字,通過做位運算組合成多種權限的場景,這就要求數字必須是2的n次方形式,即0b10,0b100,0b1000的形式。然后申請權限時,只需要傳入權限字符串即可,會生成對應的權限碼。

優勢,不足和對不足的規避方案。

1、requestCode是int型,取值范圍是-2^32 ~ 2^32 - 1,除去負數,然后0表示沒有權限,則實際可用的只有30位,而危險權限有25種,不排除后續繼續增加,所以用來表示的場景有2^5。因為場景不可能出現組合的情況,所以增加方式是+1,而不是位移(<)。

——適用於我自己的小項目,2^5種場景基本夠用了。再就是diy的小項目肯定不會用到全部的權限,給16位到24位就滿足絕大多數場景了。

 2、針對Activity定義場景,在onRequestPermissionsResult方法中,盡量走默認場景的統一處理:權限被禁則彈框。如果有特殊處理,就要先判斷權限,后判斷場景,但這個過程跟Activity強相關,即只有極少數Activity有這樣的場景。

  下面是我場景的代碼,這是Utils類:

public class PermissionUtils {
    /** 權限組下有多個權限,一個權限被獲取了,則改組的所有權限都獲取。有25個危險權限,我們定義24個,忽略一個,留個小坑 */
    public static final int PERMISSION_CODE_CALENDAR = 0b1;
    public static final int PERMISSION_CODE_CAMERA = PERMISSION_CODE_CALENDAR << 1;
    public static final int PERMISSION_CODE_CONTACTS = PERMISSION_CODE_CAMERA << 1;
    public static final int PERMISSION_CODE_LOCATION = PERMISSION_CODE_CONTACTS << 1;
    public static final int PERMISSION_CODE_MICROPHONE = PERMISSION_CODE_LOCATION << 1;
    public static final int PERMISSION_CODE_READ_PHONE_STATE = PERMISSION_CODE_MICROPHONE << 1;
    public static final int PERMISSION_CODE_CALL_PHONE = PERMISSION_CODE_READ_PHONE_STATE << 1;
    public static final int PERMISSION_CODE_READ_CALL_LOG = PERMISSION_CODE_CALL_PHONE << 1;
    public static final int PERMISSION_CODE_SENSORS = PERMISSION_CODE_READ_CALL_LOG << 1;
    public static final int PERMISSION_CODE_SMS = PERMISSION_CODE_SENSORS << 1;
    public static final int PERMISSION_CODE_STORAGE = PERMISSION_CODE_SMS << 1;
    /** 有25個危險權限,我們定義24個,忽略一個,留個小坑 */
    public static final int PERMISSION_CODE_ALL = (1<<24) - 1; // 24個1

    private static final Map<String, Integer> permission2CodeMap = new HashMap<>();
    private static final Map<String, String> permission2zhMap = new HashMap<>();

    static {
        permission2CodeMap.put(Manifest.permission.READ_CALENDAR, PERMISSION_CODE_CALENDAR);
        permission2CodeMap.put(Manifest.permission.READ_PHONE_STATE, PERMISSION_CODE_READ_PHONE_STATE);
        permission2CodeMap.put(Manifest.permission.READ_CALL_LOG, PERMISSION_CODE_CALL_PHONE);
        permission2CodeMap.put(Manifest.permission.READ_EXTERNAL_STORAGE, PERMISSION_CODE_STORAGE);
        permission2CodeMap.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, PERMISSION_CODE_STORAGE);

        permission2zhMap.put(Manifest.permission.READ_CALENDAR, App.getAppContext().getString(R.string.permission_read_calendar));
        permission2zhMap.put(Manifest.permission.READ_PHONE_STATE, App.getAppContext().getString(R.string.permission_read_phone_state));
        permission2zhMap.put(Manifest.permission.READ_CALL_LOG, App.getAppContext().getString(R.string.permission_read_call_log));
        permission2zhMap.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, App.getAppContext().getString(R.string.permission_write_sdcard));
    }

    protected static int getPermissionCode(String permission) {
        return permission2CodeMap.get(permission);
    }

    protected static String getPermissionZh(String permission) {
        return permission2zhMap.get(permission);
    }

    protected static @NonNull String[] getPermissionsZh(String... permissions) {
        int size = permissions.length;
        if (size <= 0) {
            return null;
        }
        String[] permissionsZh = new String[size];
        for (int i = 0; i < size; i++) {
            permissionsZh[i] = permission2zhMap.get(i);
        }
        return permissionsZh;
    }

    /**
     * 檢查是否有權限,如果沒有,則申請。沒有考慮場景
     * @param activity
     * @param permission
     * @return
     */
    protected static boolean checkRequestSinglePermission(Activity activity, String permission) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            int permissionState = activity.checkSelfPermission(permission);
            if (permissionState == PackageManager.PERMISSION_GRANTED) {
                return true;
            } else {
                activity.requestPermissions(new String[]{permission}, permission2CodeMap.get(permission));
                return false;
            }
        } else {
            return true;
        }
    }

    protected static boolean checkRequestMultiPermissions(Activity activity, String... permissions) {
        return checkRequestMultiPermissions(activity, 0, permissions);
    }

    /**
     * @param scene     場景
     */
    protected static boolean checkRequestMultiPermissions(Activity activity, int scene, String... permissions) {
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
            ArrayList<String> deniedPermissions = new ArrayList<>();
            for(String permission: permissions) {
                int permissionState = activity.checkSelfPermission(permission);
                if (permissionState == PackageManager.PERMISSION_DENIED) {
                    deniedPermissions.add(permission);
                }
            }
            int deniedPermissionCount = deniedPermissions.size();
            if (deniedPermissionCount == 0) {
                return true;
            } else {
                String[] deniedPermissionArray =  deniedPermissions.toArray(new String[0]);
                int deniedPermissionCode = 0;
                for (String permission: deniedPermissionArray) {
                    deniedPermissionCode |= permission2CodeMap.get(permission).intValue();
                }
                activity.requestPermissions(deniedPermissionArray, deniedPermissionCode | scene/*拼接場景和權限碼*/);
                return false;
            }
        } else {
            return true;
        }
    }

    public static final int SCENE_FLAG = 0b1000000000000000000000000; // 2^24,requestCode大於這個值,說明帶場景了
    public static final int SCENE_MAX = 0b11111111000000000000000000000000;

    public static boolean isDefaultScene(int requestCode) {
        return requestCode < SCENE_FLAG;
    }

    public static int getScene(int requestCode) {
        return requestCode & SCENE_MAX;
    }

    public static int getPermissionCodeIgnoreScene(int requestCode) {
        return requestCode & PERMISSION_CODE_ALL;
    }
}
View Code

接收處的處理,從requestCode解析出場景和權限

    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        if (PermissionUtils.isDefaultScene(requestCode)) {
            /* 默認場景 */
            ArrayList<String> deniedPermissions = new ArrayList<>();
            for (int i = 0, size = permissions.length; i < size; i++) {
                if (grantResults[i] == PackageManager.PERMISSION_DENIED) {
                    deniedPermissions.add(permissions[i]);
                }
            }
            if (deniedPermissions.size() == 0) {
                startActivity(new Intent(this, MainActivity.class));
            } else {
                showPermissionDialog(deniedPermissions);
            }
            return;
        } else {
            /* 自定義的特殊場景,解析出場景和權限 */
            int permissionCode = PermissionUtils.getPermissionCodeIgnoreScene(requestCode);
            int scene = PermissionUtils.getScene(requestCode);
        }
    }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM