翻譯自:
https://developer.android.com/training/permissions/declaring.html
歡迎大家指正討論
使用markdown編寫的效果發佈出來和預覽的不一樣啊,圖片還無法顯示,希望知道的同學指點一下.
另外前後字體怎麼無法統一啊 ,看起來很糾結,希望達人指點!!!
在Android6.0中,有一個重要的變化,就是對權限系統進行了重新的設計,引入了動態權限,即Runtime Permission的概念.這一變化很重要,我們之前的代碼可能需要比較大的改變才能做出適配.國外有個哥們針對這個主題發表了一篇博客,而我剛好遇到這個事情,就翻譯一下,做個記錄.
1.新的動態權限系統
一直以來,Android的權限系統都是最大的安全問題之一,因爲在進行安裝的時候,所有的權限都會統一進行請求,你必須允許這些權限請求才能進行安裝.應用安裝後,就可以在用戶毫不知情的情況下,訪問這些權限.所以有很多應用利用這一漏洞,偷偷地進行蒐集用戶個人信息或者其他進行其他用途,可以自行腦補一下.
Android開發團隊顯然也意識到這個問題了,終於在七年後,重新設計了權限系統.在最新的Android6.0 Marshmallow中,安裝應用的時候不再需要允許任何權限的請求了,相反,應用必須在運行的時候獲取用戶的權限許可.(注:這樣用戶就會意識到應用正在獲取權限,如果是敏感權限,就會引起用戶的注意,而不再是之前的情況,用戶對應用獲取敏感權限毫無察覺)
請注意,上圖顯示的權限請求對話框並不會自動的加載,而是需要開發者手動的調用.當開發者嘗試調用的方法需要某個權限,而用戶並沒有允許應用使用該權限的時候,你猜會怎麼樣... !
另外,用戶可以在任何時候,通過設置,來撤銷之前給應用賦予的權限.
作爲一名程序猿,這個時候你可能意識到出大事了,你不再可以像之前那樣,只需要簡單地調用某個方法來完成一項工作,現在,你必須要在每一次涉及相關權限的操作進行之前,檢查該權限是否被用戶允許.否則,很簡單,崩潰.
對於用戶來說,這是一種進步,然而對於程序猿們來說,這真是一場噩夢.我們必須對源代碼進行適配工作.
好消息是,動態權限系統只有在應用將targetSdkVersion設爲23之後纔會奏效,而且這一特性必須是在Android6.0上才能運行,這爲我們的適配工作贏得了時間.
2.已有應用會遇到什麼問題
新的動態權限系統可能會立刻引起你的恐慌,"我3年前的應用怎麼辦,如果把它安裝在Android6.0的機器上,它是否能正常運行,還是會崩潰?!?"
不必擔心,Android開發團隊已經考慮過這個問題了.如果應用的targetSdkVersion小於23,那麼它就會被假定爲並未通過動態權限系統的測試,它依然會使用之前的權限系統:在安裝的時候向用戶請求所有需要的權限,用戶必須接受這些權限請求才能進行安裝. 結果就是,應用依然會像之前那樣運行良好.但是請注意,此時用戶仍然可以在安裝之後撤銷賦予應用的權限,雖然系統會在用戶這麼做的時候發出警告.
下一刻,你可能擔心自己的應用在用戶撤銷權限之後發生崩潰了!!!
Android開發組團隊給我們送了一個福利,在應用的targetSdkVersion小於23,用戶撤銷了某項權限之後,我們調用需要該項權限的方法後,不會拋出任何異常.調用的方法什麼也不會做,而返回值則是看情況返回null或者0.
不要高興的太早,儘管調用方法不會產生異常,但是你很可能在處理返回值的時候發生問題.(注:這個時候可以體現出對返回值檢查判斷的重要性了) 目前來說,由於新版本的普及問題,發生這種事的機率很小.用戶撤銷權限後,可能就會意識到將會產生問題,就像系統警告的那樣.但是將來肯定會發生很多用戶撤銷權限許可的問題,我們必須對應用進行適配以便在新的手機上進行使用.
在你完成對Android6.0動態權限的適配之前,切勿將應用的targetSdkVersion設爲23,否則將會使你陷入到適配問題中去.
注意在你使用Android Studio創建新工程的時候,它會自動地將工程的targetSdkVersion設爲最新的版本,因此在你的應用支持動態權限之前,建議你修改一下targetSdkVersion
3.默認許可權限
還有一些權限是在安裝的時候默認許可並且不可撤銷的,我們把它們叫做普通權限,這些權限如下:
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.FLASHLIGHTandroid.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.READ_SYNC_SETTINGS
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
這些權限和以前一樣使用,你無需檢查它們是否被撤銷了
4.使應用支持動態權限
現在進入正題,如果使我們的應用完美的支持動態權限這一新特性呢?首先,將應用的compileSdkVersion和targetSdkVersion都設爲23
android {
compileSdkVersion 23
...
defaultConfig {
...
targetSdkVersion 23
...
}
private static final String TAG = "Contacts";
private void insertDummyContact() {
// Two operations are needed to insert a new contact.
ArrayList operations = new ArrayList(2);
// First, set up a new raw contact.
ContentProviderOperation.Builder op =
ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI)
.withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, null)
.withValue(ContactsContract.RawContacts.ACCOUNT_NAME, null);
operations.add(op.build());
// Next, set the name for the contact.
op = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI)
.withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0)
.withValue(ContactsContract.Data.MIMETYPE,
ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE)
.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME,
"__DUMMY CONTACT from runtime permissions sample");
operations.add(op.build());
// Apply the operations.
ContentResolver resolver = getContentResolver();
try {
resolver.applyBatch(ContactsContract.AUTHORITY, operations);
} catch (RemoteException e) {
Log.d(TAG, "Could not add a new contact: " + e.getMessage());
} catch (OperationApplicationException e) {
Log.d(TAG, "Could not add a new contact: " + e.getMessage());
}
}
<uses-permissionandroid:name="android.permission.WRITE_CONTACTS"/>
然後我們需要創建一個方法來檢查該權限是否得到了許可.如果沒有獲得許可,那麼我們使用dialog來詢問用戶是否許可,如果權限已經獲得許可的話,我們就直接創建一個新的聯繫人即可.如果一個權限獲得了許可,那麼和它同組的其他權限也會自動的獲得許可.比如說,你得到了WRITE_CONTACTS的權限許可,那麼該組裏面的READ_CONTACTS和GET_ACCOUNTS也會同時自動獲得許可.
在Android6.0的源碼中,Activity的源碼添加了checkSelfPermission和requestPermissions方法用來檢查和請求權限
final private int REQUEST_CODE_ASK_PERMISSIONS = 123;
private void insertDummyContactWrapper() {
int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_ASK_PERMISSIONS);
return;
}
insertDummyContact();
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_PERMISSIONS:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted
insertDummyContact();
} else {
// Permission Denied
Toast.makeText(MainActivity.this, "WRITE_CONTACTS Denied", Toast.LENGTH_SHORT)
.show();
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
5.處理Never Ask Again
如果Never Ask Again選項被選中了而且用戶拒絕了權限的許可,當下一次我們調用requestPermissions方法來請求同一權限(對同組的權限呢?未測試)的許可的時候,這個dialog將不會出現了.
當用戶做了交互之後,卻毫無反應,這是相當糟糕的用戶體驗,因此我們要在這種情況下做一下特別的處理:當我們調用requestPermissions之前,我們需要先確定一下是否需要通過Activity的shouldShowRequestPermissionRationale方法來說明一下應用需要權限許可的理由.使用方法如下:
final private int REQUEST_CODE_ASK_PERMISSIONS = 123;
private void insertDummyContactWrapper() {
int hasWriteContactsPermission = checkSelfPermission(Manifest.permission.WRITE_CONTACTS);
if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
if (!shouldShowRequestPermissionRationale(Manifest.permission.WRITE_CONTACTS)) {
showMessageOKCancel("You need to allow access to Contacts",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_ASK_PERMISSIONS);
}
});
return;
}
requestPermissions(new String[] {Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_ASK_PERMISSIONS);
return;
}
insertDummyContact();
}
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(MainActivity.this)
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show();
}
該方法的調用效果就是,會在第一次請求某個權限的許可或者用戶在該權限的請求中選中"Nerver Ask Again"後拒絕許可的情況下,顯示一個請求權限許可的dialog.在後一種情況下,onRequestPermissionsResult會被調用並返回PERMISSION_DENIED,並且不會出現權限請求的dialog.
搞定!
6.一次請求多個權限許可
final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
private void insertDummyContactWrapper() {
List permissionsNeeded = new ArrayList();
final List permissionsList = new ArrayList();
if (!addPermission(permissionsList, Manifest.permission.ACCESS_FINE_LOCATION))
permissionsNeeded.add("GPS");
if (!addPermission(permissionsList, Manifest.permission.READ_CONTACTS))
permissionsNeeded.add("Read Contacts");
if (!addPermission(permissionsList, Manifest.permission.WRITE_CONTACTS))
permissionsNeeded.add("Write Contacts");
if (permissionsList.size() > 0) {
if (permissionsNeeded.size() > 0) {
// Need Rationale
String message = "You need to grant access to " + permissionsNeeded.get(0);
for (int i = 1; i < permissionsNeeded.size(); i++)
message = message + ", " + permissionsNeeded.get(i);
showMessageOKCancel(message,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
}
});
return;
}
requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
return;
}
insertDummyContact();
}
private boolean addPermission(List permissionsList, String permission) {
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
permissionsList.add(permission);
// Check for Rationale Option
if (!shouldShowRequestPermissionRationale(permission))
return false;
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS:
{
Map perms = new HashMap();
// Initial
perms.put(Manifest.permission.ACCESS_FINE_LOCATION, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.READ_CONTACTS, PackageManager.PERMISSION_GRANTED);
perms.put(Manifest.permission.WRITE_CONTACTS, PackageManager.PERMISSION_GRANTED);
// Fill with results
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
// Check for ACCESS_FINE_LOCATION
if (perms.get(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED
&& perms.get(Manifest.permission.WRITE_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
// All Permissions Granted
insertDummyContact();
} else {
// Permission Denied
Toast.makeText(MainActivity.this, "Some Permission is Denied", Toast.LENGTH_SHORT)
.show();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
7.使用support library來適配代碼
// Marshmallow+
} else {
// Pre-Marshmallow
}
private void insertDummyContactWrapper() {
int hasWriteContactsPermission = ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.WRITE_CONTACTS);
if (hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.WRITE_CONTACTS)) {
showMessageOKCancel("You need to allow access to Contacts",
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this,
new String[] {Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_ASK_PERMISSIONS);
}
});
return;
}
ActivityCompat.requestPermissions(MainActivity.this,
new String[] {Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_ASK_PERMISSIONS);
return;
}
insertDummyContact();
}
8.使用第三方包來適配
9.當應用使用時撤銷權限會怎樣
10.總結和建議
Good Luck!