Android6.0權(quán)限全解析

Android在 6.0中摒棄了之前的install time permissions model取而代之的是runtime permissions model,也就是動態(tài)權(quán)限管理。
這種改變讓用戶更加容易的控制自己的隱私,好處不言而喻。但是對于程序員來說,還是有點(diǎn)小負(fù)擔(dān)的,增加了一些學(xué)習(xí)和開發(fā)的成本。

Android M

權(quán)限分類

Android 將系統(tǒng)權(quán)限分成了四個保護(hù)等級:

  • normal :普通級別
  • dangerous :危險(xiǎn)級別
  • signature:簽名級別
  • signatureOrSystem:系統(tǒng)簽名級別

而對于開發(fā)而言,關(guān)心的只有 普通權(quán)限危險(xiǎn)權(quán)限 兩類
其他兩級權(quán)限,為高級權(quán)限,應(yīng)用擁有platform級別的認(rèn)證才能申請。

當(dāng)應(yīng)用試圖在沒有權(quán)限的情況下做受限操作,應(yīng)用將被系統(tǒng)殺掉以警示。
所以權(quán)限的控制很重要,一個不留神,程序就會系統(tǒng)干掉,后果很嚴(yán)重~~

普通權(quán)限 (normal permission)

普通權(quán)限 會在App安裝期間被默認(rèn)賦予。這類權(quán)限不需要開發(fā)人員進(jìn)行額外操作,這類權(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
FLASHLIGHT
GET_PACKAGE_SIZE
INTERNET
KILL_BACKGROUND_PROCESSES
MODIFY_AUDIO_SETTINGS
NFC
READ_SYNC_SETTINGS
READ_SYNC_STATS
RECEIVE_BOOT_COMPLETED
REORDER_TASKS
REQUEST_INSTALL_PACKAGES
SET_TIME_ZONE
SET_WALLPAPER
SET_WALLPAPER_HINTS
TRANSMIT_IR
USE_FINGERPRINT
VIBRATE
WAKE_LOCK
WRITE_SYNC_SETTINGS
SET_ALARM
INSTALL_SHORTCUT

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

這些權(quán)限是在開發(fā)6.0程序時(shí),必須要注意的。
這些權(quán)限處理不好,程序可能會直接被系統(tǒng)干掉。
權(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
STORAGE READ_EXTERNAL_STORAGE,WRITE_EXTERNAL_STORAGE

我們會發(fā)現(xiàn)這些權(quán)限被分成了組。每個組里面包含了一些相近的權(quán)限。

分組的作用:

這些分組實(shí)際上是有一些特殊含義的。
系統(tǒng)在動態(tài)賦予權(quán)利的時(shí)候,是按照組去賦予的。即:
如果允許了某一個權(quán)限,那么同組中的其他權(quán)限也會被直接賦予
對于申請時(shí)彈出的dialog上面的文本說明也是對整個權(quán)限組的說明,而不是對單個權(quán)限的說明。

注意:
不要對權(quán)限組過多的依賴,盡可能對每個危險(xiǎn)權(quán)限都進(jìn)行正常流程的申請,因?yàn)樵诤笃诘陌姹局羞@個權(quán)限組可能會產(chǎn)生變化。

代碼的處理

  • 權(quán)限檢查

if (ContextCompat.checkSelfPermission(
        this, 
        Manifest.permission.WRITE_EXTERNAL_STORAGE) 
        != PackageManager.PERMISSION_GRANTED
        ) {
    Toast.makeText(this, "權(quán)限拒絕了", Toast.LENGTH_SHORT).show();
} else {
    Toast.makeText(this, "權(quán)限同意了", Toast.LENGTH_SHORT).show();
}
  • 權(quán)限申請

權(quán)限的申請方法??梢砸淮紊暾埗鄠€方法。

// 類似 startActivityForResult()中的REQUEST_CODE
int REQUEST_CODE = 99;
// 權(quán)限列表,將要申請的權(quán)限以數(shù)組的形式提交。
// 系統(tǒng)會依次進(jìn)行彈窗提示。
// 注意:如果AndroidManifest.xml中沒有進(jìn)行權(quán)限聲明,這里配置了也是無效的,不會有彈窗提示。
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
ActivityCompat.requestPermissions(this,
        permissions,
        REQUEST_CODE);
  • 權(quán)限回調(diào)

這個方法是Activity的一個回調(diào)方法。每一次調(diào)用 requestPermissions() 方法,都會回調(diào)一次這個方法。

@Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[], int[] grantResults) {
    switch (requestCode) {
        case REQUEST_CODE: {
            // grantResults是一個數(shù)組,和申請的數(shù)組一一對應(yīng)。
            // 如果請求被取消,則結(jié)果數(shù)組為空。
            if (grantResults.length > 0
                    && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // 權(quán)限同意了,做相應(yīng)處理
            } else {
                // 權(quán)限被拒絕了
            }
            return;
        }
    }
}
  • 權(quán)限再次申請

