Android 6.0動態許可權介紹與處理
一、Android 6.0許可權介紹
從 Android 6.0(API 級別 23)開始,使用者開始在應用執行時向其授予許可權,而不是在應用安裝時授予。 Android 6.0系統6.0以前,所有的許可權,訪問網路的許可權,讀取SD卡的許可權,訪問通訊錄,撥打電話的許可權都在安裝的時候系統授予了要安裝的應用。在系統執行的時候不需要對許可權做任何的處理。使用者在安裝的時候一般都不知道這些許可權用在了什麼地方,這樣做很不安全。一個應用很容易在一個Service中讀取使用者所有的通訊錄資訊傳送到伺服器上。Android 6.0後的動態許可權讓我們的系統更加安全,犧牲了使用者的方便性,得到的是安全。下圖是Android 6.0系統對撥打電話和管理電話許可權組的詢問。
Android 6.0把許可權分為兩種:Normal Permissions(正常許可權)和Dangerous Permissions(危險許可權)。其中危險許可權又進行了分類。把所有的危險許可權分成了幾個組。正常許可權不會給使用者的隱私帶來不安全,不需要動態申請,在應用安裝的時候就已經被授予了。危險許可權需要動態處理,只有使用者批准了這些許可權,應用才能被授予這些許可權。
所有的普通許可權:
- 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 6.0+危險許可權的動態處理
在開發應用的時候不管是正常許可權還是危險許可權都必須在應用的Manifest.xml檔案中宣告。如果裝置執行的是Android 5.1或更低的系統,或者應用的目標SDK小於23,那麼在Manifest.xml檔案中列出的危險許可權在安裝的時候使用者必須接受,要不應用沒法安裝。如果裝置執行的是Android 6.0或更高的系統,或者應用的目標SDK大於等於23,那麼在Manifest.xml檔案中列出的危險許可權,會在應用執行的時候被使用者授予或拒絕。開發者需要在程式碼中對危險許可權進行處理。
public class MainActivity extends AppCompatActivity {
final public static int REQUEST_CODE_ASK_CALL_PHONE = 123;
private String mMobile;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button).setOnClickListener(new
View.OnClickListener() {
@Override
public void onClick(View v) {
onCallWrapper("12345678912");
}
});
}
public void onCallWrapper(String mobile) {
this.mMobile = mobile;
int checkCallPhonePermisssion = ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE);
if (checkCallPhonePermisssion != PackageManager.PERMISSION_GRANTED) {
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
Toast.makeText(this, "shouldShowRequestPermissionRationale", Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(this)
.setTitle("提示")
.setMessage("應用需要開啟拍照的許可權,是否繼續?")
.setPositiveButton("確定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
}).show();
} else {
ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.CALL_PHONE}, REQUEST_CODE_ASK_CALL_PHONE);
}
} else {
callDirectly(mobile);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode) {
case REQUEST_CODE_ASK_CALL_PHONE:
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission Granted 授予許可權
callDirectly(mMobile);
} else {
// Permission Denied 許可權被拒絕
Toast.makeText(MainActivity.this, "Permission Denied",
Toast.LENGTH_SHORT).show();
}
break;
default:
break;
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void callDirectly(String mobile) {
Intent intent = new Intent();
intent.setAction("android.intent.action.CALL");
intent.setData(Uri.parse("tel:" + mobile));
this.startActivity(intent);
}
}
上面這個簡單的例子是通過點選一個按鈕撥打電話。在6.0系統之前,我們只需要直接呼叫方法callDirectly()方法就可以了。增加了系統的安全性,但是多了很多程式碼上的處理。ContextCompat和
ActivityCompat分別是Context和Activity的相容類,在上的程式碼中我們就不用去判斷當前系統的API是多好了,在API23之前也能用上面程式碼中的方法。ContextCompat.checkSelfPermission()方法是用來判斷,當前應用是否擁有某個許可權。如果擁有我們可以直接呼叫callDirectly()方法撥打電話。如果當前應用沒有撥打電話的許可權,會調ActivityCompat.shouldShowRequestPermissionRationale()方法,注意當前系統還沒有彈出讓使用者去選擇是允許還是拒絕,這個時候shouldShowRequestPermissionRationale()方法返回false。所以接下來會執ActivityCompat.requestPermissions()方法會向系統去請求,系統會彈出一個對話方塊讓使用者去選擇。onRequestPermissionsResult()方法用來處理使用者的選擇。這個方法可以監聽使用者選擇的是允許還是拒絕。當用戶選擇了允許,那麼直接呼叫callDirectly()方法撥打電話。當用戶選擇了拒絕,上面的程式碼只是提示一個Toast。
當用戶選擇了拒絕,那麼在下次點選按鈕撥打電話的時候ActivityCompat.shouldShowRequestPermissionRationale()方法會返回true,在上面的程式碼中我們會彈出一個對話方塊給使用者,給使用者一個提示。當用戶選擇確定,會向系統請求許可權。當用戶選擇取消,關閉對話方塊什麼也不做。當我們選擇了不在提示並且選擇了拒絕的時候ActivityCompat.shouldShowRequestPermissionRationale()方法返回false。
三、用Easy Permissions開源庫處理許可權
1.配置
在app層的build.gradle中
dependencies {
// EasyPermissions
compile 'pub.devrel:easypermissions:0.3.0'
}
在app層的build.gradle中
dependencies {
// EasyPermissions
compile 'pub.devrel:easypermissions:0.3.0'
}
2.舉例
public class SecondActivity extends AppCompatActivity implements EasyPermissions.PermissionCallbacks {
private static final int RC_CAMERA_PERM = 123;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
cameraTask();
}
});
}
@AfterPermissionGranted(RC_CAMERA_PERM)
public void cameraTask() {
if (EasyPermissions.hasPermissions(this, Manifest.permission.CAMERA)) {
// 該應用已經有打電話的許可權
Toast.makeText(this, "TODO: Camera things", Toast.LENGTH_LONG).show();
} else {
EasyPermissions.requestPermissions(this, "需要獲取系統的拍照的許可權!", RC_CAMERA_PERM, Manifest.permission.CAMERA);
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
}
@Override
public void onPermissionsGranted(int requestCode, List<String> perms) {
Toast.makeText(this, "onPermissionsGranted", Toast.LENGTH_SHORT).show();
}
@Override
public void onPermissionsDenied(int requestCode, List<String> perms) {
Toast.makeText(this, "onPermissionsDenied", Toast.LENGTH_SHORT).show();
new AppSettingsDialog.Builder(this)
.setTitle("請求許可權")
.setRationale("需要開啟啟用相機的許可權才能繼續下去!")
.build()
.show();
}
}
這個例子是點選一個按鈕,處理拍照的事情,所以我們需要獲取相機的許可權。首先我們要複寫Activity或者Fragment的onRequestPermissionsResult()方法。SecondActivity實現了EasyPermissions.PermissionCallbacks這個介面複寫了onPermissionsGranted()和onPermissionsDenied()這兩個介面。當用戶選擇了允許那麼呼叫onPermissionsGranted()方法,當用戶選擇了拒絕那麼呼叫onPermissionsDenied()方法。AppSettingsDialog是一個詢問使用者是否跳轉到執行應用的設定介面去開啟許可權的對話方塊。
四、用Permissions Dispatcher開源庫處理許可權
1.配置
在app層的build.gradle中
dependencies {
// Permissions Dispatcher
compile 'com.github.hotchemi:permissionsdispatcher:2.3.1'
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:2.3.1'
}
這種配置需要有個條件:Android Gradle Plugin >= 2.2
2.舉例
這個庫用到了5個註解:
註解 | 是否必須 | 描述 |
---|---|---|
@RuntimePermissions | 是 | 註冊在Acttivity或者Fragment上 |
@NeedsPermission | 是 | 再需要許可權的方法上註冊 |
@OnShowRationale | 否 | 被註解的方法可以提示為什麼需要這個許可權 |
@OnPermissionDenied | 否 | 如果使用者拒絕了許可權申請那麼呼叫該方法 |
@OnNeverAskAgain | 否 | 如果使用者選擇了不再詢問,呼叫該方法 |
這個庫用到了Annotion Processor,需要用到一個應用編譯期間產生的類,這個類的命名是:類名+PermissionsDispatcher。
@RuntimePermissions
public class ThirdActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_third);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ThirdActivityPermissionsDispatcher.showContactsWithCheck(ThirdActivity.this);
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
ThirdActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
@NeedsPermission({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void showContacts() {
Toast.makeText(this, "讀取通訊錄", Toast.LENGTH_SHORT).show();
}
@OnPermissionDenied({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void onContactsDenied() {
Toast.makeText(this, "onContactsDenied", Toast.LENGTH_SHORT).show();
}
@OnNeverAskAgain({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void onContactsNeverAskAgain() {
Toast.makeText(this, "onContactsNeverAskAgain", Toast.LENGTH_SHORT).show();
new AppSettingsDialog.Builder(this)
.setTitle("請求許可權")
.setRationale("需要開啟啟用相機的許可權才能繼續下去!")
.build()
.show();
}
@OnShowRationale({Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS})
public void showRationaleContacts(final PermissionRequest request) {
// Toast.makeText(this, "showRationaleContacts", Toast.LENGTH_SHORT).show();
new AlertDialog.Builder(this)
.setPositiveButton("繼續", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.proceed();
}
})
.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(@NonNull DialogInterface dialog, int which) {
request.cancel();
}
})
.setCancelable(false)
.setMessage("需要通訊錄許可權")
.show();
}
}
具體的過程可以參考Demo。