文章一般首發(fā)在個(gè)人公眾號(hào):追風(fēng)記憶,公眾號(hào)微信號(hào):zhuifengThread
Android Developer指南中,對(duì)Android安全體系結(jié)構(gòu)的核心有這么一個(gè)說法:默認(rèn)情況下,任何應(yīng)用程序都無權(quán)執(zhí)行任何會(huì)對(duì)其他應(yīng)用程序、操作系統(tǒng)或者用戶產(chǎn)生負(fù)面影響的操作。這句話其實(shí)就很好的詮釋了權(quán)限管理的意義,即用戶才是手中設(shè)備的主人,沒有用戶的允許,設(shè)備不可以私自記錄用戶的通訊錄,不可以上傳用戶的姓名和身份證號(hào),更不可以偷偷地竊取屬于用戶的高級(jí)隱私。但在如今的手機(jī)程序中,特別是一些流氓應(yīng)用,私自獲取用戶高級(jí)權(quán)限的現(xiàn)象也不少見。隨著Android版本的更新,對(duì)于權(quán)限這一塊也比以往做得更好了。這一次重新梳理權(quán)限管理環(huán)節(jié),并通過實(shí)例展示在Android 6.0版本后的權(quán)限處理過程。
什么是Android權(quán)限?
權(quán)限(Permission),顧名思義是一種對(duì)信息訪問的申請(qǐng)。Android的權(quán)限有上百種,例如應(yīng)用程序嘗試調(diào)用撥號(hào)權(quán)限、調(diào)用攝像頭權(quán)限、調(diào)用讀取短信權(quán)限、調(diào)用讀取通訊錄權(quán)限等等。對(duì)于這些權(quán)限,Android將其按照危險(xiǎn)等級(jí)進(jìn)行了劃分分組,分成如下的三種類別:
正常權(quán)限(PROTECTION_NORMAL):指的是應(yīng)用程序需要訪問的一些數(shù)據(jù)資源,但并不涉及到用戶的隱私或者對(duì)其他應(yīng)用程序無害。例如設(shè)置鬧鐘就是屬于正常權(quán)限。Android在處理正常權(quán)限時(shí)并不會(huì)提示用戶,而用戶也沒有辦法取消這些正常權(quán)限簽名權(quán)限(PROTECTION_SIGNATURE):指的是Android在安裝時(shí)授予應(yīng)用程序的權(quán)限,利用簽名權(quán)限,兩個(gè)簽名相同的應(yīng)用程序就可以進(jìn)行安全的數(shù)據(jù)共享。危險(xiǎn)權(quán)限(PROTECTION_DANGEROUS ):指的是直接觸碰到用戶隱私或者影響其他程序操作的權(quán)限,對(duì)于這一類的權(quán)限,Android會(huì)以彈窗的方式向用戶進(jìn)行問詢,應(yīng)用程序必須要經(jīng)過用戶的授權(quán)后才可以進(jìn)行相應(yīng)的行為。
以危險(xiǎn)權(quán)限為例,Android規(guī)定了如下的權(quán)限必須請(qǐng)求用戶的許可。
| 危險(xiǎn)權(quán)限組 | 危險(xiǎn)權(quán)限組含義 | 危險(xiǎn)權(quán)限舉例 | 危險(xiǎn)權(quán)限含義 |
|---|---|---|---|
| CALENDAR | 日歷 | READ_CALENDAR | 讀取日歷 |
| CALL_LOG | 通話記錄 | READ_CALL_LOG | 讀取通話記錄 |
| CAMERA | 攝像頭 | CAMERA | 打開攝像頭 |
| CONTACTS | 聯(lián)系人 | READ_CONTACTS | 讀取聯(lián)系人 |
| LOCATION | 定位 | ACCESS_FINE_LOCATION | 獲取定位 |
| MICROPHONE | 麥克風(fēng) | RECORD_AUDIO | 錄音 |
| PHONE | 電話 | CALL_PHONE | 打電話 |
| SENSORS | 傳感器 | BODY_SENSORS | 自身狀態(tài) |
| SMS | 短信 | SEND_SMS | 發(fā)送短信 |
| STORAGE | 存儲(chǔ) | READ_EXTERNAL_STORAGE | 讀取外部存儲(chǔ) |
Android權(quán)限獲取的方式
對(duì)于程序中申請(qǐng)的權(quán)限,都應(yīng)該在AndroidManifest.XML文件中進(jìn)行注冊(cè),否則申請(qǐng)的權(quán)限將無法發(fā)揮作用。下圖中的AndroidManifest文件中添加了打電話和攝像頭的權(quán)限。
Android權(quán)限獲取可以分成兩個(gè)階段,在Android 6.0之前,所申請(qǐng)的權(quán)限只要在AndroidManifest文件中列舉就可以了,并會(huì)在程序安裝時(shí)全部顯示在安裝頁面上,這個(gè)過程并不區(qū)分權(quán)限是否為常規(guī)權(quán)限還是正常權(quán)限。這種方式是造成早期Android系統(tǒng)在隱私性做的不好的直接原因,因?yàn)橛脩粼诎惭b應(yīng)用程序時(shí),很多時(shí)候并不會(huì)去仔細(xì)查看程序彈出的方框到底包含了哪些危險(xiǎn)的權(quán)限,為了盡快的進(jìn)入程序首頁,一般都會(huì)同意全部彈出的權(quán)限,這就給了很多流氓程序肆意發(fā)揮的入口。下圖展示了Android 5.0安裝界面的部分危險(xiǎn)權(quán)限截圖。
Google顯然也注意到了這一點(diǎn),于是在Android 6.0中推出了一種運(yùn)行時(shí)權(quán)限管理機(jī)制,這種機(jī)制對(duì)原有的權(quán)限處理方式進(jìn)行了很大程度的改善:應(yīng)用程序安裝后,點(diǎn)開程序時(shí),不再是列出程序申請(qǐng)的所有權(quán)限,而是將部分危險(xiǎn)權(quán)限與應(yīng)用本身的功能相關(guān)聯(lián)。例如相機(jī)應(yīng)用,只有當(dāng)用戶點(diǎn)擊拍照按鈕時(shí),系統(tǒng)就會(huì)彈出申請(qǐng)攝像頭的權(quán)限,這種方式將用戶的注意力集中到了當(dāng)下的操作上,使得用戶有足夠的時(shí)間和意愿去判定是否同意程序的權(quán)限申請(qǐng),并且用戶隨時(shí)可以在設(shè)置中關(guān)掉授予程序的危險(xiǎn)權(quán)限,從而極大程度上避免了對(duì)危險(xiǎn)權(quán)限的放行,保護(hù)了用戶的隱私。
Android 6.0之后的運(yùn)行時(shí)權(quán)限處理機(jī)制很好的解決了危險(xiǎn)權(quán)限的獲取問題,它具有如下的兩個(gè)行為:
如果應(yīng)用程序在當(dāng)前的權(quán)限組(一組權(quán)限的集合)中沒有任何權(quán)限,那么在請(qǐng)求權(quán)限時(shí),系統(tǒng)會(huì)顯示該權(quán)限組的請(qǐng)求對(duì)話框,例如程序請(qǐng)求
CALL_PHONE權(quán)限,那么Android將彈出CALL權(quán)限對(duì)話框顯示應(yīng)用希望撥打電話功能。如果一個(gè)權(quán)限組中的任意一個(gè)權(quán)限被授權(quán),那么該權(quán)限組中的其他權(quán)限都會(huì)被Android默認(rèn)授權(quán)。例如上面的
CALL_PHONE權(quán)限被允許,那么PHONE權(quán)限組中的其它權(quán)限,例如READ_PHONE_NUMBERS讀取電話號(hào)碼的權(quán)限就會(huì)默認(rèn)被授權(quán),并且不會(huì)向用戶彈框顯示權(quán)限申請(qǐng)過程。
運(yùn)行時(shí)權(quán)限處理機(jī)制中的第二點(diǎn)的特性并不被Google推崇,Google認(rèn)為后續(xù)的Android版本中這個(gè)特征可能會(huì)發(fā)生變化,并建議開發(fā)者應(yīng)明確指出所需要的每一個(gè)權(quán)限。
Android實(shí)現(xiàn)權(quán)限管理
關(guān)于Android權(quán)限更詳細(xì)的介紹可以在官方的Android Developer指南中查閱。重點(diǎn)是如何在實(shí)踐中學(xué)會(huì)使用Android權(quán)限,后半部分將會(huì)以代碼和流程圖的方式展示Android權(quán)限管理。
Android權(quán)限處理可以分解為三個(gè)部分:
檢查權(quán)限:權(quán)限是否為危險(xiǎn)權(quán)限,正常權(quán)限會(huì)被系統(tǒng)默認(rèn)允許,危險(xiǎn)權(quán)限需要用戶手動(dòng)允許,所以我們的權(quán)限討論范圍是危險(xiǎn)權(quán)限的獲取,在Android中檢查權(quán)限是否獲取的方法是
ContextCompat.checkSelfPermission(),這個(gè)方法返回一個(gè)int類型的PERMISSION_GRANTED或者PERMISSION_DENIED,一般來說,程序剛申請(qǐng)權(quán)限的時(shí)候都是處于PERMISSION_DENIED狀態(tài),因此需要后續(xù)的申請(qǐng)過程。請(qǐng)求權(quán)限:當(dāng)權(quán)限并沒有被允許的情況下,就需要向用戶請(qǐng)求處理權(quán)限申請(qǐng),在應(yīng)用層上則表現(xiàn)為Android系統(tǒng)會(huì)彈出一個(gè)對(duì)話框,提示用戶進(jìn)行操作。
從代碼層面考慮,Android提供了一個(gè)requestPermissions()的調(diào)用方法來請(qǐng)求相應(yīng)權(quán)限,這個(gè)方法接受目標(biāo)Activity、 需要請(qǐng)求授權(quán)的權(quán)限組和識(shí)別權(quán)限請(qǐng)求的請(qǐng)求代碼作為參數(shù)傳遞,并且它是一個(gè)異步的方法,并返回產(chǎn)生的結(jié)果。
- 處理權(quán)限響應(yīng):當(dāng)用戶對(duì)彈出的權(quán)限申請(qǐng)框進(jìn)行響應(yīng)后,Android會(huì)調(diào)用
onRequestPermissionsResult()方法,將用戶的響應(yīng)作為參數(shù)傳遞。開發(fā)者必須使用@Override聲明覆蓋這個(gè)方法,來確認(rèn)這個(gè)權(quán)限是否真的被用戶所允許,并進(jìn)行后續(xù)的業(yè)務(wù)邏輯編寫。
權(quán)限獲取的一般過程就是遵循上面的三個(gè)步驟進(jìn)行的,但是千萬不要忘記了所申請(qǐng)的權(quán)限一定要在AndroidManifest.xml中注冊(cè),不然就準(zhǔn)備嘗嘗異常拋出鐵拳的力量吧。
當(dāng)然,更清晰明了的是用流程圖來展示權(quán)限申請(qǐng)和授權(quán)的過程。
單個(gè)權(quán)限的獲取過程
下面以獲取打電話的權(quán)限為例,通過代碼實(shí)現(xiàn)的方式來解釋這個(gè)流程的具體做法。以下面一個(gè)Demo的頁面為測(cè)試對(duì)象,只要點(diǎn)擊獲取電話權(quán)限按鈕,就會(huì)彈出權(quán)限提示窗,然后允許該請(qǐng)求,就可以實(shí)現(xiàn)跳轉(zhuǎn)到撥號(hào)頁面進(jìn)行通話的功能。
第一部分是檢測(cè)權(quán)限部分。點(diǎn)擊獲取電話權(quán)限按鈕,就會(huì)調(diào)用程序中的callPermission()這個(gè)方法,在callPermission中調(diào)用checkSelfPermission的方法進(jìn)行權(quán)限檢測(cè),實(shí)參是當(dāng)前的Activity對(duì)象和對(duì)應(yīng)的權(quán)限,這個(gè)方法返回一個(gè)int類型的值,其中若權(quán)限允許則返回值為0的PERMISSION_GRANTED,否則返回值為-1的PERMISSION_DENIED,當(dāng)權(quán)限已經(jīng)被允許的情況下,直接調(diào)用else語句中的callPhone()方法,意味著直接可以撥打電話了。
當(dāng)權(quán)限檢測(cè)為未允許的情況下,進(jìn)入請(qǐng)求權(quán)限狀態(tài),即if語句中的requestPermissions這個(gè)方法,這個(gè)方法會(huì)創(chuàng)建一個(gè)字符串?dāng)?shù)組,將請(qǐng)求的權(quán)限同一放入這個(gè)數(shù)組中,最后一個(gè)參數(shù)是一個(gè)int類型的requestCode,該值在后續(xù)的處理權(quán)限中發(fā)揮作用,并且這個(gè)值不一定取1,只要這個(gè)值大于等于0即可。為了方便起見,這里取1作為請(qǐng)求碼。
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.getCallPermission:
Toast.makeText(MainActivity.this, "獲取打電話權(quán)限", Toast.LENGTH_SHORT).show(); callPermission();
break;
case R.id.getCameraPermission:
Toast.makeText(MainActivity.this, "轉(zhuǎn)到第二個(gè)頁面", Toast.LENGTH_SHORT).show(); Intent intent = new Intent(this, SecondActivity.class); startActivity(intent);
default:
break;
}
}
/**
* 查詢app是否有相關(guān)權(quán)限
* 如果有就直接調(diào)用寫的方法
* 沒有的話就需要申請(qǐng)權(quán)限
*/
private void callPermission(){
if(ActivityCompat.checkSelfPermission(MainActivity.this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED){ ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.CALL_PHONE}, 1);
}else {
callPhone();
}
}
當(dāng)用戶點(diǎn)擊了權(quán)限的彈窗后,Android會(huì)調(diào)用下面的onRequestPermissionsResult的方法,這個(gè)方法接受從requestPermissions()方法傳遞的requestCode、權(quán)限字符串?dāng)?shù)組和用戶響應(yīng)數(shù)組這三種作為參數(shù),用戶響應(yīng)數(shù)組中的元素個(gè)數(shù)應(yīng)與申請(qǐng)的權(quán)限字符串?dāng)?shù)組中元素個(gè)數(shù)保持一致。requestCode的作用是作為請(qǐng)求權(quán)限時(shí)權(quán)限處理成功的一種標(biāo)識(shí),只有這個(gè)標(biāo)識(shí)匹配正確了,才能進(jìn)一步的核對(duì)用戶響應(yīng)數(shù)組中的元素是否與PERMISSION_GRANTED相等,從而驗(yàn)證權(quán)限是否真正的被用戶所允許。所以上一步的requestCode在這里發(fā)揮了作用。**應(yīng)當(dāng)注意的是,由于這個(gè)實(shí)例只用了一個(gè)權(quán)限,所以應(yīng)該通過索引的方式來獲取用戶響應(yīng)數(shù)組中的第一個(gè)元素grantResult[0]。
/**
* 權(quán)限申請(qǐng)的回調(diào)結(jié)果
* @param requestCode 請(qǐng)求碼
* @param permissions 請(qǐng)求權(quán)限
* @param grantResults 授權(quán)結(jié)果,是一個(gè)int型數(shù)組,若有多個(gè)授權(quán),則依次讀取 */
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(requestCode == 1){
if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
callPhone();
}else {
Toast.makeText(this, "權(quán)限未授權(quán)!", Toast.LENGTH_SHORT).show();
}
}
}
/**
* 打電話,注意異常處理,不然會(huì)報(bào)錯(cuò)
*/
private void callPhone(){
try{
Intent intent = new Intent(Intent.ACTION_CALL);
Uri uri = Uri.parse("tel:" + 10086);
intent.setData(uri);
startActivity(intent);
}catch (SecurityException e){
e.printStackTrace();
}
}
對(duì)于單個(gè)的權(quán)限而言,上述的流程就可以完成權(quán)限獲取的全部操作,在手機(jī)端運(yùn)行程序,點(diǎn)擊獲取電話權(quán)限后就會(huì)彈出權(quán)限窗口,點(diǎn)擊允許后轉(zhuǎn)到電話撥打的界面。
那么如果想一次性申請(qǐng)多個(gè)權(quán)限,該如何處理這種需求?
多個(gè)權(quán)限的獲取過程
假設(shè)需要一個(gè)按鈕來獲取兩個(gè)權(quán)限:打電話權(quán)限和攝像頭權(quán)限。處理的方式和上面的大同小異,如果你注意到上述請(qǐng)求權(quán)限和處理權(quán)限響應(yīng)的方法中,它們都是接收一個(gè)權(quán)限字符串?dāng)?shù)組和用戶響應(yīng)字符串?dāng)?shù)組,那么問題就很好解決了。思路如下:
構(gòu)建一個(gè)申請(qǐng)權(quán)限的
ArrayList檢測(cè)權(quán)限,并將沒有被授予允許的權(quán)限通通
add到ArrayList中轉(zhuǎn)換
ArrayList變?yōu)?code>requestPermissions的參數(shù)依次讀取用戶響應(yīng)數(shù)組中的
grantCode,判斷是否授權(quán)授權(quán)過程結(jié)束
下面的代碼展示了如何一鍵處理兩個(gè)權(quán)限的過程。
private void callAllPermissions(){
List<String> permissionsList = new ArrayList<>();
// 如果沒有被授權(quán),那么就add到permissionsList中
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE)
!= PackageManager.PERMISSION_GRANTED){ permissionsList.add(Manifest.permission.CALL_PHONE);
}
if(ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED){
permissionsList.add(Manifest.permission.CAMERA);
}
//不為空,說明有需要授權(quán)的部分,則進(jìn)行請(qǐng)求權(quán)限步驟
if(!permissionsList.isEmpty()){
ActivityCompat.requestPermissions(this,
permissionsList.toArray(new String[permissionsList.size()]), 1);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode){
case 1:
int resultLength = grantResults.length;
//說明回調(diào)成功了,權(quán)限授權(quán)被允許
if(resultLength > 0){
for(int grantCode : grantResults){
if(grantCode == PackageManager.PERMISSION_GRANTED){ Toast.makeText(this, "授權(quán)成功", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "授權(quán)失敗", Toast.LENGTH_SHORT).show(); }
}
}
break;
default:
break;
}
}
上述的過程完成后,程序所需要的權(quán)限得到了滿足,便可以繼續(xù)的進(jìn)行后續(xù)的業(yè)務(wù)邏輯。但是仍然要提醒一點(diǎn),Android 6.0以后,權(quán)限是可以由用戶手動(dòng)關(guān)閉的,并不是永久授權(quán),這意味著今天的授權(quán)成功并不代表著明天就不需要授權(quán)了,因此權(quán)限的檢查是必須要有的一個(gè)步驟。
總結(jié)
在以前學(xué)習(xí)Android的時(shí)候接觸過權(quán)限處理,所以這次結(jié)合業(yè)務(wù)上遇到權(quán)限處理的問題,借助Android Developer的指南,對(duì)Android 6.0后的權(quán)限問題進(jìn)行了一次重新的梳理。通過實(shí)例和流程圖來展示Android對(duì)于危險(xiǎn)權(quán)限的獲取過程和一些應(yīng)該注意的地方。同時(shí)也應(yīng)該時(shí)刻的關(guān)注官網(wǎng)的指南,因?yàn)闄?quán)限問題可能隨著版本的更迭而發(fā)生一些調(diào)整或改變,不然很容易出現(xiàn)代碼一樣但出現(xiàn)異常的情況。
相關(guān)參考:
Android Developer