標籤:status time 運行時 should 本地 multi 兩種 相機 ble
聊一聊 Android 6.0 的運行時許可權
- 許可權一刀切
- 棉花糖運行時許可權
- 許可權的分組
- 正常許可權
- 特殊許可權
- 請求SYSTEM_ALERT_WINDOW
- 請求WRITE_SETTINGS
- 危險許可權
- 必須要支援運行時許可權麼
- 不支援運行時許可權會崩潰麼
- 該來的還得來
- 一個標準的流程
- 如何批量申請
- 申請這麼多許可權豈不是很累
- 注意事項
- 兩個許可權
- 些許建議
- 注意
Android 6.0,代號棉花糖,自發布伊始,其主要的特徵運行時許可權就很受關注。因為這一特徵不僅改善了使用者對於應用的使用體驗,還使得應用開發人員在實踐開發中需要做出改變。
沒有深入瞭解運行時許可權的開發人員通常會有很多疑問,比如什麼是運行時許可權,哪些是運行時的許可權,我的應用程式是不是會在6.0系統上各種崩潰呢,如何才能支援運行時許可權機制呢。本文講嘗試回答這一些問題,希望讀者閱讀完成之後,都能找到較為完美的答案。
許可權一刀切
在6.0以前的系統,都是許可權一刀切的處理方式,只要使用者安裝,Manifest申請的許可權都會被賦予,並且安裝後許可權也撤銷不了。
這種情況下,當我們從Google Play安裝一個應用,在安裝之前會得到這樣的許可權提示資訊。
當上述對話方塊彈出後,使用者只有兩種選擇:
- 我信任你,即使有敏感許可權
- 你一個**應用,要這個許可權幹嘛,我還是不安裝了。
所以,這種一刀切的處理方式還是有弊端的,我們沒有辦法只允許某些許可權或者拒絕某些許可權。
棉花糖運行時許可權
從棉花糖開始,Android系統引入了新的許可權機制,即本文要講的運行時許可權。
何為運行時許可權呢?舉個栗子,以某個需要拍照的應用為例,當運行時許可權生效時,其Camera許可權不是在安裝後賦予,而是在應用啟動並執行時候進行請求許可權(比如當使用者按下”相機拍照“按鈕後)看到的效果則是這樣的
接下來,對於Camera許可權的處理完全權交給使用者。是不是有點像蘋果系統的處理呢,不要說這是抄襲,暫且稱為師夷長技以制夷。
許可權的分組
Android中有很多許可權,但並非所有的許可權都是敏感許可權,於是6.0系統就對許可權進行了分類,一般為下述幾類
- 正常(Normal Protection)許可權
- 危險(Dangerous)許可權
- 特殊(Particular)許可權
- 其他許可權(一般很少用到)
正常許可權
正常許可權具有如下的幾個特點
- 對使用者隱私沒有較大影響或者不會打來安全問題。
- 安裝後就賦予這些許可權,不需要顯示提醒使用者,使用者也不能取消這些許可權。
正常許可權列表
ACCESS_LOCATION_EXTRA_COMMANDSACCESS_NETWORK_STATEACCESS_NOTIFICATION_POLICYACCESS_WIFI_STATEBLUETOOTHBLUETOOTH_ADMINBROADCAST_STICKYCHANGE_NETWORK_STATECHANGE_WIFI_MULTICAST_STATECHANGE_WIFI_STATEDISABLE_KEYGUARDEXPAND_STATUS_BARGET_PACKAGE_SIZEINTERNETKILL_BACKGROUND_PROCESSESMODIFY_AUDIO_SETTINGSNFCREAD_SYNC_SETTINGSREAD_SYNC_STATSRECEIVE_BOOT_COMPLETEDREORDER_TASKSREQUEST_INSTALL_PACKAGESSET_TIME_ZONESET_WALLPAPERSET_WALLPAPER_HINTSTRANSMIT_IRUSE_FINGERPRINTVIBRATEWAKE_LOCKWRITE_SYNC_SETTINGSSET_ALARMINSTALL_SHORTCUTUNINSTALL_SHORTCUT
上述的許可權基本設計的是關於網路,藍芽,時區,捷徑等方面,只要在Manifest指定了這些許可權,就會被授予,並且不能撤銷。
特殊許可權
這裡講特殊許可權提前講一下,因為這個相對來說簡單一些。
特殊許可權,顧名思義,就是一些特別敏感的許可權,在Android系統中,主要由兩個
- SYSTEM_ALERT_WINDOW,設定懸浮窗,進行一些黑科技
- WRITE_SETTINGS 修改系統設定
關於上面兩個特殊許可權的授權,做法是使用startActivityForResult
啟動授權介面來完成。
請求SYSTEM_ALERT_WINDOW
private static final int REQUEST_CODE = 1;private void requestAlertWindowPermission() { Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION); intent.setData(Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_CODE);}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE) { if (Settings.canDrawOverlays(this)) { Log.i(LOGTAG, "onActivityResult granted"); } }}
上述代碼需要注意的是
- 使用Action
Settings.ACTION_MANAGE_OVERLAY_PERMISSION
啟動隱式Intent
- 使用
"package:" + getPackageName()
攜帶App的包名資訊
- 使用
Settings.canDrawOverlays
方法判斷授權結果
請求WRITE_SETTINGS
private static final int REQUEST_CODE_WRITE_SETTINGS = 2;private void requestWriteSettings() { Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS); intent.setData(Uri.parse("package:" + getPackageName())); startActivityForResult(intent, REQUEST_CODE_WRITE_SETTINGS );}@Overrideprotected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_WRITE_SETTINGS) { if (Settings.System.canWrite(this)) { Log.i(LOGTAG, "onActivityResult write settings granted" ); } }}
上述代碼需要注意的是
- 使用Action
Settings.ACTION_MANAGE_WRITE_SETTINGS
啟動隱式Intent
- 使用
"package:" + getPackageName()
攜帶App的包名資訊
- 使用
Settings.System.canWrite
方法檢測授權結果
注意:關於這兩個特殊許可權,一般不建議應用申請。
危險許可權
危險許可權實際上才是運行時許可權主要處理的對象,這些許可權可能引起隱私問題或者影響其他程式運行。Android中的危險許可權可以歸為以下幾個分組:
- CALENDAR
- CAMERA
- CONTACTS
- LOCATION
- MICROPHONE
- PHONE
- SENSORS
- SMS
- STORAGE
各個許可權分組與其具體的許可權,可以參考:
必須要支援運行時許可權麼
目前應用實際上是可以不需要支援運行時許可權的,但是最終肯定還是需要支援的,只是時間問題而已。
想要不支援運行時許可權機制很簡單,只需要將targetSdkVersion
設定低於23就可以了,意思是告訴系統,我還沒有完全在API 23(6.0)上完全搞定,不要給我啟動新的特性。
不支援運行時許可權會崩潰麼
可能會,但不是那種一上來就劈裡啪啦崩潰不斷的那種。
如果你的應用將targetSdkVersion
設定低於23,那麼在6.0的系統上不會為這個應用開啟運行時許可權機制,即按照以前的一刀切方式處理。
然而有點糟糕的是
6.0系統提供了一個應用許可權管理介面,介面長得是這樣的
既然是可以管理,使用者就能取消許可權,當一個不支援運行時許可權的應用某項許可權被取消時
系統會彈出一個對話方塊提醒撤銷的危害,如果使用者執意撤銷,會帶來如下的反應
- 如果你的程式正在運行,則會被殺掉。
- 當你的應用再次運行時,可能出現崩潰
為什麼會可能崩潰的,比如下面這段代碼
TelephonyManager telephonyManager = (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);String deviceId = telephonyManager.getDeviceId();if (deviceId.equals(mLastDeviceId)) {//This may cause NPE //do something}
如果使用者撤消了擷取DeviceId的許可權,那麼再次運行時,deviceId就是null,如果程式後續處理不當,就會出現崩潰。
該來的還得來
6.0的運行時許可權,我們最終都是要支援的,通常我們需要使用如下的API
- int checkSelfPermission(String permission) 用來檢測應用是否已經具有許可權
- void requestPermissions(String[] permissions, int requestCode) 進行請求單個或多個許可權
- void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) 使用者對請求作出響應後的回調
以一個請求Camera許可權為例
@Override public void onClick(View v) { if (!(checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED)) { requestCameraPermission(); } } private static final int REQUEST_PERMISSION_CAMERA_CODE = 1; private void requestCameraPermission() { requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_PERMISSION_CAMERA_CODE); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSION_CAMERA_CODE) { int grantResult = grantResults[0]; boolean granted = grantResult == PackageManager.PERMISSION_GRANTED; Log.i(LOGTAG, "onRequestPermissionsResult granted=" + granted); } }
通常情況下,我們會得到這樣的一個對話方塊
當使用者選擇允許,我們就可以在onRequestPermissionsResult方法中進行響應的處理,比如開啟網路攝影機
當使用者拒絕,你的應用可能就開始危險了
當我們再次嘗試申請許可權時,彈出的對話方塊和之前有點不一樣了,主要表現為多了一個checkbox。如
當使用者勾選了”不再詢問“拒絕後,你的程式基本這個許可權就Game Over了。
不過,你還有一絲希望,那就是再出現上述的對話方塊之前做一些說明資訊,比如你使用這個許可權的目的(一定要坦白)。
shouldShowRequestPermissionRationale這個API可以幫我們判斷接下來的對話方塊是否包含”不再詢問“選擇框。
一個標準的流程
if (!(checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED)) { if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) { Toast.makeText(this, "Please grant the permission this time", Toast.LENGTH_LONG).show(); } requestReadContactsPermission();} else { Log.i(LOGTAG, "onClick granted");}
如何批量申請
批量申請許可權很簡單,只需要字串數組放置多個許可權即可。如請求代碼
private static final int REQUEST_CODE = 1;private void requestMultiplePermissions() { String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_PHONE_STATE}; requestPermissions(permissions, REQUEST_CODE);}
對應的介面效果是
注意:間隔較短的多個許可權申請建議設定成單次多個許可權申請形式,避免彈出多個對話方塊,造成不太好的視覺效果。
申請這麼多許可權豈不是很累
其實你不需要每個許可權都去顯式申請,舉一個例子,如果你的應用授權了讀取連絡人的許可權,那麼你的應用也是被賦予了寫入連絡人的許可權。因為讀取連絡人和寫入連絡人這兩個許可權都屬於連絡人許可權分組,所以一旦組內某個許可權被允許,該組的其他許可權也是被允許的。
注意事項API問題
由於checkSelfPermission和requestPermissions從API 23才加入,低於23版本,需要在運行時判斷 或者使用Support Library v4中提供的方法
- ContextCompat.checkSelfPermission
- ActivityCompat.requestPermissions
- ActivityCompat.shouldShowRequestPermissionRationale
多系統問題
當我們支援了6.0必須也要支援4.4,5.0這些系統,所以需要在很多情況下,需要有兩套處理。比如Camera許可權
if (isMarshmallow()) { requestPermission();//然後在回調中處理} else { useCamera();//低於6.0直接使用Camera}
兩個許可權
運行時許可權對於應用影響比較大的許可權有兩個,他們分別是
- READ_PHONE_STATE
- WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE
其中READ_PHONE_STATE用來擷取deviceID,即IMEI號碼。這是很多統計依賴計算裝置唯一ID的參考。如果新的許可權導致讀取不到,避免導致統計的異常。建議在完全支援運行時許可權之前,將對應的值寫入到App本機資料中,對於新安裝的,可以採取其他策略減少對統計的影響。
WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE這兩個許可權和外置儲存(即sdcard)有關,對於下載相關的應用這一點還是比較重要的,我們應該儘可能的說明和引導使用者授予該許可權。
些許建議
- 不要使用多餘的許可權,新增許可權時要謹慎
- 使用Intent來替代某些許可權,如撥打到電話(和你的產品經理PK去吧)
- 對於使用許可權擷取的某些值,比如deviceId,盡量本機存放區,下次訪問直接使用本地的資料值
- 注意,由於使用者可以撤銷某些許可權,所以不要使用應用本地的標誌位來記錄是否擷取到某許可權。
注意
即使支援了運行時許可權,也要在Manifest聲明,因為市場應用會根據這個資訊和硬體裝置進行匹配,決定你的應用是否在該裝置上顯示。
##是否支援運行時許可權
個人覺得Marshmallow的運行時許可權對於使用者來說絕對是一個好東西,但是目前想要支援需要做的事情還是比較多的。
對於一個有很多依賴的宿主應用,想要做到支援還是有一些工作量的,因為你的許可權申請受制於依賴。
建議在短期內暫時可以不考慮支援該運行時許可權機制,等時機成熟或者簡單易用的第三方庫完善之後再支援也未嘗不可。
聊一聊 Android 6.0 的運行時許可權