1.引子
在換到Android手機之前,對Android系統的印象是這系統app的跑馬場,app可以任意索取各種權限,随意竊取各種隐私,換手機後才知道Android系統對權限的管理已有很大的改觀,索取的每個危險權限都需要提示使用者,當然Android隻是盡可能提示使用者,還是存在着使用者不同意就不給用的情況。
權限管理的改進給開發者增加了一定工作量,申請危險權限不再是簡單的在AndroidManifest.xml添加一行代碼的事,而是需要提示使用者進行動态申請。了解動态申請之後,覺得沒多複雜,但不了解時,隻知道在網上拷貝現成代碼片段,碰到需要申請新的權限就不知道從何下手。我曾經粘貼了幾段動态申請權限代碼,就是因為不了解原理,隻會複制粘貼,代碼雖然能夠運作,但無比臃腫。
為此通過此文梳理Android權限,以便掌握Android權限管理,拒絕做Copy工程師。
2.權限的作用
權限的目的是保護Android使用者的隐私。作為一個可以任意定制的開源系統,權限真是使用者隐私最後的屏障,Android系統要求,在預設情況下,任何應用程式無權執行會對其他應用程式、作業系統或使用者産生不利影響的任何操作。包括讀寫使用者的私人資料(例如聯系人或電子郵件),讀寫其他應用程式的檔案,通路網絡等等。要執行這些操作,Android應用必須獲得對應的權限。
Android有上百種權限,可分為三大類,一類是普通權限,一類是簽名權限,還有一類是危險權限。
其中普通權限是指那些不會威脅到使用者安全和隐私的權限,例如設定鬧鐘權限。授予app普通權限隻需要在AndroidManifest.xml檔案裡聲明,系統會自動授權,不需要使用者确認,使用者也無法做撤銷操作。
第二種是簽名權限,這類權限同樣是在應用安裝時由系統授予,不過并不是無條件授予,想使用權限的應用程式必須與定義該權限的應用程式使用相同的證書才行,例如應用A使用證書A簽名,定義了一個簽名權限,應用B想使用這個權限,必須由證書A簽名。簽名權限使用的場景如一個公司有多個app,為了能讓這些app能夠互相調用,但不希望被外部app調用,就可以通過自定義一個簽名調用權限實作。
最後再來看第三類危險權限,危險權限之是以被危險,是因為這類權限涉及到使用者個人敏感資料資源,或者影響使用者存儲的資料或者其他應用程式的資料,例如讀取使用者聯系人,打開麥克風等,江湖上一直有傳言,目前許多app會偷偷打開麥克風竊聽使用者對話,然後根據對話内容精準推送廣告,這類傳言像是都市傳說,因為具有神秘性能廣泛傳播,要知道,原版Android系統中,應用索取危險權限必須需要向使用者提示,在使用者許可之前,應用是無法擷取權限的。不過Android系統畢竟是開源的,将原生Android權限管理功能修改倒是有可能雖然使用者沒同意,但app仍可以獲得權限,這話題就在本文之外了,個人認為有其他更好的方法來竊取隐私,竊聽對話是成本較高的那種,用起來不值得。
如前所述,權限有上百種,一一記住不現實,其實我們隻要知道哪些是危險權限,危險權限之外都是普通權限。
危險權限總共9個分組,24個權限,如果我們申請的權限不在這個表中,那就說明是普通權限,而權限分組意思是組内的這些權限同屬于一個組,使用者同意授權一個權限組,則組内的所有權限都會授予,比如,應用被授予READ_EXTERNAL_STORAGE權限之後,如果再申請WRITE_EXTERNAL_STORAGE權限,系統會立即授予該權限,不再向使用者提示。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiclRnblN2XjlGcjAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLzcGVPh3dXRmdO5mYohnMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL4UDO5QDM1cTM4EDNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
3.申請權限步驟和示例
- 普通權限申請很簡單,隻需要在AndroidManifest.xml檔案申請即可,例如申請設定鬧鈴的權限
- 簽名權限申請類似,隻需要修飾protectionLevel成signature即可,例如設定一個自定義權限成簽名權限,代碼如下,其他應用申請這個權限時必須具有相同的簽名才能授予
<permission android:name="com.test.permission.act" android:protectionLevel="signature"/>
- 申請危險權限重點說一下,一方面和普通權限一樣,仍需要在AndroidManifest.xml檔案聲明,另一方面,還需要額外動态申請。下面以申請存儲讀寫權限來作為例子說明。
- 在AndroidManifest檔案中聲明,這一步不可少
- 在應用啟動代碼中檢查權限,确認應用是否已經擁有讀寫存儲的權限。這部分是在代碼中實作了,是以叫動态申請。Android中使用
檢查權限的授權情況,這個方法第一個參數是上下文Context,第二個參數是所要申請權限的名稱,名稱當然不能随便寫阿貓阿狗,而是有特定名稱,建議直接調Android在ContextCompat.checkSelfPermission()
中已定義好的名稱,比如現在要申請的讀外部存儲權限是Manifest.permission
。該方法傳回一個int類型的Manifest.permission.WRITE_EXTERNAL_STORAGE
或者PERMISSION_GRANTED(同意)
。一般來說,程式初次安裝後,所申請的權限的時候都是處于PERMISSION_DENIED`(拒絕)
狀态,需要提出申請,使用者同意之後不需要再次申請,除非解除安裝應用重新安裝或者到系統設定裡把權限給關了;PERMISSION_DENIED
- 接下來是申請權限,申請使用的方法
,該方法傳入參數有三個,第一個是Activity、 第二個是需要請求授權的權限字元串數組,可以把想申請的權限名稱都列入其中,第三個是識别權限請求的請求代碼,在權限申請的回調函數requestPermissions()
用得着,該值必須大于或等于0,其作用在回調函數裡再詳細說明,這裡隻要了解成是一個自定義的标記。需要注意的是,申請權限是異步處理,也就是說,代碼不會阻塞在等使用者點選,而是使用者點選後再回調onRequestPermissionsResult()
。以上兩個步驟的代碼如下:onRequestPermissionsResult()
- 在應用啟動代碼中檢查權限,确認應用是否已經擁有讀寫存儲的權限。這部分是在代碼中實作了,是以叫動态申請。Android中使用
//檢查應用是否已經擁有權限
if(ActivityCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){
// 說明沒有該權限,就需要申請權限
ActivityCompat.requestPermissions(MainActivity.this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}else {
//已經擁有權限,做進一步操作
}
- 接下來是覆寫申請權限回調函數
,對權限申請做後續處理,該回調函數有三個傳回結果,第一個onRequestPermissionsResult()
,這個參數就是在requestCode
提到的識别權限的請求代碼,作用就是一個回執單号,根據這個單号我們能确定是我們申請權限的處理結果,也就是說,在申請權限時傳入這個參數,在處理結束後,回調函數原封不動給回來,這樣我們就知道是不是我們的處理結果;第二個參數是權限字元串數組,也就是我們在requestPermissions()
填的權限字元串數組;第三個參數使用者響應的數組,使用者的對每個申請權限的批複情況就是在這個數組裡,這個數組的個數是和申請權限數組是一一對應的。代碼如下:requestPermissions()
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
//通過requestCode來識别是否同一個請求
if (requestCode == 1){
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
//使用者同意執行的操作
}else{
//使用者不同意不同意執行的操作,例如提示使用者不同意不給用。。。
}
}
}
以上如果申請單個權限,如果申請多個權限又怎麼操作呢?基本步驟和申請單個權限類似,但因為是多個權限,還需要做一下額外處理,例如怎麼判斷是否每個權限都授權了。下面以多申請一個錄音權限來說明
- 同樣,首先在AndroidManifest.xml做聲明
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
- 準備工作是建立一個權限字元串數組和聲明一個權限清單list,其中清單用于判斷每個權限的處理情況
String[] permissions = new String[]{Manifest.permission.RECORD_AUDIO, Manifest.permission.READ_EXTERNAL_STORAGE}; List<String> mPermissionList = new ArrayList<>();
- 判斷每個權限授權情況,未同意的列入申請名單,然後申請權限
private void myRequestPermission() {
mPermissionList.clear();
//逐個判斷權限是否已經通過
for (int i = 0; i < permissions.length; i++) {
if (ContextCompat.checkSelfPermission(this, permissions[i]) != PackageManager.PERMISSION_GRANTED) {
//如果未授權,則添加到清單中
mPermissionList.add(permissions[i]);
}
}
//申請權限,如果清單不為空,說明有權限需要申請
if (mPermissionList.size() > 0) {
//有權限沒有通過,需要申請
ActivityCompat.requestPermissions(this, permissionsList.toArray(new String[permissionsList.size()]), 1);
}else{
//已有權限,做進一步操作
}
}
- 覆寫請求權限回調的方法,做後續處理
@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;
//說明回調成功了,權限授權被允許
if(resultLength > 0){
for(int grantCode : grantResults){
if(grantCode == PackageManager.PERMISSION_GRANTED){
Toast.makeText(this, "授權成功", Toast.LENGTH_SHORT).show();
}else{
Toast.makeText(this, "授權失敗", Toast.LENGTH_SHORT).show();
}
}
}
break;
default:
break;
}
}
4.總結
經過了解Android權限後,可以看出申請權限過程不是很複雜,普通權限比較簡單,隻需要在AndroidManifest.xml中聲明即可,危險權限不僅需要在AndroidManifest.xml聲明,同時還需要在代碼中動态申請,首先是使用
checkSelfPermission()
檢查是否某個危險權限是否已授權,如果沒有授權,則
requestPermissions()
申請授權,記得這是異步處理,代碼不會阻塞在等待使用者授權位置,接下來就是處理授權情況,系統回調
onRequestPermissionsResult
通知我們處理結果,我們根據這個結果做進一步處理。同時申請多個權限稍微複雜一點,需要處理每個權限的處理情況。