Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android6.0運行時權限處理透析

Android6.0運行時權限處理透析

編輯:關於Android編程

Android 6.0的權限定義:

從 Android 6.0(API 級別 23)開始,用戶開始在應用運行時向其授予權限,而不是在應用安裝時授予。此方法可以簡化應用安裝過程,因為用戶在安裝或更新應用時不需要授予權限。它還讓用戶可以對應用的功能進行更多控制;例如,用戶可以選擇為相機應用提供相機訪問權限,而不提供設備位置的訪問權限。用戶可以隨時進入應用的“Settings”屏幕調用權限。

系統權限分為兩類:正常權限危險權限(運行時權限,一下統稱危險權限)

  • 正常權限不會直接給用戶隱私權帶來風險。如果您的應用在其清單中列出了正常權限,系統將自動授予該權限。
  • 危險權限會授予應用訪問用戶機密數據的權限。如果您的應用在其清單中列出了正常權限,系統將自動授予該權限。如果您列出了危險權限,則用戶必須明確批准您的應用使用這些權限。

權限組

所有危險的 Android 系統權限都屬於權限組。如果設備運行的是 Android 6.0(API 級別 23),並且應用的targetSdkVersion是 23 或更高版本,則當用戶請求危險權限時系統會發生以下行為:

  • 如果應用請求其清單中列出的危險權限,而應用目前在權限組中沒有任何權限,則系統會向用戶顯示一個對話框,描述應用要訪問的權限組。對話框不描述該組內的具體權限。例如,如果應用請求READ_CONTACTS權限,系統對話框只說明該應用需要訪問設備的聯系信息。如果用戶批准,系統將向應用授予其請求的權限。
  • 如果應用請求其清單中列出的危險權限,而應用在同一權限組中已有另一項危險權限,則系統會立即授予該權限,而無需與用戶進行任何交互。例如,如果某應用已經請求並且被授予了READ_CONTACTS權限,然後它又請求WRITE_CONTACTS,系統將立即授予該權限。
    任何權限都可屬於一個權限組,包括正常權限和應用定義的權限。但權限組僅當權限危險時才影響用戶體驗。可以忽略正常權限的權限組。
  • 如果設備運行的是 Android 5.1(API 級別 22)或更低版本,並且應用的targetSdkVersion是 22 或更低版本,則系統會在安裝時要求用戶授予權限。再次強調,系統只告訴用戶應用需要的權限,而不告知具體權限。 低於6.0的應用targetSdkVersion是 22的版本運行時是強制用戶允許在AndroidManifest.xml聲明的權限的,而6.0以後,用戶開始在應用運行時向其授予權限,而不是在應用安裝時授予。

表 1.危險權限和權限組。

權限組 權限 CALENDAR
  • READ_CALENDAR
    • WRITE_CALENDAR
CAMERA
  • CAMERA
CONTACTS
  • READ_CONTACTS
  • WRITE_CONTACTS
  • GET_ACCOUNTS
LOCATION
  • ACCESS_FINE_LOCATION
  • ACCESS_COARSE_LOCATION
MICROPHONE
  • RECORD_AUDIO
PHONE
  • READ_PHONE_STATE
  • CALL_PHONE
  • READ_CALL_LOG
  • WRITE_CALL_LOG
  • ADD_VOICEMAIL
  • USE_SIP
  • PROCESS_OUTGOING_CALLS
SENSORS
  • BODY_SENSORS
SMS
  • SEND_SMS
  • RECEIVE_SMS
  • READ_SMS
  • RECEIVE_WAP_PUSH
  • RECEIVE_MMS
STORAGE
  • READ_EXTERNAL_STORAGE
  • WRITE_EXTERNAL_STORAGE

    來源:https://developer.android.com/guide/topics/security/permissions.html

 

現在我們寫一個小例子驗證一下:

一、首先目標版本設置為23

強調,在低於23版本也可以使用V7包來調用檢測權限和申請權限的方法,還能在回調接收到,但是,也會出現回調返回的狀態不准的問題,在下就被這個坑坑了好久。targetSdkVersion如果是23以下,調用ActivityCompat.requestPermissions(),會彈出權限選擇對話框,但是選擇拒絕授權,onRequestPermissionsResult中的返回值卻是PERMISSION_GRANTED,但選擇同意授權,會把應用關閉重新開啟當前activity,而不會調用onRequestPermissionsResult中的方法,所以不要在targetSdkVersion設置為23以下,又把complierSdkversion設置為23,這樣會出現上述的問題。最好的方式是把targetSdkVersion也設置為23,就可以解決。一切完美運行。

