1. 程式人生 > >Android 6.0及以上系統動態申請許可權詳解

Android 6.0及以上系統動態申請許可權詳解

1.Android 許可權簡介

自從Android6.0釋出以來,在許可權上做出了很大的變動,不再是之前的只要在manifest設定就可以任意獲取許可權,而是更加的注重使用者的隱私和體驗,不會再強迫使用者因拒絕不該擁有的許可權而導致的無法安裝的事情,也不會再不徵求使用者授權的情況下,就可以任意的訪問使用者隱私,而且即使在授權之後也可以及時的更改許可權。這就是6.0版本做出的更擁護和注重使用者的一大體現。

2.Android 許可權級別

Android6.0系統把許可權分為兩個級別:

一類是Normal Permissions,即普通許可權,這類許可權不會潛藏有侵害使用者隱私和安全的問題,比如,訪問網路的許可權,訪問WIFI的許可權等。

一類是Dangerous Permissions,即危險許可權,這類許可權會直接的威脅到使用者的安全和隱私問題,比如說訪問簡訊,相簿等許可權,地理位置許可權。

2.1.Normal Permissions (普通許可權 舉例)

  • 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

使用以上許可權是不會威脅到使用者安全的,所以這類許可權是可以直接的在manifest裡面直接的使用,而且在安裝後也會直接的生效了。

2.2.Dangerous Permissions (危險許可權 舉例)

  • SMS(簡訊)
    • SEND_SMS
    • RECEIVE_SMS
    • READ_SMS
    • RECEIVE_WAP_PUSH
    • RECEIVE_MMS
  • STORAGE(儲存卡)
    • READ_EXTERNAL_STORAGE
    • WRITE_EXTERNAL_STORAGE
  • CONTACTS(聯絡人)
    • READ_CONTACTS
    • WRITE_CONTACTS
    • GET_ACCOUNTS
  • PHONE(手機)
    • READ_PHONE_STATE
    • CALL_PHONE
    • READ_CALL_LOG
    • WRITE_CALL_LOG
    • ADD_VOICEMAIL
    • USE_SIP
    • PROCESS_OUTGOING_CALLS
  • CALENDAR(日曆)
    • READ_CALENDAR
    • WRITE_CALENDAR
  • CAMERA(相機)
    • CAMERA
  • LOCATION(位置)
    • ACCESS_FINE_LOCATION
    • ACCESS_COARSE_LOCATION
  • SENSORS(感測器)
    • BODY_SENSORS
  • MICROPHONE(麥克風)
    • 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:android.permission-group.STORAGE
permission:android.permission.READ_EXTERNAL_STORAGE
permission:android.permission.WRITE_EXTERNAL_STORAGE
  

聯絡人

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.LOCATION
permission:android.permission.ACCESS_FINE_LOCATION
permission:android.permission.ACCESS_COARSE_LOCATION  



感測器
group:android.permission-group.SENSORS
permission:android.permission.BODY_SENSORS
  
  

 

麥克風
group:android.permission-group.MICROPHONE
permission:android.permission.RECORD_AUDIO

危險許可權包含:感測器、日曆、攝像頭、通訊錄、地理位置、麥克風、電話、簡訊、儲存空間,具體見下圖:


  

危險許可權和普通許可權也有區別,普通許可權是單條的許可權,而危險許可權是以組展示的,也就是說,當你接受一個危險許可權時,不但但接受的是介面上展示的這一個許可權,而是它所在這個組裡面的其他所有訪問許可權也將會被自動獲取許可權,比如,一旦WRITE_CONTACTS被授權了,App也有READ_CONTACTS和GET_ACCOUNTS的許可權了。
值得注意的是,這類許可權也是需要在manifest中註冊的。

注意:同一組的任何一個許可權被授權了,其他許可權也自動被授權。例如,一旦WRITE_CONTACTS被授權了,app也有READ_CONTACTS和GET_ACCOUNTS了。


3.舉例說明

3.1.獲取當前裝置手機聯絡人

Android 6.0以下

測試裝置

清單檔案

java 程式碼

 /**
         * 獲取當前裝置聯絡人資訊
         * 1.從raw_contacts中讀取聯絡人的id("contact_id")
         * 2.根據contact_id從data表中查詢出相應的電話號碼和聯絡人名稱
         * 3.根據mimetype來區分哪個是聯絡人,哪個是電話號碼
         * */

        private ArrayList<HashMap<String, String>> readContact () {
            Uri rawContactsUri = Uri.parse("content://com.android.contacts/raw_contacts");
            Uri dataUri = Uri.parse("content://com.android.contacts/data");
            ArrayList<HashMap<String, String>> list = new ArrayList<HashMap<String, String>>();
            //從raw_contacts中讀取聯絡人的id("contact_id")
            Cursor rawContactsCursor = getContentResolver().query(rawContactsUri, new String[]{"contact_id"}, null, null, null);
            if (rawContactsCursor != null) {
                while (rawContactsCursor.moveToNext()) {
                    String contactId = rawContactsCursor.getString(0);
//根據contact_id從data表中查詢出相應的電話號碼和聯絡人名稱, 實際上查詢的是檢視view_data
                    Cursor dataCursor = getContentResolver().query(dataUri,
                            new String[]{"data1", "mimetype"}, "contact_id=?",
                            new String[]{contactId}, null);
                    if (dataCursor != null) {
                        HashMap<String, String> map = new HashMap<String, String>();
                        while (dataCursor.moveToNext()) {
                            String data1 = dataCursor.getString(0);
                            String mimetype = dataCursor.getString(1);
                            if ("vnd.android.cursor.item/phone_v2".equals(mimetype)) {
                                map.put("phone", data1);
                            } else if ("vnd.android.cursor.item/name".equals(mimetype)) {
                                map.put("name", data1);
                            }
                        }
                        list.add(map);
                        dataCursor.close();
                    }
                }
                rawContactsCursor.close();
            }
            return list;
        }


        button = (Button) findViewById(R.id.permissions_btn);
        button.setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                ArrayList<HashMap<String, String>> list = readContact();
                int count = list.size();
                StringBuilder sb = new StringBuilder();
                sb.append("當前裝置聯絡人數:" + count + "\n\n\n");
                for (int i = 0; i < count; i++) {
                    HashMap<String, String> map = list.get(i);
                    String name = map.get("name");
                    String phone = map.get("phone");
                    sb.append("姓名:" + name + "     手機號:" + phone + "\n\n\n");
                }
                String result = sb.toString();
                tv.setText(result);
            }
        });

