Android新特性解析一:執行時許可權
Android新特性解析一:執行時許可權
關於作者
郭孝星,程式設計師,吉他手,主要從事Android平臺基礎架構方面的工作,歡迎交流技術方面的問題,可以去我的Github提issue或者發郵件至guoxiaoxingse@163.com與我交流。
文章資源
在討論執行時許可權以前,我們先來回憶一下以前的許可權使用,通常我們申請一個許可權,必須在應用manifest檔案中包含一個或多個 <uses-permission> 標記。
例如,需要監控傳入的簡訊的應用要指定:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
</manifest>
Android 6.0開始引入了新的執行時許可權檢查授權機制,替代了之前安裝應用的時候對許可權進行授權的方案。該方案將許可權分為正常許可權和危險許可權,對於
正常許可權我們想以前一樣在manifest裡註冊即可,但是對於危險許可權即便我們註冊了,它還是會在執行時進行許可權檢查,這便是執行時許可權。
具體說來:
- 如果裝置執行的是 Android 6.0(API 級別 23)或更高版本,並且應用的 targetSdkVersion 是 23 或更高版本,則應用在執行時向使用者請求許可權。用
戶可隨時呼叫許可權,因此應用在每次執行時均需檢查自身是否具備所需的許可權。如需瞭解在應用中請求許可權的更多資訊,請參閱使用系統許可權培訓指南。 - 如果裝置執行的是 Android 5.1(API 級別 22)或更低版本,並且應用的 targetSdkVersion 是 22 或更低版本,則系統會在使用者安裝應用時要求使用者
授予許可權。如果將新許可權新增到更新的應用版本,系統會在使用者更新應用時要求授予該許可權。使用者一旦安裝應用,他們撤銷許可權的唯一方式是解除安裝應用。通常,
許可權失效會導致 SecurityException 被扔回應用。但不能保證每個地方都是這樣。例如,sendBroadcast(Intent) 方法在資料傳遞到每個接收者時會檢查
許可權,在方法呼叫返回後,即使許可權失效,您也不會收到異常。但在幾乎所有情況下,許可權失效會記入系統日誌。
許可權型別
系統許可權分為兩類,正常許可權和危險許可權:
- 正常許可權涵蓋應用需要訪問其沙盒外部資料或資源,但對使用者隱私或其他應用操作風險很小的區域。例如,設定時區的許可權就是正常許可權。如果應用宣告其需要
正常許可權,系統會自動向應用授予該許可權。 - 危險許可權涵蓋應用需要涉及使用者隱私資訊的資料或資源,或者可能對使用者儲存的資料或其他應用的操作產生影響的區域。例如,能夠讀取使用者的聯絡人屬於危險
許可權。如果應用宣告其需要危險許可權,則使用者必須明確嚮應用授予該許可權。
正常許可權
- 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
參考連結
https://developer.android.com/guide/topics/security/normal-permissions.html
危險許可權
危險許可權是分組的,同一組的任何一個許可權被授權了,其他許可權也自動被授權。例如,一旦WRITE_CONTACTS被授權了,app也有WRITE_CONTACTS
和GET_ACCOUNTS許可權了。
group:android.permission-group.CONTACTS
- permission:android.permission.WRITE_CONTACTS
- permission:android.permission.GET_ACCOUNTS
- permission:android.permission.WRITE_CONTACTS
group:android.permission-group.PHONE
- permission:android.permission.READ_CALL_LOG
- permission:android.permission.READ_PHONE_STATE
- permission:android.permission.CALL_PHONE
- permission:android.permission.WRITE_CALL_LOG
- permission:android.permission.USE_SIP
- permission:android.permission.PROCESS_OUTGOING_CALLS
- permission:com.android.voicemail.permission.ADD_VOICEMAIL
group:android.permission-group.CALENDAR
- permission:android.permission.READ_CALENDAR
- permission:android.permission.WRITE_CALENDAR
group:android.permission-group.CAMERA
- permission:android.permission.CAMERA
group:android.permission-group.SENSORS
- permission:android.permission.BODY_SENSORS
group:android.permission-group.LOCATION
- permission:android.permission.ACCESS_FINE_LOCATION
- permission:android.permission.ACCESS_COARSE_LOCATION
group:android.permission-group.STORAGE
- permission:android.permission.READ_EXTERNAL_STORAGE
- permission:android.permission.WRITE_EXTERNAL_STORAGE
group:android.permission-group.MICROPHONE
- permission:android.permission.RECORD_AUDIO
group:android.permission-group.SMS
- permission:android.permission.READ_SMS
- permission:android.permission.RECEIVE_WAP_PUSH
- permission:android.permission.RECEIVE_MMS
- permission:android.permission.RECEIVE_SMS
- permission:android.permission.SEND_SMS
- permission:android.permission.READ_CELL_BROADCASTS
我們可以使用 adb 工具從命令列管理許可權:
按組列出許可權和狀態:
$ adb shell pm list permissions -d -g
授予或撤銷一項或多項許可權:
$ adb shell pm [grant|revoke] <permission-name> ...
參考連結
https://developer.android.com/guide/topics/security/permissions.html#permissions
執行時許可權處理流程
1 向清單新增許可權
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.snazzyapp">
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<application ...>
...
</application>
</manifest>
2 檢查許可權
如果我們的應用需要危險許可權,則每次執行需要這一許可權的操作時您都必須檢查自己是否具有該許可權。使用者始終可以自由呼叫此許可權,因此,即使應用昨天使用了相機,它
不能假設自己今天仍具有該許可權。要檢查是否具有某項許可權,可以呼叫 ContextCompat.checkSelfPermission() 方法。
例如,以下程式碼段顯示瞭如何檢查 Activity 是否具有寫入聯絡人的許可權:
// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
Manifest.permission.WRITE_CONTACTS);
如果應用具有此許可權,方法將返回 PackageManager.PERMISSION_GRANTED,並且應用可以繼續操作。如果應用不具有此許可權,方法將返回 PERMISSION_DENIED,且應用必須明確向使用者要求許可權。
3 請求許可權
//請求許可權
ActivityCompat.requestPermissions(PermissionActivity.this,
new String[]{Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_FOR_PERMISSION_CALLBACK);
該方法會彈出一個系統對話方塊,來供使用者選擇是否允許該許可權申請,當然使用者可能會拒絕我們的許可權申請,這種情況下說明使用者不理解我們
為什麼要申請這個許可權,這個時候最好的做法是給使用者一個解釋,如下所示:
//是否給使用者一個關於許可權申請的解釋
if (ActivityCompat.shouldShowRequestPermissionRationale(PermissionActivity.this,
Manifest.permission.WRITE_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
//請求許可權
ActivityCompat.requestPermissions(PermissionActivity.this,
new String[]{Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_FOR_PERMISSION_CALLBACK);
}
注:如果應用之前請求過此許可權但使用者拒絕了請求,此方法將返回 true。如果使用者在過去拒絕了許可權請求,並在許可權請求系統對話方塊中選擇了 Don't ask again 選
項,此方法將返回 false。如果裝置規範禁止應用具有該許可權,此方法也會返回 false。此方法非同步執行:它會立即返回,並且在使用者響應對話方塊之後,系統會使用結
果呼叫應用的回撥方法,將應用傳遞的相同請求程式碼傳遞到 requestPermissions()。
4 處理許可權請求響應
當應用請求許可權時,系統將向使用者顯示一個對話方塊。當使用者響應時,系統將呼叫應用的 onRequestPermissionsResult() 方法,向其傳遞使用者響應。我們可以在此方法裡是否已獲得相應許可權。
回撥會將您傳遞的相同請求程式碼傳遞給 requestPermissions()。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CODE_FOR_PERMISSION_CALLBACK:
//如果許可權請求被拒絕,則grantResults為空
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//許可權已經被授予,可以新增聯絡人了
Toast.makeText(PermissionActivity.this, "新增聯絡人許可權被授予", Toast.LENGTH_LONG).show();
insertDummyContact();
} else {
//許可權請求被拒絕
Toast.makeText(PermissionActivity.this, "新增聯絡人許可權被拒絕", Toast.LENGTH_LONG).show();
}
break;
}
}
整個流程的程式碼如下:
/******************
* 原生程式碼申請許可權 *
******************/
private static String DUMMY_CONTACT_NAME = "__DUMMY CONTACT from runtime permissions sample";
private static final int REQUEST_CODE_FOR_PERMISSION_CALLBACK = 0x000001;
private void requestRuntimePermission() {
// API 23及其以後的版本
if (Build.VERSION.SDK_INT >= 23) {
// 檢測是否已經被授權
if (ContextCompat.checkSelfPermission(PermissionActivity.this,
Manifest.permission.WRITE_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
//是否給使用者一個關於許可權申請的解釋
if (ActivityCompat.shouldShowRequestPermissionRationale(PermissionActivity.this,
Manifest.permission.WRITE_CONTACTS)) {
// Show an expanation to the user *asynchronously* -- don't block
// this thread waiting for the user's response! After the user
// sees the explanation, try again to request the permission.
} else {
//請求許可權
ActivityCompat.requestPermissions(PermissionActivity.this,
new String[]{Manifest.permission.WRITE_CONTACTS},
REQUEST_CODE_FOR_PERMISSION_CALLBACK);
}
}
}
//API 23以前版本
else {
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case REQUEST_CODE_FOR_PERMISSION_CALLBACK:
//如果許可權請求被拒絕,則grantResults為空
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//許可權已經被授予,可以新增聯絡人了
Toast.makeText(PermissionActivity.this, "新增聯絡人許可權被授予", Toast.LENGTH_LONG).show();
insertDummyContact();
} else {
//許可權請求被拒絕
Toast.makeText(PermissionActivity.this, "新增聯絡人許可權被拒絕", Toast.LENGTH_LONG).show();
}
break;
}
}
/**
* Accesses the Contacts content provider directly to insert a new contact.
* <p>
* The contact is called "__DUMMY ENTRY" and only contains a name.
*/
private void insertDummyContact() {
// Two operations are needed to insert a new contact.
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(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_NAME);
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());
}
}
簡化許可權處理流程
如果每次處理執行時許可權都要寫辣麼一堆程式碼,估計我們也要被累死了~~,所以也用相應的開源庫來簡化執行時許可權的處理。試用了很多,目前感覺最好用
的是PermissionsDispatcher,該庫試用使用註解的方式,動態生成類處理執行時許可權,下面介紹一個它的試用流程。
1 安裝外掛
AndroidStudio安裝外掛PermissionsDispatcher
2 新增依賴
注:當前的${latest.version}是2.2.0
For Android Gradle Plugin >= 2.2 users
To add it to your project, include the following in your app module build.gradle file:
dependencies {
compile 'com.github.hotchemi:permissionsdispatcher:${latest.version}'
annotationProcessor 'com.github.hotchemi:permissionsdispatcher-processor:${latest.version}'
}
For Android Gradle Plugin < 2.2 users
To add it to your project, include the following in your project build.gradle file:
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
And on your app module build.gradle:
apply plugin: 'android-apt'
dependencies {
compile 'com.github.hotchemi:permissionsdispatcher:${latest.version}'
apt 'com.github.hotchemi:permissionsdispatcher-processor:${latest.version}'
}
3 右鍵點選生成執行時許可權程式碼
填寫好方法名後生成的方法會帶有以下4個註解:
Annotation | Required | Description |
---|---|---|
@RuntimePermissions |
✓ | Register an Activity or Fragment to handle permissions |
@NeedsPermission |
✓ | Annotate a method which performs the action that requires one or more permissions |
@OnShowRationale |
Annotate a method which explains why the permission/s is/are needed. It passes in a PermissionRequest object which can be used to continue or abort the current permission request upon user input |
|
@OnPermissionDenied |
Annotate a method which is invoked if the user doesn't grant the permissions | |
@OnNeverAskAgain |
Annotate a method which is invoked if the user chose to have the device "never ask again" about a permission |
/******************
* 註解方式申請許可權 *
******************/
private static String DUMMY_CONTACT_NAME = "__DUMMY CONTACT from runtime permissions sample";
private void setupView() {
findViewById(R.id.btn_request_runtime_permission).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//insertContactWithCheck()是自動生成的方法
PermissionActivityPermissionsDispatcher.insertContactWithCheck(PermissionActivity.this);
}
});
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
//許可權申請的結果交由PermissionActivityPermissionsDispatcher來處理
PermissionActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
/**
* 需要申請許可權的操作
*/
@NeedsPermission(Manifest.permission.WRITE_CONTACTS)
void insertContact() {
insertDummyContact();
}
/**
* 解釋許可權申請原因
*
* @param request request
*/
@OnShowRationale(Manifest.permission.WRITE_CONTACTS)
void onShowRationale(final PermissionRequest request) {
new AlertDialog.Builder(this)
.setMessage("解釋為何申請許可權")
.setPositiveButton("同意", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
request.proceed();
}
})
.setNegativeButton("拒絕", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
request.cancel();
}
})
.show();
}
/**
* 許可權申請拒絕
*/
@OnPermissionDenied(Manifest.permission.WRITE_CONTACTS)
void onPermissionDenied() {
Toast.makeText(PermissionActivity.this, "新增聯絡人許可權被拒絕", Toast.LENGTH_LONG).show();
}
/**
* 許可權申請不再詢問
*/
@OnNeverAskAgain(Manifest.permission.WRITE_CONTACTS)
void onNeverAskAgain() {
Toast.makeText(PermissionActivity.this, "新增聯絡人許可權不再被詢問", Toast.LENGTH_LONG).show();
}
/**
* Accesses the Contacts content provider directly to insert a new contact.
* <p>
* The contact is called "__DUMMY ENTRY" and only contains a name.
*/
private void insertDummyContact() {
// Two operations are needed to insert a new contact.
ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>(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_NAME);
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());
}
}
相關文章
- Android 6.0 在執行時請求許可權Android
- Android 6.0 執行時許可權管理最佳實踐Android
- linux 檔案許可權 s 許可權和 t 許可權解析Linux
- Android SELinux許可權AndroidLinux
- Android 通知許可權Android
- Docker容器執行時許可權和Linux系統功能DockerLinux
- android permission 許可權與安全機制解析(下)Android
- android動態許可權到自定義許可權框架Android框架
- android 許可權庫EasyPermissionsAndroid
- 普通使用者許可權執行dockerDocker
- Linux檔案讀、寫、執行許可權Linux
- Android6.0------許可權申請管理(單個許可權和多個許可權申請)Android
- android 許可權元件設計Android元件
- 許可權系統:一文搞懂功能許可權、資料許可權
- lockdown profile 12c之後的許可權控制新特性
- php執行shell指令碼需要sudo許可權PHP指令碼
- 一次Android許可權刪除經歷Android
- 授權許可權服務設計解析
- Android 中的危險許可權Android
- Android動態許可權總結Android
- Android許可權處理分類Android
- Android property屬性許可權新增Android
- android強制申請許可權Android
- Laravel 日誌有時候有許可權有時候沒有許可權?Laravel
- 記一次 Laravel日誌許可權許可權問題(定時器導致)Laravel定時器
- 許可權之選單許可權
- 探索Android Q上的位置許可權Android
- android AVC錯誤修改許可權方法Android
- Android手機獲取Root許可權Android
- android 6.0許可權機制的簡單封裝(支援批量申請許可權)Android封裝
- 如何用 Vue 實現前端許可權控制(路由許可權 + 檢視許可權 + 請求許可權)Vue前端路由
- 分享一段Android許可權設定的程式碼Android
- Vim儲存時許可權不足
- django開發之許可權管理(一)——許可權管理詳解(許可權管理原理以及方案)、不使用許可權框架的原始授權方式詳解Django框架
- 一對一原始碼,前端頁面許可權和按鈕許可權控制原始碼前端
- Android 輔助許可權與懸浮窗Android
- 【專案實踐】一文帶你搞定頁面許可權、按鈕許可權以及資料許可權
- Linux特殊許可權之suid、sgid、sbit許可權LinuxUI
- Linux配置IP地址需要什麼許可權?可以執行哪些操作?Linux