Android執行時許可權機制解析
從Android M(6.0 API級別23)開始,使用者開始在應用執行時向其授予許可權,而不是在應用安裝時授予。此方法可以簡化應用安裝過程,因為使用者在安裝或更新應用時不需要授予許可權。
-
許可權介紹
-
許可權類別
-
正常許可權
- 正常許可權不會給使用者的隱私帶來風險,如果你在清單檔案(AndroidManifest.xml)中加入了正常許可權宣告,則安卓系統會自動授予App應用該許可權,如下列出了正常許可權
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
-
危險許可權
- 檢視危險許可權
可以通過adb shell pm list permissions -d -g進行檢視(Windows使用adb 命令首先需要自行配置環境變數)
adb 檢視危險許可權.png
- 危險許可權會授予應用訪問使用者隱私資料的許可權。如果您的應用在清單中列出了正常許可權,系統將自動授予該許可權。如果您列出了危險許可權,則使用者在清單檔案中列出的同時還必須在觸發使用相應功能的時候讓使用者同意應用使用這些許可權
危險許可權.png
-
-
由危險許可權的表可以看出,危險許可權都是一組一組出現的,並且你只要授予一組許可權的其中一個,那麼該組危險許可權的其他許可權也同樣被授予了(例如,如果某應用已經請求並且被授予了READ_CONTACTS 許可權,然後它又請求WRITE_CONTACTS,系統將立即授予該許可權)
-
使用許可權
當我們新建一個Android應用的時候,預設應用是沒有有申請任何許可權的,我們不管需要什麼許可權,首先需在清單檔案中使用<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>
-
許可權相關API
-
檢查許可權
- 當我們應用需要危險的許可權的時候,每次執行操作都需要檢查是否授予了危險許可權,檢查是否具有該許可權我們使用ContextCompat.checkSelfPermission() 方法
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.WRITE_CALENDAR); //如果具有該許可權,則方法返回PackageManager.PERMISSION_GRANTED,並且應用可以 //繼續操作。如果應用不具有此許可權,方法將返回 PERMISSION_DENIED,且應用必須明確向用戶要求許可權
-
請求獲取許可權
- 當我們應用某個功能操作需要危險許可權的申請,則我們可以呼叫ActivityCompat.requestPermissions的方法來獲取相應的許可權。該方法非同步執行:它會立即返回,並且在使用者響應對話方塊之後,系統會使用結果呼叫應用的回撥方法,將應用傳遞的相同請求程式碼傳遞到 ActivityCompat.requestPermissions方法。
- 以下程式碼可以檢查應用是否具備讀取使用者聯絡人的許可權,並根據需要請求該許可權
if (ContextCompat.checkSelfPermission(thisActivity, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { //是否具有該讀取聯絡人許可權 if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity, Manifest.permission.READ_CONTACTS)) { //判斷是否需要向用戶解釋,為什麼需要這些許可權。有時候使用者會不理解應用程式為什麼需要這些許可權。 //這個方法只有在APP請求過某一許可權且使用者禁止APP使用該許可權的時候返回true。在使用者授權了許可權和禁止許可權時勾選了“Don't ask again”選項的情況下都會返回false //也就是說如果進入到這裡,就說明該許可權曾經被拒絕過 /** 被拒絕又想再次申請可跳轉應用資訊介面授予許可權 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.setData(Uri.fromParts("package", context.getPackageName(), null)); startActivity(intent); */ } else { //申請許可權 ActivityCompat.requestPermissions(thisActivity, new String[]{Manifest.permission.READ_CONTACTS}, MY_PERMISSIONS_REQUEST_READ_CONTACTS); // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an // app-defined int constant. The callback method gets the // result of the request. } }
-
處理許可權請求的響應
-
當權限申請提示框與使用者互動的時候,我們開發人員必須知道使用者到底是否同意應用的許可權申請。所以使用者響應時,Android系統將呼叫應用的 onRequestPermissionsResult() 方法,向其傳遞使用者響應
-
延續上面讀取聯絡人的例子
/** * * @param requestCode 申請許可權傳入的請求碼 * @param permissions 申請許可權的陣列 * @param grantResults 請求的結果(用於區分上一個引數permissions中的許可權有沒有被授予,permissions和grantResults兩個陣列大小是一樣的,具體值和上方提到的PackageManager中的兩個常量做比較) */ @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { switch (requestCode) { case MY_PERMISSIONS_REQUEST_READ_CONTACTS: { // If request is cancelled, the result arrays are empty. if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // permission was granted, yay! Do the // contacts-related task you need to do. //使用者同意權限申請,繼續應用操作 } else { // permission denied, boo! Disable the // functionality that depends on this permission. //使用者拒絕許可權申請,提示使用者應用的操作需要該許可權 } return; } // other 'case' lines to check for other // permissions this app might request } }
-
-
執行時許可權小例子
-
讀取聯絡人列表完整例子
public class MainActivity extends AppCompatActivity { private static final int MY_PERMISSIONS_REQUEST_READ_CONTACTS = 1; //儲存聯絡人 public List<String> list = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); readContacts(); } //讀取聯絡人 public void readContacts(){ if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){ //申請許可權 //申請許可權 if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.READ_CONTACTS)) { //之前申請許可權的時候拒絕過 ,向用戶解釋為什麼需要該許可權 Toast.makeText(MainActivity.this, "Permission Denied,Show an expanation to the user *asynchronously* -- don't block", Toast.LENGTH_SHORT).show(); }else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, MY_PERMISSIONS_REQUEST_READ_CONTACTS); } }else { //獲取聯絡人列表 list=readContact(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if(requestCode == MY_PERMISSIONS_REQUEST_READ_CONTACTS){ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { //獲取聯絡人列表 list=readContact(); } else { // Permission Denied Toast.makeText(MainActivity.this, "Permission Denied", Toast.LENGTH_SHORT).show(); } return; } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } //讀取聯絡人 public List<String> readContact(){ List<String> list = new ArrayList<>(); Cursor cursor = null; try { //cursor指標 query詢問 contract協議 kinds種類 cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, null, null, null); if (cursor != null) { while (cursor.moveToNext()) { String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)); list.add(displayName + '\n' + number); } } return list; } catch (Exception e) { e.printStackTrace(); } finally { if (cursor != null) { cursor.close(); } } return null; } } //清單檔案中別忘了加上 <uses-permission android:name="android.permission.READ_CONTACTS"/>
-
-
熱門框架了解
- AndPermission(嚴振杰大大的框架)
- 專案地址
ofollow,noindex">https://github.com/yanzhenjie/AndPermission - 該框架也是日常開發用得比較多的一個框架,該框架一句話搞定許可權申請,還是比較方便的
AndPermission.with(this).runtime() .permission(Permission.Group.STORAGE) .onGranted(new Action<List<String>>() { @Override public void onAction(List<String> data) { } }).onDenied(new Action<List<String>>() { @Override public void onAction(List<String> data) { } }).start();
- 通過閱讀框架原始碼,我發現這個框架的核心就是PermissionActivity,它是一個沒有介面的Activity,所有的許可權申請都由它來發起,並進行相應操作的回撥,結合前面瞭解的執行時許可權的知識,相信這個Activity你可以很輕易就瞭解。
/** * <p> * Request permission. * </p> * Created by Yan Zhenjie on 2017/4/27. */ public final class PermissionActivity extends Activity { private static final String KEY_INPUT_OPERATION = "KEY_INPUT_OPERATION"; private static final int VALUE_INPUT_PERMISSION = 1; private static final int VALUE_INPUT_PERMISSION_SETTING = 2; private static final int VALUE_INPUT_INSTALL = 3; private static final int VALUE_INPUT_OVERLAY = 4; private static final int VALUE_INPUT_ALERT_WINDOW = 5; private static final String KEY_INPUT_PERMISSIONS = "KEY_INPUT_PERMISSIONS"; private static RequestListener sRequestListener; /** * Request for permissions. */ public static void requestPermission(Context context, String[] permissions, RequestListener requestListener) { PermissionActivity.sRequestListener = requestListener; Intent intent = new Intent(context, PermissionActivity.class); intent.putExtra(KEY_INPUT_OPERATION, VALUE_INPUT_PERMISSION); intent.putExtra(KEY_INPUT_PERMISSIONS, permissions); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } /** * Request for setting. */ public static void permissionSetting(Context context, RequestListener requestListener) { PermissionActivity.sRequestListener = requestListener; Intent intent = new Intent(context, PermissionActivity.class); intent.putExtra(KEY_INPUT_OPERATION, VALUE_INPUT_PERMISSION_SETTING); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } /** * Request for package install. */ public static void requestInstall(Context context, RequestListener requestListener){ PermissionActivity.sRequestListener = requestListener; Intent intent = new Intent(context, PermissionActivity.class); intent.putExtra(KEY_INPUT_OPERATION, VALUE_INPUT_INSTALL); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } /** * Request for overlay. */ public static void requestOverlay(Context context, RequestListener requestListener) { PermissionActivity.sRequestListener = requestListener; Intent intent = new Intent(context, PermissionActivity.class); intent.putExtra(KEY_INPUT_OPERATION, VALUE_INPUT_OVERLAY); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } /** * Request for alert window. */ public static void requestAlertWindow(Context context, RequestListener requestListener) { PermissionActivity.sRequestListener = requestListener; Intent intent = new Intent(context, PermissionActivity.class); intent.putExtra(KEY_INPUT_OPERATION, VALUE_INPUT_ALERT_WINDOW); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); context.startActivity(intent); } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); int operation = intent.getIntExtra(KEY_INPUT_OPERATION, 0); switch (operation) { case VALUE_INPUT_PERMISSION: { String[] permissions = intent.getStringArrayExtra(KEY_INPUT_PERMISSIONS); if (permissions != null && sRequestListener != null) { requestPermissions(permissions, VALUE_INPUT_PERMISSION); } else { finish(); } break; } case VALUE_INPUT_PERMISSION_SETTING: { if (sRequestListener != null) { RuntimeSettingPage setting = new RuntimeSettingPage(new ContextSource(this)); setting.start(VALUE_INPUT_PERMISSION_SETTING); } else { finish(); } break; } case VALUE_INPUT_INSTALL: { if (sRequestListener != null) { Intent manageIntent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); manageIntent.setData(Uri.fromParts("package", getPackageName(), null)); startActivityForResult(manageIntent, VALUE_INPUT_INSTALL); } else { finish(); } break; } case VALUE_INPUT_OVERLAY: { if (sRequestListener != null) { OverlaySettingPage settingPage = new OverlaySettingPage(new ContextSource(this)); settingPage.start(VALUE_INPUT_OVERLAY); } else { finish(); } break; } case VALUE_INPUT_ALERT_WINDOW: { if (sRequestListener != null) { AlertWindowSettingPage settingPage = new AlertWindowSettingPage(new ContextSource(this)); settingPage.start(VALUE_INPUT_ALERT_WINDOW); } else { finish(); } break; } default: { throw new AssertionError("This should not be the case."); } } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (sRequestListener != null) { sRequestListener.onRequestCallback(); } finish(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (sRequestListener != null) { sRequestListener.onRequestCallback(); } finish(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { return true; } return super.onKeyDown(keyCode, event); } @Override public void finish() { sRequestListener = null; super.finish(); } /** * permission callback. */ public interface RequestListener { void onRequestCallback(); } }
看過不一定記得,記得不一定會寫,會寫不代表不會忘記,所以記下來是最好的選擇。好了,又通過一篇文章讓我對Android執行時許可權有了更深入的瞭解。文章中如果有錯誤,請大家給我提出來,大家一起學習進步,如果覺得我的文章給予你幫助,也請給我一個喜歡和關注。