詳情請看:

按照如下設置:

 

android {
    compileSdkVersion 23
    buildToolsVersion "23.0.1"

    defaultConfig {
        applicationId "com.qinglin.permissiondemo"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
}

二、聲明需要的權限

首先,必須在AndroidManifest.xml文件裡面聲明需要的權限,否則在使用到相應的權限的時候強行申請權限會崩潰的,這也許是android系統的保護機制吧。

檢查權限


如果您的應用需要危險權限,則每次執行需要這一權限的操作時您都必須檢查自己是否具有該權限。用戶始終可以自由調用此權限,因此,即使應用昨天使用了相機,它不能假設自己今天仍具有該權限。

要檢查您是否具有某項權限,請調用ContextCompat.checkSelfPermission()方法。例如,以下代碼段顯示了如何檢查 Activity 是否具有在日歷中進行寫入的權限:

// Assume thisActivity is the current activity
int permissionCheck = ContextCompat.checkSelfPermission(thisActivity,
    Manifest.permission.WRITE_CALENDAR);

 

請求權限

1、解釋應用為什麼需要權限

在某些情況下,您可能需要幫助用戶了解您的應用為什麼需要某項權限。例如,如果用戶啟動一個攝影應用,用戶對應用要求使用相機的權限可能不會感到吃驚,但用戶可能無法理解為什麼此應用想要訪問用戶的位置或聯系人。在請求權限之前,不妨為用戶提供一個解釋。請記住,您不需要通過解釋來說服用戶;如果您提供太多解釋,用戶可能發現應用令人失望並將其移除。

您可以采用的一個方法是僅在用戶已拒絕某項權限請求時提供解釋。如果用戶繼續嘗試使用需要某項權限的功能,但繼續拒絕權限請求,則可能表明用戶不理解應用為什麼需要此權限才能提供相關功能。對於這種情況,比較好的做法是顯示解釋。

為了幫助查找用戶可能需要解釋的情形,Android 提供了一個實用程序方法,即shouldShowRequestPermissionRationale()。如果應用之前請求過此權限但用戶拒絕了請求,此方法將返回true。

:如果用戶在過去拒絕了權限請求,並在權限請求系統對話框中選擇了Don't ask again選項,此方法將返回false。如果設備規范禁止應用具有該權限,此方法也會返回false。

2、請求您需要的權限

如果應用尚無所需的權限,則應用必須調用一個requestPermissions()方法,以請求適當的權限。應用將傳遞其所需的權限,以及您指定用於識別此權限請求的整型請求代碼。此方法異步運行:它會立即返回,並且在用戶響應對話框之後,系統會使用結果調用應用的回調方法,將應用傳遞的相同請求代碼傳遞到requestPermissions()。

以下代碼可以檢查應用是否具備讀取用戶聯系人的權限,並根據需要請求該權限:

// Here, thisActivity is the current activity

if (ContextCompat.checkSelfPermission(thisActivity,
        Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {

  // Should we show an explanation?
  if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
      Manifest.permission.READ_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 {

    // No explanation needed, we can request the permission.

    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.
  }
}
3、處理權限請求響應(onRequestPermissionsResult回調
   當應用請求權限時,系統將向用戶顯示一個對話框。當用戶響應時,系統將調用應用的 onRequestPermissionsResult() 方法,向其傳遞用戶響應。您的應用必須替換該方法,以了解是否已獲得相應權限。回調會將您傳遞的相同請求代碼傳遞給 requestPermissions()。例如,如果應用請求 READ_CONTACTS 訪問權限,則它可能采用以下回調方法:

@Override
public void onRequestPermissionsResult(int requestCode,
                                       String permissions[], int[] grantResults) {
    switch (requestCode) {
        case PluginUtil.BAIDU_READ_LOCATION_STATE: {
            // 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.
                Toast.makeText(this, "用戶允許此權限", Toast.LENGTH_LONG).show();
            } else {
              if(!ActivityCompat.shouldShowRequestPermissionRationale(this,permissions[0])){
                  //用戶拒絕了此申請權限,並勾選了不再詢問
                  ToastUtil.showToast(this,"點擊權限,並打開全部權限");
                  PluginUtil.gotoAppDetailSettingIntent(this);
                  return;
              }
                // permission denied, boo! Disable the
                // functionality that depends on this permission.
                Toast.makeText(this, "用戶拒絕此權限", Toast.LENGTH_LONG).show();
            }
            return;
        }

        // other 'case' lines to check for other
        // permissions this app might request
    }
}    
      系統顯示的對話框說明了您的應用需要訪問的權限組;它不會列出具體權限。例如,如果您請求 READ_CONTACTS 權限,系統對話框只顯示您的應用需要訪問設備的聯系人。用戶只需要為每個權限組授予一次權限。如果您的應用請求該組中的任何其他權限(已在您的應用清單中列出),系統將自動授予應用這些權限。當您請求此權限時,系統會調用您的 onRequestPermissionsResult() 回調方法,並傳遞 PERMISSION_GRANTED,如果用戶已通過系統對話框明確同意您的權限請求,系統將采用相同方式操作。
權限功能規范化(統一創建一個工具類PermissionUtils)
      由於權限的功能會在多個地方反復使用,為了提高復用性,必然需要建立一個工具類,把常用方法寫在這個工具類裡面,方便調用。
以下是我的工具類封裝PermissionUtils,共享給大家:

public class PermissionUtils { private static final String SCHEME = "package"; public static final int BAIDU_READ_LOCATION_STATE = 100; //自定義一個權限獲取碼,用於回調函數中做對應處理 public static void opendPermissionSetting(Context context) { String deviceInfo = getDeviceInfo(); LogUtil.i("hql", "deviceInfo:" + deviceInfo); if ("Xiaomi".equals(Build.MANUFACTURER)) { gotoMiuiPermission(context); } else if ("Meizu".equals(Build.MANUFACTURER)) { gotoMeizuPermission(context); } else if ("HUAWEI".equals(Build.MANUFACTURER)) { gotoHuaweiPermission(context); } else { if (Build.VERSION.SDK_INT >= 23) {// 用於判斷是否為Android 6.0系統以上版本 gotoAppDetailSettingIntent(context); } else { gotoAppSettingIntent(context); } } } public static void needPermission(Activity context, int requestCode, String[] permissions) { try { // 申請一個(或多個)權限,並提供用於回調返回的獲取碼(用戶定義) ActivityCompat.requestPermissions(context, permissions, requestCode); } catch (Exception e) { e.printStackTrace(); LogUtil.e("hql", e.toString()); } } // 判斷權限集合是否都擁有,只要有一個缺少就返回false public static boolean isHasPermission(Context context, String... permissions) { for (String permission : permissions) { if (!isHasPermission(context, permission)) { return false; } } return true; } // 判斷是否擁有權限 private static boolean isHasPermission(Context context, String permission) { Context mContext = context.getApplicationContext(); return ContextCompat.checkSelfPermission(mContext, permission) == PackageManager.PERMISSION_GRANTED; } /** * 跳轉到miui的權限管理頁面 */ public static void gotoMiuiPermission(Context context) { Intent i = new Intent("miui.intent.action.APP_PERM_EDITOR"); ComponentName componentName = new ComponentName("com.miui.securitycenter", "com.miui.permcenter.permissions.AppPermissionsEditorActivity"); i.setComponent(componentName); i.putExtra("extra_pkgname", context.getPackageName()); try { context.startActivity(i); } catch (Exception e) { e.printStackTrace(); gotoAppSettingIntent(context); } } /** * 跳轉到魅族的權限管理系統 */ public static void gotoMeizuPermission(Context context) { Intent intent = new Intent("com.meizu.safe.security.SHOW_APPSEC"); intent.addCategory(Intent.CATEGORY_DEFAULT); intent.putExtra("packageName", BuildConfig.APPLICATION_ID); try { context.startActivity(intent); } catch (Exception e) { e.printStackTrace(); gotoAppSettingIntent(context); } } /** * 華為的權限管理頁面 */ public static void gotoHuaweiPermission(Context context) { try { Intent intent = new Intent(); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ComponentName comp = new ComponentName("com.huawei.systemmanager", "com.huawei.permissionmanager.ui.MainActivity");//華為權限管理 intent.setComponent(comp); context.startActivity(intent); } catch (Exception e) { e.printStackTrace(); gotoAppSettingIntent(context); } } /** * 打開應用詳情頁面intent */ public static void gotoAppDetailSettingIntent(Context context) { Intent localIntent = new Intent(); localIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); if (Build.VERSION.SDK_INT >= 9) { localIntent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); localIntent.setData(Uri.fromParts(SCHEME, context.getPackageName(), null)); } else if (Build.VERSION.SDK_INT <= 8) { localIntent.setAction(Intent.ACTION_VIEW); localIntent.setClassName("com.android.settings", "com.android.settings.InstalledAppDetails"); localIntent.putExtra("com.android.settings.ApplicationPkgName", context.getPackageName()); } context.startActivity(localIntent); } /** * 打開系統設置界面 */ public static void gotoAppSettingIntent(Context context) { Intent intent = new Intent(Settings.ACTION_SETTINGS);//系統設置界面 context.startActivity(intent); } }


Android 6.0運行時權限勾選不再詢問後該如何處理?

當第一次請求權限申請被拒絕後再進行第二次申請時,對話框中會多出一個 不再詢問 的復選框。如果勾選了該復選框並且拒絕請求,那麼以後將無法再申請該權限。也就是說在調用 requestPermissions() 後,onRequestPermissionsResult() 會立刻被調用並且申請結果為 PERMISSION_DENIED 。 其實這個時候還是有一根救命稻草的。

 

1、首先需要判斷用戶是否勾選了不再詢問,使用方法:ActivityCompat.shouldShowRequestPermissionRationale(this,permissions[0])。

2、ActivityCompat 位於 support.v7 包中,因為運行時權限是 6.0 的新特性,使用該類可以省略對版本的判斷當權限申請被拒絕並且shouldShowRequestPermissionRationale() 返回 false 就表示勾選了不再詢問。轉到設置界面現在我們唯一能做的就是跳轉到我們 App 的設置界面,讓用戶手動開啟權限了。

權限最佳做法

應用如果一味要求用戶提供授權,可能會讓用戶無所適從。如果用戶發現應用難以使用,或者擔心應用會濫用其信息,他們可能不願意使用該應用,甚至會將其完全卸載。以下最佳做法有助於避免此類糟糕的用戶體驗。

考慮使用 intent

許多情況下,您可以使用以下兩種方式之一來讓您的應用執行某項任務。您可以將應用設置為要求提供權限才能執行操作。或者,您可以將應用設置為使用 intent,讓其他應用來執行任務。

例如,假設應用需要使用設備相機才能夠拍攝照片。應用可以請求 CAMERA 權限,以便允許其直接訪問相機。然後,應用將使用 Camera API 控制相機並拍攝照片。利用此方法,您的應用能夠完全控制攝影過程,並支持您將相機 UI 整合至應用中。

不過,如果您無需此類完全控制,則可以使用 ACTION_IMAGE_CAPTURE intent 來請求圖像。發送該 intent 時,系統會提示用戶選擇相機應用(如果沒有默認相機應用)。用戶使用選定的相機應用拍攝照片,該相機應用會將照片返回給應用的 onActivityResult() 方法。

同樣,如果您需要撥打電話、訪問用戶的聯系人或要執行其他操作,可以通過創建適當的 intent 來完成,或者您也可以請求相應的權限並直接訪問相應的對象。每種方法各有優缺點。

如果使用權限:

  • 您的應用可在您執行操作時完全控制用戶體驗。不過,如此廣泛的控制會增加任務的復雜性,因為您需要設計適當的 UI。
  • 系統會在運行或安裝應用時各提示用戶提供一次權限(具體取決於用戶的 Android 版本)。之後,應用即可執行操作,不再需要用戶進行其他交互。不過,如果用戶不授予權限(或稍後撤銷權限),您的應用將根本無法執行操作。

如果使用 intent:

  • 您無需為操作設計 UI。處理 intent 的應用將提供 UI。不過,這意味著您無法控制用戶體驗。用戶可能與您從未見過的應用交互。
  • 如果用戶沒有適用於操作的默認應用,則系統會提示用戶選擇一款應用。如果用戶未指定默認處理程序,則他們每次執行此操作時都必須處理一個額外對話框。
  • 僅要求您需要的權限

每次您要求權限時,實際上是在強迫用戶作出決定。您應盡量減少提出這些請求的次數。如果用戶運行的是 Android 6.0(API 級別 23)或更高版本,則每次用戶嘗試要求提供權限的新應用功能時,應用都必須中斷用戶的操作並發起權限請求。如果用戶運行的是較早版本的 Android,則在安裝應用時需要為應用的每一權限請求給予授權;如果列表過長或看起來不合適,用戶可能會決定不安裝該應用。為此,您應盡量減少應用需要的權限數。

例如,很多情況下應用可以通過使用 intent 來避免請求權限。如果某項功能並非應用的核心功能,不妨考慮將相關工作交給其他應用來執行,如考慮使用 intent 中所述。

不要讓用戶感到無所適從

 

如果用戶運行的是 Android 6.0(API 級別 23)或更高版本,則用戶必須在應用運行時為其授權。如果您的應用一次要求用戶提供多項權限,用戶可能會感到無所適從並因此退出應用。您應根據需要請求權限。

某些情況下,一項或多項權限可能是應用所必需的。在這種情況下,合理的做法是,在應用啟動之後立即要求提供這些權限。例如,如果您運行攝影應用,應用需要訪問設備的相機。在用戶首次啟動應用時,他們不會對提供相機使用權限的要求感到驚訝。但是,如果同一應用還具備與用戶聯系人共享照片的功能,您不應在應用首次啟動時要求用戶提供 READ_CONTACTS 權限,而應等到用戶嘗試使用“共享”功能之後,再要求提供該權限。

如果應用提供了教程,則合理的做法是,在教程結束時請求提供應用的必要權限。

解釋需要權限的原因:

系統在您調用 requestPermissions() 時顯示的權限對話框將說明應用需要的權限,但不會解釋為何需要這些權限。某些情況下,用戶可能會感到困惑。因此,最好在調用 requestPermissions() 之前向用戶解釋應用需要相應權限的原因。

例如,攝影應用可能需要使用位置服務,以便能夠為照片添加地理標簽。通常,用戶可能不了解照片能夠包含位置信息,並且對攝影應用想要了解具體位置感到不解。因此在這種情況下,應用最好在調用 requestPermissions() 之前告知用戶此功能的相關信息。

告知用戶的一種辦法是將這些請求納入應用教程。這樣,教程可以依次顯示應用的每項功能,並在顯示每項功能時解釋需要哪些相應的權限。例如,攝影應用的教程可以演示其“與您的聯系人共享照片”功能,然後告知用戶需要為應用授予權限才能查看用戶的聯系人。然後,應用可以調用 requestPermissions(),要求用戶提供該訪問權限。當然,並非所有用戶都會按照教程操作,因此您仍需在應用的正常操作期間檢查和請求權限

測試兩種權限模式

從 Android 6.0(API 級別 23)開始,用戶是在運行時而不是在應用安裝時授予或撤銷應用權限。因此,您應在多種不同條件下測試應用。在低於 Android 6.0 的版本中,您可以認為如果應用得到運行,它就可以得到在應用清單中聲明的全部權限。在新的權限模式中,這一推斷不再成立。

以下提示可幫助您識別在運行 API 級別 23 或更高級別的設備上與權限有關的代碼問題:

  • 識別應用的當前權限和相關的代碼路徑。
  • 在各種受權限保護的服務和數據中測試用戶流程。
  • 使用授予或撤銷權限的各種組合進行測試。例如,相機應用可能會在清單中列出 CAMERA、READ_CONTACTS 和 ACCESS_FINE_LOCATION。您應在測試應用時逐一打開和關閉這些權限,確保應用可以妥善處理所有權限配置。請記住,自 Android 6.0 起,用戶可以打開或關閉任何應用的權限,即使面向 API 級別 22 或更低級別的應用也是如此。
  • 使用 adb 工具從命令行管理權限:
  • 按組列出權限和狀態:
    $ adb shell pm list permissions -d -g
  • 授予或撤銷一項或多項權限:
$ adb shell pm [grant|revoke]  ...

來源:https://developer.android.com/training/permissions/best-practices.html
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved