重新梳理Android權(quán)限管理

文章一般首發(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)限。

AndroidManifest注冊(cè)權(quán)限.JPG

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)限截圖。

Android 5.0權(quán)限獲取.JPG

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è)部分:

  1. 檢查權(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)過程。

  2. 請(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)行操作。

運(yùn)行時(shí)權(quán)限處理機(jī)制

從代碼層面考慮,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é)果。

  1. 處理權(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)限通通addArrayList

  • 轉(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

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

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

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