轉載地址:http://blog.csdn.net/gaugamela/article/details/56277793
一直以來,爲了保證最大的安全性,安裝Android應用時,系統總是讓用戶選擇是否同意該應用所需的所有權限。
一旦安裝應用,就意味着該應用所需的所有權限均已獲得。
若在使用某個功能時用到了某個權限,系統將不會提醒用戶該權限正在被獲取(比如微信需要使用攝像頭拍照,
在Android 6.0以前的設備上,用戶將不會被系統告知正在使用“使用系統攝像頭”的權限)。
這在安全性上是個隱患:在不經用戶同意的情況下,一些應用在後臺可以自由地收集用戶隱私信息而不被用戶察覺。
爲了解決這個問題,從Android 6.0版本開始,在安裝應用時,該應用無法取得任何權限。
相反,在使用應用的過程中,若某個功能需要獲取某個權限,系統會彈出一個對話框,顯式地由用戶決定是否將該權限賦予應用。
只有得到了用戶的許可,該功能纔可以被使用。
需要注意的是,賦予權限的對話框並不會自動彈出,而需要由開發者手動調用。
若程序調用的某個方法需要用戶賦予相應權限,而此時該權限並未被賦予時,那麼程序就會拋出異常並崩潰。
除此之外,用戶還可以在任何時候,通過設置中的應用管理撤銷賦予過的權限。
應用的targetSDKVersion < 23時,權限檢查仍是早期的形式(僅在安裝時賦予權限,使用時將不被提醒);
應用的targetSDKVersion ≥ 23時,則將使用新的運行時權限規則。
以下羅列了在安裝應用時,自動賦予應用的權限,這些權限無法在安裝後手動撤銷,我們稱其爲基本權限(Normal Permission)。
開發者僅需要在AndroidManifest.xml中聲明這些權限,應用就能自動獲取無需用戶授權。
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.FLASHLIGHT
android.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
運行時權限被劃分成權限組(Permission Group),如下表所示:
若應用被賦予了某個權限組中的一個權限(比如READ_CONTACTS權限被賦予),
那麼該組中的其他權限將被自動獲取(WRITE_CONTACTS和GET_ACCOUNTS權限被自動獲取)。
檢查和申請權限的方法分別是Activity.checkSelfPermission()和Activity.requestPermissions,這兩個方法是在 API 23 中新增的。
代碼示例如下:
............
mDialButton = (Button) v.findViewById(R.id.crime_dial);
mDialButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= 23) {
//我是在Fragment裏寫代碼的,因此調用getActivity
//如果不想判斷SDK,可以使用ActivityCompat的接口來檢查和申請權限
int hasReadContactsPermission = getActivity().checkSelfPermission(
android.Manifest.permission.READ_CONTACTS);
if (hasReadContactsPermission != PackageManager.PERMISSION_GRANTED) {
//這裏就會彈出對話框
getActivity().requestPermissions(
new String[] {Manifest.permission.READ_CONTACTS},
ASK_READ_CONTACTS_PERMISSION);
return;
}
//高版本中檢查是否有運行時權限,具有權限時才調用
getPhoneNumberAndDial();
} else {
//在AndroidManifest.xml中仍然聲明使用"android.permission.READ_CONTACTS"
//在低版本中直接調用該函數
getPhoneNumberAndDial();
}
}
});
............
如前文所述,在Android高版本中,應用初始安裝時,即使聲明瞭運行時權限,Android系統也不會爲其賦予任何權限。
如下圖所示:
此時,點擊按鍵調用Activity的requestPermissions時,將會彈出對話框,類似於下圖所示(不同設備商有不同的定製):
無論選擇的是“允許”還是“拒絕”,系統都將回調Activity.onRequestPermissionsResult()方法,
並將選擇的結果傳到方法的第三個參數中。
此時的處理代碼示例如下:
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode) {
case ASK_READ_CONTACTS_PERMISSION:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getPhoneNumberAndDial();
} else {
Toast.makeText(getContext(),
"READ_CONTACTS Denied",
Toast.LENGTH_SHORT)
.show();
}
return;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
需要注意的是:
目前,如果在原生的Fragment中調用Activity.requestPermissions函數時,
onRequestPermissionsResult必須實現在包含該Fragment的Activity中。
如果是在android.support.v4.app.Fragment中實現相同功能時,
就可以直接在Fragment中定義onRequestPermissionsResult函數,
並可以藉助於兼容庫,此時代碼如下:
................
mDialButton = (Button) v.findViewById(R.id.crime_dial);
mDialButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//checkSelfPermission、requestPermissions均是調用兼容庫中的函數,此時不再需要判斷SDK
int hasReadContactsPermission = checkSelfPermission(getContext(),
android.Manifest.permission.READ_CONTACTS);
if (hasReadContactsPermission != PackageManager.PERMISSION_GRANTED) {
requestPermissions(
new String[] {Manifest.permission.READ_CONTACTS},
ASK_READ_CONTACTS_PERMISSION);
return;
}
getPhoneNumberAndDial();
}
});
..................
關於該問題的討論可以參考:onRequestPermissionsResult not being called in dialog fragment和Android M Permissions: onRequestPermissionsResult() not being called
如上圖所示,每當系統申請權限時,彈出的對話框會有一個類似於“拒絕後不再詢問”的勾選項。
若用戶打了勾,並選擇拒絕,那麼下次程序調用Activity.requestPermissions()方法時,將不會彈出對話框,權限也不會被賦予。
這種沒有反饋的交互並不是一個好的用戶體驗。
所以,下次啓動時,程序應彈出一個對話框,提示用戶類似於“您已經拒絕了使用該功能所需要的權限,若需要使用該功能,請手動開啓權限”的信息,
此時應調用Activity.shouldShowRequestPermissionRationale()方法,示例代碼如下:
............
mDialButton = (Button) v.findViewById(R.id.crime_dial);
mDialButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int hasReadContactsPermission = checkSelfPermission(getContext(),
android.Manifest.permission.READ_CONTACTS);
if (hasReadContactsPermission != PackageManager.PERMISSION_GRANTED) {
//判斷是否點擊過“拒絕並不再提示”,若點擊了,則應用自己彈出一個Dialog
if (!shouldShowRequestPermissionRationale(android.Manifest.permission.READ_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.READ_CONTACTS},
ASK_READ_CONTACTS_PERMISSION);
}
});
return;
}
requestPermissions(
new String[] {Manifest.permission.READ_CONTACTS},
ASK_READ_CONTACTS_PERMISSION);
return;
}
getPhoneNumberAndDial();
}
});
...............
private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
new AlertDialog.Builder(getContext())
.setMessage(message)
.setPositiveButton("OK", okListener)
.setNegativeButton("Cancel", null)
.create()
.show
此時,應用第一次申請權限及用戶勾選了“不再詢問”複選框時,均會彈出類似如下的對話框:
若第一次申請時點擊OK,將會彈出權限申請的界面;
用戶勾選過“拒絕後不再詢問時”,點擊OK不會再次拉起申請界面,
同時onRequestPermissionsResult中收到的結果爲PackageManager.PERMISSION_DENIED。
最後看看同時申請多個運行時權限的代碼示例,其思想基本與前文一致,這段代碼是直接借鑑過來的:
final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
private void insertDummyContactWrapper() {
//提示用戶需要手動開啓的權限集合
List<String> permissionsNeeded = new ArrayList<String>();
//功能所需權限的集合
final List<String> permissionsList = new ArrayList<String>();
//若用戶拒絕了該權限申請,則將該申請的提示添加到“用戶需要手動開啓的權限集合”中
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 ";
for (int i = 0; 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<String> permissionsList, String permission) {
//若配置了該權限,返回true
if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
//若未配置該權限,將其添加到所需權限的集合,返回true
permissionsList.add(permission);
// 若用戶勾選了“永不詢問”複選框,並拒絕了權限,則返回false
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集合,其中Key存放所需權限,Value存放該權限是否被賦予
Map<String, Integer> perms = new HashMap<String, Integer>();
// 向Map集合中加入元素,初始時所有權限均設置爲被賦予(PackageManager.PERMISSION_GRANTED)
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);
// 將第二個參數回傳的所需權限及第三個參數回傳的權限結果放入Map集合中,由於Map集合要求Key值不能重複,所以實際的權限結果將覆蓋初始值
for (int i = 0; i < permissions.length; i++)
perms.put(permissions[i], grantResults[i]);
// 若所有權限均被賦予,則執行方法
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();
}
//否則彈出toast,告知用戶需手動賦予權限
else {
// Permission Denied
Toast.makeText(MainActivity.this,
"Some Permission is Denied",
Toast.LENGTH_SHORT)
.show();
}
}
break;
default:
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
P.S.
根據permission名稱,獲取PermissionInfo的代碼類似於:
................
public static List<PermissionInfo> getPermissionInfos(Context context,
String... permissions) {
List<PermissionInfo> rst = new ArrayList<>();
PackageManager pm = context.getPackageManager();
try {
for (String name : permissions) {
rst.add(pm.getPermissionInfo(name, 0));
}
} catch (PackageManager.NameNotFoundException e) {
}
return rst;
}
..............
PermissionInfo中主要保存Permission對應的ProtectionLevel。