當(dāng)用戶拒絕了某個權(quán)限時(shí),我們可以再次去申請這個權(quán)限。但是這個時(shí)候,你應(yīng)該告訴用戶,你為什么要申請這個權(quán)限。
系統(tǒng)有一個API可以判斷一個權(quán)限是否被用戶拒絕了。

if (ActivityCompat.shouldShowRequestPermissionRationale(this,
        Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
    // 用戶拒絕過這個權(quán)限了,應(yīng)該提示用戶,為什么需要這個權(quán)限。
}

注意:
這個API有兩種情況會返回false:

  1. 用戶申請的權(quán)限沒有被用戶拒絕。
  2. 用戶在系統(tǒng)權(quán)限彈窗中,選中了 不再提醒 選項(xiàng)。
  • 整合代碼

看看官方給的代碼示例

// 首先檢查權(quán)限
if (ContextCompat.checkSelfPermission(thisActivity,Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {
    // 檢查用戶是否拒絕了這個權(quán)限
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
     Manifest.permission.READ_CONTACTS)) {
     // 給出一個提示,告訴用戶為什么需要這個權(quán)限

    } else {
    // 用戶沒有拒絕,直接申請權(quán)限
    ActivityCompat.requestPermissions(thisActivity,
            new String[]{Manifest.permission.READ_CONTACTS},
            MY_PERMISSIONS_REQUEST_READ_CONTACTS);
    //用戶授權(quán)的結(jié)果會回調(diào)到FragmentActivity的onRequestPermissionsResult
    }
}else {
 //已經(jīng)擁有授權(quán)
 //TODO: 正常業(yè)務(wù)邏輯
}
public void onRequestPermissionsResult(int requestCode,
    String permissions[], int[] grantResults) {
    switch (requestCode) {
        case MY_PERMISSIONS_REQUEST_READ_CONTACTS: {
            if (grantResults.length > 0
                && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                readContacts();
            } else {
             // 權(quán)限拒絕了。
            }
            return;
        }
    }
}

注意:
這里有一個問題,就是當(dāng)用戶在彈窗中用戶選擇了 不再提示
前面也說了,選中了 不再提示 會導(dǎo)致shouldShowRequestPermissionRationale 返回false。
同時(shí)系統(tǒng)的權(quán)限彈窗也不會再出現(xiàn)了。就算調(diào)用代碼
ActivityCompat.requestPermissions() 也不會有彈窗。
這里我們就需要自己做一個判斷,如果用戶選擇了 不再提示 (沒有API,自己加標(biāo)記判斷吧),我們可以給個提示,并且跳轉(zhuǎn)到設(shè)置界面,讓用戶手動設(shè)置,跳轉(zhuǎn)設(shè)置界面代碼如下:

Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", activity.getPackageName(), null);
intent.setData(uri);
activity.startActivityForResult(intent, PERMISSIONS_REQUEST_READ_CONTACTS);

  • 示例代碼

判斷用戶是否選擇了 不再提示 代碼示例:

SharedPreferences sp; // 用來保存配置
private void setFlag(boolean f) {
    SharedPreferences.Editor edit = sp.edit();
    edit.putBoolean("flag", f);
    edit.commit();
}
private boolean getFlag() {
    return sp.getBoolean("flag", false);
}
private boolean dontShowAgain() {
    return getFlag() && !ActivityCompat.shouldShowRequestPermissionRationale(this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
// 檢查權(quán)限
private void permission() {
    if (ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED
            ) {
        // 權(quán)限拒絕了
        if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
            // 用戶拒絕過權(quán)限了,這里給用戶個提示。
        } else {
            if (dontShowAgain()) {
                // 用戶選擇了 “不再提示”??梢蕴D(zhuǎn)到設(shè)置界面。
            } else {
                // 用戶沒有拒絕權(quán)限,這時(shí)正常申請權(quán)限。
                String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
                ActivityCompat.requestPermissions(this,
                    permissions,
                    REQUEST_CODE);
            }
            setFlag(true);
        }
    } else {
        // 權(quán)限同意了
    }
}

網(wǎng)上有很多關(guān)于權(quán)限的封裝庫。在真正使用的時(shí)候可以在網(wǎng)上找一個封裝庫來使用,會方便很多。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容