Android 6.0 動態權限申請


1. 概述

Android 6.0 (API 23) 之前應用的權限在安裝時全部授予,運行時應用不再需要詢問用戶。在 Android 6.0 或更高版本對權限進行了分類,對某些涉及到用戶隱私的權限可在運行時根據用戶的需要動態授予。這樣就不需要在安裝時被強迫同意某些權限。

2. 正常權限 和 危險權限

Android系統權限分為幾個保護級別。需要了解的兩個最重要保護級別是 正常權限危險權限:

(1)正常權限:

涵蓋應用需要訪問其沙盒外部數據或資源,但對用戶隱私或其他應用操作風險很小的區域。這些權限在應用安裝時授予,運行時不再詢問用戶。例如: 網絡訪問、WIFI狀態、音量設置等。完整的正常權限列表參考官網 正常權限

(2)危險權限:

涵蓋應用需要涉及用戶隱私信息的數據或資源,或者可能對用戶存儲的數據或其他應用的操作產生影響的區域。例如: 讀取通訊錄、讀寫存儲器數據、獲取用戶位置等。如果應用聲明需要這些危險權限,則必須在運行時明確告訴用戶,讓用戶手動授予。

3. 權限組

Android系統對所有的危險權限進行了分組,稱為 權限組 。屬於同一組的危險權限將自動合並授予,用戶授予應用某個權限組的權限,則應用將獲得該權限組下的所有權限(前提是相關權限在 AndroidManifest.xml 中有聲明)。

危險權限權限組 列表如下:

危險權限和權限組列表

PS: 在 AndroidManifest.xml 聲明過的危險權限對應的權限組可以在系統 “設置” -> “應用” -> “應用信息” -> “權限” 中查看,可以手動授權和取消授權。

權限組和權限在Android代碼中以 字符串常量 來表示,分別定義在以下兩個 靜態內部類 的字段中:

  • android.Manifest.permission_group(權限組):
    • Manifest.permission_group.CALENDAR
    • Manifest.permission_group.STORAGE
    • ......
  • android.Manifest.permission(權限):
    • Manifest.permission.READ_CALENDAR
    • Manifest.permission.READ_EXTERNAL_STORAGE
    • ......

4. 在運行時請求權限

設備系統是 Android 6.0 (API 23) 或更高版本,並且應用的 targetSdkVersion 是 23 或更高版本,則針對在 AndroidManifest.xml 中聲明的危險權限,在運行時還需要動態請求用戶授權。

動態權限請求相關操作的API封裝在在android.support.v4包中,發起請求權限的Activity需要直接或間接繼承android.support.v4.app.FragmentActivity

PS: 也可以在直接或間接繼承 android.support.v4.app.FragmentFragment 中發起權限請求。

代碼步驟中主要包含以下幾個方法:

(1)檢查權限

// 檢查權限 ContextCompat.checkSelfPermission(Context context, String permission)

 

返回值(android.content.pm.PackageManager中的常量):

  • 有權限: PackageManager.PERMISSION_GRANTED
  • 無權限: PackageManager.PERMISSION_DENIED

當應用需要用到危險權限時,在執行權限相關代碼前,使用該方法判斷是否擁有指定的權限。有權限,則繼續執行設計需要權限的代碼;無權限,則向用戶請求授予權限。

(2)解釋權限

// 解釋權限 ActivityCompat.shouldShowRequestPermissionRationale(Activity activity, String permission)
  • 1

判斷是否有必要向用戶解釋為什么要這項權限。如果應用第一次請求過此權限,但是被用戶拒絕了,則之后調用該方法將返回 true,此時就有必要向用戶詳細說明需要此權限的原因(個人認為此方法是可選的)。

PS: 如果應用第一次請求此權限時被用戶拒絕,第二次再請求此權限時,用戶勾選了權限請求對話框的“不再詢問”,則此方法返回 false。如果設備規范禁止應用擁有該權限,此方法也返回 false。

(3)請求權限

// 請求權限 ActivityCompat.requestPermissions(Activity activity, String[] permissions, int requestCode)

 

當檢測到應用沒有指定的權限時,調用此方法向用戶請求權限。調用此方法將彈出權限請求對話框詢問用戶 “允許” 或 “拒絕” 指定的權限。

PS:

  • 權限參數傳入的是數組,可以調用該方法一次請求多個權限;
  • 傳入的權限數組參數以單個具體權限為單位,但彈框詢問用戶授權時,屬於同一權限組的權限將自動合並詢問授權一次
  • 請求的權限必須事先在 AndroidManifest.xml 中有聲明,否則調用此方法請求時,將不彈框,而是直接返回“拒絕”的結果;
  • 第一次請求權限時,用戶點擊了“拒絕”,第二次再請求該權限時,對話框將出現“不再詢問”復選框,如果用戶勾選了“不再詢問”並點擊了“拒絕”,則之后再請求此權限組時將不彈框,而是直接返回“拒絕”的結果。

