這是一篇遲來的文章,Android M已經發布一年多了(6.0的變化),在Android M中權限系統被從新設計,發生了顛覆性的變化,不少人把握很差這個變化,一是對這個權限策略和套路尚未摸透,二是沒有一個很好的實踐來支撐,在個人技術開發羣裏不少人問我關於權限的問題,每每我都沒有直接回答,由於這個問題不是一兩句說的清楚的,這幾點是今天我寫這篇文章的緣由。這裏有一切關於Android運行時權限你須要知道的,包括如何在代碼中實現,若是你之前不知道這些東西,如今來看也爲時不晚,我將在詳解以後給你一個最佳的實踐方案。html
運行時權限開源庫AndPermission:github.com/yanzhenjie/…。 java
若是你的英文夠好,推薦你閱讀官網的文章: android
正開始開始以前來幾張個人實例圖:git
Activity/Fragment中申請單個權限github
Activity/Fragment中同時申請多個權限shell
Activity/Fragment中被用戶拒絕後,下次申請時提醒用戶數組
在舊的權限管理系統中,權限僅僅在App安裝時詢問用戶一次,用戶贊成了這些權限App才能被安裝(某些深度定製系統另說),App一旦安裝後就能夠偷偷的作一些鮮爲人知的事情了。微信
在Android6.0開始,App能夠直接安裝,App在運行時一個一個詢問用戶授予權限,系統會彈出一個對話框讓用戶選擇是否受權某個權限給App(這個Dialog不能由開發者定製),當App須要用戶授予不恰當的權限的時候,用戶能夠拒絕,用戶也能夠在設置頁面對每一個App的權限進行管理。網絡
特別注意:這個對話框不是開發者調用某個權限的功能時由系統自動彈出,而是須要開發者手動調用,若是你直接調用而沒有去申請權限的話,將會致使App奔潰。異步
也許你已經開始慌了,這對於用戶來講是好事,可是對於開發者來講咱們不能直接調用方法了,咱們不得不在每個須要權限的地方檢查並請求用戶受權,因此就引出瞭如下兩個問題。
新的權限策略講權限分爲兩類,第一類是不涉及用戶隱私的,只須要在Manifest中聲明便可,好比網絡、藍牙、NFC等;第二類是涉及到用戶隱私信息的,須要用戶受權後纔可以使用,好比SD卡讀寫、聯繫人、短信讀寫等。
此類權限都是正常保護的權限,只須要在AndroidManifest.xml中簡單聲明這些權限便可,安裝即受權,不須要每次使用時都檢查權限,並且用戶不能取消以上受權,除非用戶卸載App。
- 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
- GET_PACKAGE_SIZE
- INSTALL_SHORTCUT
- INTERNET
- KILL_BACKGROUND_PROCESSES
- MODIFY_AUDIO_SETTINGS
- NFC
- READ_SYNC_SETTINGS
- READ_SYNC_STATS
- RECEIVE_BOOT_COMPLETED
- REORDER_TASKS
- REQUEST_IGNORE_BATTERY_OPTIMIZATIONS
- REQUEST_INSTALL_PACKAGES
- SET_ALARM
- SET_TIME_ZONE
- SET_WALLPAPER
- SET_WALLPAPER_HINTS
- TRANSMIT_IR
- UNINSTALL_SHORTCUT
- USE_FINGERPRINT
- VIBRATE
- WAKE_LOCK
- WRITE_SYNC_SETTINGS
全部危險的Android系統權限屬於權限組,若是APP運行在Android 6.0 (API level 23)
或者更高級別的設備中,並且targetSdkVersion>=23
時,系統將會自動採用動態權限管理策略,若是你在涉及到特殊權限操做時沒有作動態權限的申請將會致使App崩潰,所以你須要注意:
WRITE_CONTACTS
被受權了,App也有READ_CONTACTS
和GET_ACCOUNTS
了。READ_EXTERNAL_STORAGE
,系統會提示"容許xxx訪問設備上的照片、媒體內容和文件嗎?"
。若是App運行在Android 5.1 (API level 22)
或者更迭級別的設備中,或者targetSdkVersion<=22
時(此時設備能夠是Android 6.0 (API level 23)
或者更高),在全部系統中仍將採用舊的權限管理策略,系統會要求用戶在安裝的時候授予權限。其次,系統就告訴用戶App須要什麼權限組,而不是個別的某個權限。
- 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
使用adb命令能夠查看這些須要受權的權限組:
adb shell pm list permissions -d -g複製代碼
使用adb命令一樣能夠受權/撤銷某個權限:
adb shell pm [grant|revoke]... 複製代碼
只請求你須要的權限,減小請求的次數,或用Intent來代替,讓其餘的應用來處理。
防止一次請求太多的權限或請求次數太多,用戶可能對你的應用感到厭煩,在應用啓動的時候,最好先請求應用必須的一些權限,非必須權限在使用的時候才請求,建議整理並按照上述分類管理本身的權限:
解釋你的應用爲何須要這些權限:在你調用requestPermissions()
以前,你爲何須要這個權限。
requestpermissions()
以前告訴用戶你爲何須要這個權限。使用兼容庫support-v4
中的方法
ContextCompat.checkSelfPermission()
ActivityCompat.requestPermissions()
ActivityCompat.shouldShowRequestPermissionRationale()複製代碼
PackageManager中的兩個常量:
Activity中或者Fragment都會有如下幾個方法:
int checkSelfPermission(String) void requestPermissions(int, String...) boolean shouldShowRequestPermissionRationale(String) void onRequestPermissionsResult()複製代碼
上述四個方法中,前三個方法在support-v4
的ActivityCompat
中都有,建議使用兼容庫中的方法。最後一個方法是用戶受權或者拒絕某個權限組時系統會回調Activity或者Fragment中的方法。
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// 沒有權限,申請權限。
}else{
// 有權限了,去放肆吧。
}複製代碼
onRequestPermissionsResult()
時返回,用來判斷是哪一個受權申請的回調。onRequestPermissionsResult()
方法。ActivityCompat.requestPermissions(activity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);複製代碼
requestPermissions()
時的第一個參數。requestPermissions()
時的第二個參數。@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MMM: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 權限被用戶贊成,能夠去放肆了。
} else {
// 權限被用戶拒絕了,洗洗睡吧。
}
return;
}
}
}複製代碼
shouldShowRequestPermissionRationale()
後返回true,應該顯示一些爲何須要這個權限的說明。shouldShowRequestPermissionRationale()
後返回false。shouldShowRequestPermissionRationale()
返回false 。綜上所述,整合代碼後:
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {// 沒有權限。
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) {
// 用戶拒絕過這個權限了,應該提示用戶,爲何須要這個權限。
} else {
// 申請受權。
ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MMM);
}
}
...
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case MMM: {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 權限被用戶贊成,能夠去放肆了。
} else {
// 權限被用戶拒絕了,洗洗睡吧。
}
return;
}
}
}複製代碼
整體下來咱們應該對運行時權限有一個系統的認識,我總結出了一些套路:
- 須要區分各類Normal Permissioin和Dangerous Permissions。
- 判斷多個權限受權回調時須要判斷每個權限是否全都是被受權了,不然操做不能繼續。
- 須要請求多個權限時須要挨個檢查是否已經被受權過,沒受權的纔去請求,還要檢查這些權限是否須要提示用戶,若是多個權限都須要提示,該如何處理。
- 上述1 2 3若是在須要在多個頁面 實現,代碼重複。
- ...
其實問題遠遠不止這些,認真看過文章的人應該會發現,實現代碼比較簡單,可是代碼重複加上須要咱們考慮和注意的細節太多了,那麼下面我就爲你們介紹一個開源內褲來解決這一系列問題。
這個開源庫名叫AndPermission:github.com/yanzhenjie/…,通過個人實踐是徹底解決了上述問題,推薦你們使用,有興趣的朋友能夠去star下。
AndroidStudio使用方法,gradle一句話遠程依賴
compile 'com.yanzhenjie:permission:1.0.0'複製代碼
Or Maven:
<dependency>
<groupId>com.yanzhenjie</groupId>
<artifactId>permission</artifactId>
<version>1.0.0</version>
<type>pom</type>
</dependency>複製代碼
一、申請權限就是這麼簡單
AndPermission.with(this)
.requestCode(101)
.permission(Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_SMS,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
.send();複製代碼
只須要在Activity中或者Fragment中直接調用便可,AndPermission自動爲你打理好後宮。
二、接授權限回調更簡單 只須要重寫Activity/Fragment的一個方法,而後提供一個受權時回調的方法便可:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// 只須要調用這一句,剩下的AndPermission自動完成。
AndPermission.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
}
// 成功回調的方法,用註解便可,裏面的數字是請求時的requestCode。
@PermissionYes(100)
private void getLocationYes() {
// 申請權限成功,能夠去作點什麼了。
Toast.makeText(this, "獲取定位權限成功", Toast.LENGTH_SHORT).show();
}
// 失敗回調的方法,用註解便可,裏面的數字是請求時的requestCode。
@PermissionNo(100)
private void getLocationNo() {
// 申請權限失敗,能夠提醒一下用戶。
Toast.makeText(this, "獲取定位權限失敗", Toast.LENGTH_SHORT).show();
}複製代碼
只須要上面這麼幾句話便可,你就能夠大刀闊斧的幹了,在總結中提到的各類判斷、複雜的狀況AndPermission自動完成。
三、若是你須要在用戶屢次拒絕權限後提示用戶
AndPermission.with(this)
.requestCode(101)
.permission(Manifest.permission.WRITE_CONTACTS,
Manifest.permission.READ_SMS,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
.rationale(mRationaleListener)
.send();
private RationaleListener mRationaleListener = new RationaleListener() {
@Override
public void showRequestPermissionRationale(int requestCode, final Rationale rationale) {
new AlertDialog.Builder(RationalePermissionActivity.this)
.setTitle("友好提醒")
.setMessage("沒有定位權限將不能爲您推薦附近妹子,請把定位權限賜給我吧!")
.setPositiveButton("好,給你", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
rationale.resume();// 用戶贊成繼續申請。
}
})
.setNegativeButton("我拒絕", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
rationale.cancel(); // 用戶拒絕申請。
}
}).show();
}
};複製代碼
這麼作的好處請看上面shouldShowRequestPermissionRationale()
方法的介紹。
基本上就到這裏啦,你們有疑問能夠關注個人微信,微信公衆號搜索:嚴振杰,便可關注我。