結果

當清單檔案中

即目標版本號是6.0以上(23為6.0)時 結果還是如上圖(測試裝置一樣)

總結

當執行app的當前裝置Android系統版本<23時,不管我們的targetSdkVersion 值是否大於23,都不會影響我們在manifest裡面申請的許可權。即在清單檔案中配置了相應的許可權就可以使用。

由上面的幾條結論,我們應該很清晰的知道了訪問許可權在真機中的使用狀況,但是我們的手機在升級,版本也會越來越高,因此我們現在的應用不可能一直只支援低版本的使用也不考慮兼顧高版本。所以現在APP許可權升級是必然的趨勢。那麼現在回來解決上面遺留的問題,當真機和目標版本都大於6.0時出現的許可權異常我們該怎麼解決呢?

主要分為三個步驟:

1:檢查是否擁有許可權

2:假如沒有許可權,則申請許可權

3:處理許可權回撥

檢查是否擁有許可權

檢查是否已擁有了許可權,可以使用ContextCompat.checkSelfPermission(Context context, String permission);

checkSelfPermission方法中有兩個引數,分別是上下文,以及所申請的許可權。

申請許可權

申請許可權則是使用:

public static void requestPermissions(final Activity activity,final String[] permissions, final int requestCode) {}

requestPermissions方法中需要三個引數,當前的activity,所申請的許可權,可以是多個,最後就是請求碼,既然有請求碼說明它會有一個回撥,也就是我們下面要講的處理回撥。

處理許可權回撥

處理許可權回撥,需要在Activity中重寫onRequestPermissionsResult方法:

注意:

要用23 版本以上的V4包。在23以下版本的V4包中並沒有這幾個方法。

  • ActivityCompat.checkSelfPermission()
  • ActivityCompat.requestPermissions()
  • ActivityCompat.OnRequestPermissionsResultCallback
  • ActivityCompat.shouldShowRequestPermissionRationale()

3.2.Android 6.0及以上相機儲存位置許可權使用

1.清單檔案

<!-- 相機許可權 -->
<uses-permission android:name="android.permission.CAMERA" />

<!-- 儲存卡 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

<!-- 允許應用訪問手機狀態 -->
<uses-permission android:name="android.permission.READ_PHONE_STATE" />

2.常量類

 /**
  * Android 6.0及以上申請許可權
* */

public static final String permission1="android.permission.READ_EXTERNAL_STORAGE";
public static final String permission2="android.permission.WRITE_EXTERNAL_STORAGE";
public static final String permission3="android.permission.CAMERA";
public static final String [] permission=new String[]{permission1,permission2,permission3};//敏感許可權

3.工具類

/**
 * Created by wjn on 2017/11/28.
 * Android 6.0及以上許可權工具類
 */

public class AndroidPermissionUtils {

    /**
     * Android 6.0及以上 檢測是否具有某些許可權
     * */

    public static boolean hasAndroidPermission(Context context, String [] permission){
        boolean has=true;
        for(String per:permission){
            if(ContextCompat.checkSelfPermission(context,per) != PackageManager.PERMISSION_GRANTED){
                has=false;
                break;
            }
        }
        return has;
    }

    /**
     * Android 6.0及以上 申請某些許可權
     * */

    public static void requestAndroidPermission(Activity activity, int code, String []permission){
        ActivityCompat.requestPermissions(activity,permission,code);
    }

}

4.使用以及回撥

/**
     * Android 6.0系統及以上申請敏感許可權方法
     * */

    private void requestAndroidPermission(){
        if(FileHelper.isSdCardExist()){
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){//6.0
                boolean has= AndroidPermissionUtils.hasAndroidPermission(SetUpActivity.this, DataConstant.permission);
                if(!has){//6.0及以上 沒有許可權
                    AndroidPermissionUtils.requestAndroidPermission(SetUpActivity.this,0,DataConstant.permission);
                }else{//6.0及以上 有許可權 操作檔案
                    loadNewVersion();
                }
            }else{//6.0以下 操作檔案
                loadNewVersion();
            }
        }else{
            toast.showToast(StringConstant.Filestatus2);
        }
    }

    /**
     * onRequestPermissionsResult 動態申請回調方法
     * */

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if(grantResults.length > 0 && !(grantResults[0] == PackageManager.PERMISSION_GRANTED)){
            showDialogs("溫馨提示",StringConstant.errorstate6);
        }
    }