(4)處理結果

請求權限的結果返回和接收一個Activity的返回類似,重寫 FragmentActivity(v4) Fragment 中的 onRequestPermissionsResult(...) 方法。

/** * 處理權限請求結果 * * @param requestCode * 請求權限時傳入的請求碼,用於區別是哪一次請求的 * * @param permissions * 所請求的所有權限的數組 * * @param grantResults * 權限授予結果,和 permissions 數組參數中的權限一一對應,元素值為兩種情況,如下: * 授予: PackageManager.PERMISSION_GRANTED * 拒絕: PackageManager.PERMISSION_DENIED */ @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { // ... }

 

5. 代碼演示

功能很簡單,如下:

  1. 點擊一個按鈕,如果有相應權限就進行備份通訊錄操作;
  2. 如果沒有相應的權限,則向用戶申請權限;
  3. 如果用戶授權通過,則繼續進行備份通訊錄操作;
  4. 如果用戶拒絕授權,則彈出對話框引導用戶跳轉到應用權限管理界面手動授權。

效果大致如下:

動畫演示1 動畫演示2

部分文件代碼:

1、首先在 AndroidManifest.xml 中聲明權限

<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.xiets.demoapp"> <!-- 聲明所有需要的權限(包括普通權限和危險權限) --> <uses-permission android:name="android.permission.READ_CONTACTS"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>

 

2、布局文件 activity_main.xml

<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp"> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="click" android:textSize="20sp" android:text="備份通訊錄" /> </RelativeLayout>

 

3、MainActivity

package com.xiets.demoapp; import android.Manifest; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; import android.provider.Settings; import android.support.annotation.NonNull; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Toast; /** * 一鍵備份通訊錄 * * @author xietansheng */ public class MainActivity extends AppCompatActivity { private static final int MY_PERMISSION_REQUEST_CODE = 10000; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } /** * 點擊按鈕,將通訊錄備份保存到外部存儲器備。 * * 需要3個權限(都是危險權限): * 1. 讀取通訊錄權限; * 2. 讀取外部存儲器權限; * 3. 寫入外部存儲器權限. */ public void click(View view) { /** * 第 1 步: 檢查是否有相應的權限 */ boolean isAllGranted = checkPermissionAllGranted( new String[] { Manifest.permission.READ_CONTACTS, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE } ); // 如果這3個權限全都擁有, 則直接執行備份代碼 if (isAllGranted) { doBackup(); return; } /** * 第 2 步: 請求權限 */ // 一次請求多個權限, 如果其他有權限是已經授予的將會自動忽略掉 ActivityCompat.requestPermissions( this, new String[] { Manifest.permission.READ_CONTACTS, Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, MY_PERMISSION_REQUEST_CODE ); } /** * 檢查是否擁有指定的所有權限 */ private boolean checkPermissionAllGranted(String[] permissions) { for (String permission : permissions) { if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) { // 只要有一個權限沒有被授予, 則直接返回 false return false; } } return true; } /** * 第 3 步: 申請權限結果返回處理 */ @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == MY_PERMISSION_REQUEST_CODE) { boolean isAllGranted = true; // 判斷是否所有的權限都已經授予了 for (int grant : grantResults) { if (grant != PackageManager.PERMISSION_GRANTED) { isAllGranted = false; break; } } if (isAllGranted) { // 如果所有的權限都授予了, 則執行備份代碼 doBackup(); } else { // 彈出對話框告訴用戶需要權限的原因, 並引導用戶去應用權限管理中手動打開權限按鈕 openAppDetails(); } } } /** * 第 4 步: 備份通訊錄操作 */ private void doBackup() { // 本文主旨是講解如果動態申請權限, 具體備份代碼不再展示, 就假裝備份一下 Toast.makeText(this, "正在備份通訊錄...", Toast.LENGTH_SHORT).show(); } /** * 打開 APP 的詳情設置 */ private void openAppDetails() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("備份通訊錄需要訪問 “通訊錄” 和 “外部存儲器”,請到 “應用信息 -> 權限” 中授予!"); builder.setPositiveButton("去手動授權", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent intent = new Intent(); intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.setData(Uri.parse("package:" + getPackageName())); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY); intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); startActivity(intent); } }); builder.setNegativeButton("取消", null); builder.show(); } }

 


 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM