天天看點

Android權限适配全攻略

    • Android權限适配全攻略
      • 概述
      • 運作時權限管理機制介紹
        • 權限組
        • 權限組的作用
      • 權限适配
        • Android 60 運作時動态權限适配
        • 國産ROM自定義權限機制适配
        • 特殊權限适配
        • 不依賴Activity進行權限申請
        • Android80權限适配
      • 參考

Android權限适配全攻略

概述

在Android6.0(API 23)之前Android的權限機制太過于簡單粗暴:App安裝時會提示使用者APP所需權限,使用者同意安裝後就會永久授權應用所需全部權限。其中大部分使用者并不知道為什麼APP需要這些權限,也不知道這些權限意味着什麼。這樣很容易被“别有用心”的APP利用,對使用者的隐私及賬戶安全形成了很大的挑戰。

Google也開始意識到問題的嚴重性并且開始收緊權限,在6.0(API 23)做了大的改動,引入了動态權限管理概念。

運作時權限管理機制介紹

對于開發者而言,在6.0之前應用申請權限隻要将權限在Androidmanifest.xml中注冊即可擷取對應權限。在6.0之後Google将所有權限進行了整合分組,将系統權限分為幾個保護級别也就是權限組。

權限組

需要了解的兩個最重要保護級别是normal permissions和dangerous permissions:

normal permissions:涵蓋應用需要通路資料或資源,但對使用者隐私或其他應用操作風險很小的區域。例如,設定時區的權限就是正常權限。如果應用聲明其需要正常權限,系統會自動向應用授予該權限。

dangerous permissions:涵蓋應用需要涉及使用者隐私資訊的資料或資源,或者可能對使用者存儲的資料或其他應用的操作産生影響的區域。例如,能夠讀取使用者的聯系人屬于危險權限。如果應用聲明其需要危險權限,則使用者必須明确向應用授予該權限。

檢視dangerous permissions權限可以在perm-groups 或通過

adb shell pm list permissions -d -g

檢視

總結一下就是Google将權限進行了分組,申請屬于normal permissions的權限在manifest中聲明即可,對于dangerous permissions 權限必須運作時動态申請。

權限組的作用

開發者可能要問了:一個規模一般的APP需要的權限最少也有7,8個,難道要一個個申請麼?這開發工作量可不小啊!

Google也考慮到了對開發者的影響,上面介紹的權限組的作用也就展現出來了:

如果應用請求其清單中列出的危險權限,而應用目前在權限組中沒有任何權限,則系統會向使用者顯示一個對話框,描述應用要通路的權限組。對話框不描述該組内的具體權限。例如,如果應用請求 READ_CONTACTS 權限,系統對話框隻說明該應用需要通路裝置的聯系資訊。如果使用者準許,系統将向應用授予其請求的權限。

如果應用請求其清單中列出的危險權限,而應用在同一權限組中已有另一項危險權限,則系統會立即授予該權限,而無需與使用者進行任何互動。例如,如果某應用已經請求并且被授予了 READ_CONTACTS 權限,然後它又請求 WRITE_CONTACTS,系統将立即授予該權限。

總結一下就是開發者申請危險權限時,隻要獲得過屬于同一組的其他權限的授權,即可立即獲得系統對該權限的授權,無需重複申請。

權限适配

權限适配主要受APP運作環境及APP開發使用的

targetSdkVersion

有關,分為以下幾種情況:

  • targetSdkVersion >= 23 && App運作在 >= 6.0(api 23)系統的終端上

    這種情況就會采用标準的Google**運作時動态權限機制**

  • targetSdkVersion >= 23 && App運作在 < 6.0(api 23)系統的終端上

    這種情況就會采用原權限機制即安裝時擷取授權機制

  • targetSdkVersion < 23 && App運作在 >= 6.0(api 23)系統的終端上

    這種情況就會采用相容模式,即在安裝時擷取授權。但是使用者可以在設定界面随時關閉/開啟 敏感權限。APP需做好權限關閉的邏輯處理。

  • targetSdkVersion < 23 && App運作在 < 6.0(api 23)系統的終端上

    這種情況就會采用原權限機制即安裝時擷取授權機制

注意:部分國産ROM存在自定義權限機制導緻差異,下節詳述。

Android 6.0 運作時動态權限适配

适配權限按步驟可以分為四步:權限檢查,權限申請,授權結果處理,權限解釋。

  • 權限檢查:可用的API較多,如下:
    • 使用

      Activity#checkSelfPermission(String permission)

      進行檢查
    • 使用

      ContextCompat#checkSelfPermission(Context context, String permission)

      進行檢查
    推薦使用

    ContextCompat#checkSelfPermission

    進行檢查,這樣可以不依賴Activity。

    已獲授權傳回

    PackageManager#PERMISSION_GRANTED

    ,未獲授權傳回

    PackageManager#PERMISSION_DENIED

  • 權限申請:可以同時申請多個權限,requestCode辨別目前申請,會彈出系統權限申請彈窗。
    • 使用

      Activity#requestPermissions(String[] permissions, int requestCode)

    • 使用

      ActivityCompat#requestPermissions(Activity activity,String[] permissions, int requestCode)

    推薦使用

    ActivityCompat#requestPermissions

    ,會根據API版本使用最佳方式。

    我們可以注意到,無論使用哪種方式進行權限申請都需要依賴Activity,因為系統在權限處理後是需要回調到Activity。

  • 授權結果處理:權限請求會通過接口回調的方式回調到Activity:
/**
   * @param requestCode 權限申請request code
   * @param permissions 申請的權限
   * @param grantResults 授權結果
   */
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
            @NonNull int[] grantResults) {
}
           
  • 權限解釋:Google提供了

    ActivityCompat#shouldShowRequestPermissionRationale

    API可以判斷使用者是否拒絕過目前申請權限,開發者可以彈出自定義框解釋申請權限原因
public boolean shouldShowRequestPermissionRationale(permission)
{
    、APP是第一次申請這個權限的話,傳回false
    、使用者拒絕,勾選了不再提示的話,傳回false
    、使用者拒絕,但是沒有勾選不再提示的話,傳回true
    是以如果想在第一次就給使用者提示,需要記錄權限是否申請過,沒有申請過的話,強制彈窗提示,而不能根據這個方法的傳回值來。
    最好在第一次給使用者提示,因為Google的權限申請文案本地化翻譯太差勁了,會導緻使用者很莫名其妙。
}
           

這幾步就是一個标準的權限申請處理步驟,我們可以看出步驟雖然不多,但是确實挺繁瑣的。權限一多,需要寫很多重複代碼。這裡我推薦大家一些處理的比較好的開源庫,可以讓權限申請更簡單:

1. EasyPermissions ,Google官方示例推薦。隻需關心權限處理結果即可,還可以結合注解使代碼更精簡。

2. PermissionsDispatcher ,封裝了大部分權限申請邏輯,使用友善。

3. AndPermission ,封裝的比較好,對國産ROM做了支援,相容Android8.0權限變化,API鍊式調用簡潔友善。

國産ROM自定義權限機制适配

相信很多開發者在機型适配或使用中會發現有些機型明明是6.0以下版本,但是也存在運作時權限管理機制。

這是因為部分國産ROM(MIUI等)在4.4以上自己使用了一套權限檢測機制,在這些機型上如果我們使用上一節介紹的6.0權限适配是無法适用的,具體的表現如下:

1. 使用

ContextCompat#checkSelfPermission

無法檢測出目前權限是否被授權,在API23以下manifest中注冊過就會傳回true,API23以上可能出現實際是被關閉但是傳回true的情況。

2. 不支援動态權限申請

那我們該如何檢測權限是否被正确授權?

雖然很坑爹,但是辦法還是有的。

查閱資料發現國産ROM中針對權限的開關可以被

AppOpsManager#noteOp(int op)

檢測出來,我們可以利用這個類來檢測權限授權狀态,這裡我們介紹這個個新的類

AppOpsManager

/**
 * API for interacting with "application operation" tracking.
 *
 * <p>This API is not generally intended for third party application developers; most
 * features are only available to system applications.  Obtain an instance of it through
 * {@link Context#getSystemService(String) Context.getSystemService} with
 * {@link Context#APP_OPS_SERVICE Context.APP_OPS_SERVICE}.</p>
 */
public class AppOpsManager {

}
           

AppOpsManager中将權限與OP_*操作碼做了一一映射,比如:

android.Manifest.permission.READ_CONTACTS

public static final int OP_READ_CONTACTS = ;
           

這些操作碼與支援的權限是在不斷添加的,我們可以通過觀察

public static final int _NUM_OP = 62;

知道目前API版本支援的操作總數,需要做好相容。

但是通過注釋可以知道,這個類主要是給系統應用使用的,并不提供給第三方應用開發者。具體表現在類中基本所有方法都被@hide标記,開發者是無法直接使用的。那該怎麼辦呢?

我們搜尋AppOpsManager有關的類,發現Google貼心的在v4包中提供了一個相容類

AppOpsManagerCompat

,這個類對開發者來說是開放的,并且從API4向後相容。

/**
     * 檢測權限,小米上使用
     * 
     * @param context
     * @param permission
     * @return
     */
    private static boolean hasSelfPermissionForXiaomi(Context context, String permission) {
        // 将permission轉換成OP_*
        String permissionToOp = AppOpsManagerCompat.permissionToOp(permission);
        if (permissionToOp == null) {
            // 不支援的權限,或者是normal permission
            return true;
        }
        int noteOp = AppOpsManagerCompat.noteOp(context, permissionToOp, android.os.Process.myUid(), context.getPackageName());
        // AppOpsManagerCompat 與 checkSelfPermission都檢測過則表明權限被開啟
        return noteOp == AppOpsManagerCompat.MODE_ALLOWED && ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED;
    }
           

我們使用這個方法就能對像小米這種自定義權限ROM進行權限檢查了,但在測試中發現這在< API 23上一直傳回true,這是什麼原因呢?我們看看

AppOpsManagerCompat

是如何實作的:

private static final AppOpsManagerImpl IMPL;
    static {
        if (Build.VERSION.SDK_INT >= ) {
            IMPL = new AppOpsManager23();
        } else {
            IMPL = new AppOpsManagerImpl();
        }
    }

    private static class AppOpsManagerImpl {
        public String permissionToOp(String permission) {
            // 直接傳回null,導緻檢測方法直接判斷為成功
            return null;
        }

        public int noteOp(Context context, String op, int uid, String packageName) {
            // 直接調用也會直接傳回MODE_IGNORED,不會進行檢測
            return MODE_IGNORED;
        }

        public int noteProxyOp(Context context, String op, String proxiedPackageName) {
            return MODE_IGNORED;
        }
    }

    private static class AppOpsManager23 extends AppOpsManagerImpl {
        @Override
        public String permissionToOp(String permission) {
            return AppOpsManagerCompat23.permissionToOp(permission);
        }

        @Override
        public int noteOp(Context context, String op, int uid, String packageName) {
            return AppOpsManagerCompat23.noteOp(context, op, uid, packageName);
        }

        @Override
        public int noteProxyOp(Context context, String op, String proxiedPackageName) {
            return AppOpsManagerCompat23.noteProxyOp(context, op, proxiedPackageName);
        }
    }
           

我們發現

AppOpsManagerCompat

在23以下的實作類中,一些方法直接傳回指定值并不會進行權限檢測。

這就尴尬了,在低版本

AppOpsManagerCompat

失效,而且

AppOpsManager

方法又是@hide無法直接使用,那隻能用反射去實作了:

/**
     * 通過反射調用 AppOpsManager#noteOp
     * 
     * @param context
     * @param op
     * @return
     */
    public static boolean noteOp(Context context, int op) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            AppOpsManager appOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
            if (appOpsManager != null) {
                try {
                    Method method = AppOpsManager.class.getDeclaredMethod("noteOp", Integer.TYPE, Integer.TYPE,
                                String.class);
                    int noteOp = (int) method.invoke(appOpsManager, op, Process.myUid(), context.getPackageName());
                    return noteOp == AppOpsManager.MODE_ALLOWED;
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }
           

總結:

對于權限檢測:

1. Build.VERSION.SDK_INT >= 23時,直接使用我們上面的

hasSelfPermissionForXiaomi

方法

2. Build.VERSION.SDK_INT < 23時,使用我們上面的反射方法

noteOp

對于權限申請:

1. Build.VERSION.SDK_INT >= 23時,使用上節介紹的标準運作時權限申請方式

2. Build.VERSION.SDK_INT < 23時,沒有辦法申請API,隻能通過引導使用者跳轉權限設定頁進行配置。

特殊權限适配

有幾個權限其行為方式與正常權限及危險權限都不同。

SYSTEM_ALERT_WINDOW

WRITE_SETTINGS

特别敏感,是以大多數應用不應該使用它們。

SYSTEM_ALERT_WINDOW

我們接觸的比較多,有些功能像桌面懸浮窗,桌面歌詞等,隻要是通過向WindowManager裡面添加View方式的,都需要通過這個權限來實作。

這裡拿

SYSTEM_ALERT_WINDOW

來講解如何處理這種特殊權限:

// AndroidManifest中添權重限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
           
// 定義權限申請request code
public static int ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE= ;

public void requestPermission() {
    // 通過Settings.canDrawOverlays(this)可以檢測是否包含SYSTEM_ALERT_WINDOW權限
        if (!Settings.canDrawOverlays(this)) {
            // 申請權限,跳轉到系統權限設定頁面
            Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
                    Uri.parse("package:" + getPackageName()));
            startActivityForResult(intent, ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE);
        }
    }

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    // 在onActivityResult中判斷是否授權成功
    if (requestCode == ACTION_MANAGE_OVERLAY_PERMISSION_REQUEST_CODE) {
        if (Settings.canDrawOverlays(this)) {
            // 授權成功
        }
    }
}
           

Tips: 懸浮窗實作可以使用

LayoutParams.TYPE_TOAST

替換

LayoutParams.TYPE_SYSTEM_ALERT

LayoutParams.TYPE_TOAST

的使用不需要權限。(LayoutParams.TYPE_TOAST限制就是Android 4.4 (API level 18)及以下使用TYPE_TOAST無法接收觸摸事件)

“不依賴Activity”進行權限申請

我們在6.0運作時動态權限适配一節中說到了,目前API進行權限申請都需要依賴Activity,因為系統在權限處理後是需要回調到Activity做處理的。

大部分場景都是可以滿足的,但是對于沒有Activity或者需要在不存在具體Activity進行權限申請的情況,就有點尴尬了。

輸入法就遇到了這種尴尬的情況,輸入法是系統服務,鍵盤區展示出來的其實是個Dialog。

有個這種場景:使用者在輸入法上點選語言輸入,輸入法需要擷取錄音權限才能進一步操作,那如何在這種場景去申請權限呢?

這就是我們這節的重點了,如何“不依賴Activity”進行權限申請,我們的方案如下:

我們這裡的“不依賴Activity”指的是不依賴具體業務Activity,申請權限時啟動一個隻用于權限申請的Activity來完成申請流程。

這裡我結合easypermissions 實作的例子如下:

PermissionsController封裝了權限請求,通過統一API進行權限申請;具體申請交由EasyPermission(可替換任何庫),僅處理對權限申請結果監聽回調的處理

申請權限時僅需調用,并在回調中處理結果即可

PermissionsController.get(mContext).requestPermissions(this, null, RC_RECORD_PERM, Manifest.permission
                .RECORD_AUDIO);
           
// PermissionsController.java
/**
 * <pre>
 *     author : lee
 *     e-mail : [email protected]
 *     time   : 2017/07/11
 *     desc   : 權限控制類,用于處理權限請求時機控制等
 *     version: 1.0
 * </pre>
 */


public class PermissionsController {

    private static final String TAG = "PermissionsTag";
    private static PermissionsController sInstance;
    private final Context mContext;
    // 權限回調監聽
    private final SparseArray<PermissionsResultCallback> mRequestIdToCallback = new SparseArray<>();


    public PermissionsController(Context context) {
        mContext = context.getApplicationContext();
    }

    @Nonnull
    public static synchronized PermissionsController get(@Nonnull Context context) {
        if (sInstance == null) {
            sInstance = new PermissionsController(context);
        }
        return sInstance;
    }

    /**
     * 請求權限;封裝了鍵盤和正常請求
     *
     * @param callback 申請權限回調,不能為空
     * @param activity 權限處理activity,鍵盤區傳null,其他非空
     * @param permissionsToRequest 需要申請的權限,多個權限可組合同時申請
     */
    public synchronized void requestPermissions(@Nonnull PermissionsResultCallback callback,
                                                Activity activity,
                                                int requestId,
                                                String... permissionsToRequest) {
        List<String> deniedPermissions = getDeniedPermissions(mContext, permissionsToRequest);
        if (deniedPermissions.isEmpty()) {
            // 被拒絕的權限為空,直接回調成功
            callback.onPermissionsGranted(requestId, deniedPermissions);
            return;
        }

        String[] permissionsArray = deniedPermissions.toArray(
                new String[deniedPermissions.size()]);

        if (activity != null) {
            String rationale = activity.getString(R.string.permission_msg);
            requestPermissions(activity, rationale, requestId, permissionsArray);
        } else {
            mRequestIdToCallback.put(requestId, callback);
            PermissionsActivity.run(mContext, requestId, permissionsArray);
        }
    }

    /**
     * 正常請求方式,用于在activity中
     *
     * @param host 權限處理activity
     * @param rationale 權限申請解釋文本,僅當使用者拒絕首次後才能彈出
     * @param requestCode 權限申請request code
     * @param perms 需要申請的權限,多個權限可組合同時申請
     */
    public static void requestPermissions(
            @NonNull Activity host, @NonNull String rationale,
            int requestCode, @NonNull String... perms) {
        EasyPermissions.requestPermissions(host, rationale, requestCode, perms);
    }

    /**
     * 申請成功回調
     *
     * @param requestCode 權限申請request code
     * @param perms 授權成功權限
     */
    public synchronized void onPermissionsGranted(int requestCode, List<String> perms) {
        PermissionsResultCallback permissionsResultCallback = mRequestIdToCallback.get(requestCode);
        mRequestIdToCallback.remove(requestCode);
        if (permissionsResultCallback != null) {
            permissionsResultCallback.onPermissionsGranted(requestCode, perms);
        }
    }

    /**
     * 申請失敗回調
     *
     * @param requestCode 權限申請request code
     * @param perms 授權失敗權限
     */
    public synchronized void onPermissionsDenied(int requestCode, List<String> perms) {
        PermissionsResultCallback permissionsResultCallback = mRequestIdToCallback.get(requestCode);
        mRequestIdToCallback.remove(requestCode);
        if (permissionsResultCallback != null) {
            permissionsResultCallback.onPermissionsDenied(requestCode, perms);
        }
    }

    /**
     *  權限申請結果處理,需在activity onRequestPermissionsResult回調中調用
     * @param requestCode 權限申請request code
     * @param permissions 申請的權限
     * @param grantResults 授權結果
     * @param receivers 授權結果處理
     */
    public static void onRequestPermissionsResult(int requestCode,
                                                  @NonNull String[] permissions,
                                                  @NonNull int[] grantResults,
                                                  @NonNull Object... receivers){
        EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, receivers);
    }

    /**
     * 存在一些權限被永久的拒絕
     *
     * @param host
     * @param deniedPermissions
     * @return
     */
    public static boolean somePermissionPermanentlyDenied(@NonNull Activity host,
                                                          @NonNull List<String> deniedPermissions) {
        return EasyPermissions.somePermissionPermanentlyDenied(host, deniedPermissions);
    }


    /**
     * 展示跳轉設定頁提示窗
     *
     * @param host
     */
    public static void showAppSettingDialog(@NonNull Activity host) {
        new AppSettingsDialog.Builder(host).build().show();
    }


    /**
     * 是否已擷取目前權限
     *
     * @param context
     * @param perms
     * @return
     */
    public static boolean hasPermissions(Context context, @NonNull String... perms) {
        return EasyPermissions.hasPermissions(context, perms);
    }

    /**
     * 擷取未授權權限
     *
     * @param context
     * @param permissions
     * @return
     */
    public static List<String> getDeniedPermissions(Context context, String... permissions) {
        final List<String> deniedPermissions = new ArrayList<String>();
        for (String permission : permissions) {
            if (ContextCompat.checkSelfPermission(context, permission)
                    != PackageManager.PERMISSION_GRANTED) {
                deniedPermissions.add(permission);
            }
        }
        return deniedPermissions;
    }

    /**
     * 權限回調接口,直接繼承 EasyPermissions.PermissionCallbacks
     */
    public interface PermissionsResultCallback extends EasyPermissions.PermissionCallbacks{

    }

}
           

PermissionsActivity 統一處理權限申請Activity,因為本身隻是個載體,樣式透明并不設定任何ContentView。

// AndroidManifest.xml
<!-- 申請權限activity -->
        <activity android:name=".permission.PermissionsActivity"
                  android:theme="@style/permissionTheme"
                  android:taskAffinity=""
                  android:configChanges="orientation|screenSize|keyboardHidden"/>
           
// styles.xml
<style name="permissionTheme" parent="@android:style/Theme.Translucent.NoTitleBar">
        <item name="android:statusBarColor" tools:targetApi="lollipop">@android:color/transparent</item>
    </style>
           
// PermissionsActivity.java
/**
 * <pre>
 *     author : lee
 *     e-mail : [email protected]
 *     time   : 2017/07/11
 *     desc   : 請求權限的Activity
 *     version: 1.0
 * </pre>
 */


public class PermissionsActivity extends Activity implements PermissionsController.PermissionsResultCallback {

    private static final String TAG = "PermissionsActivity";
    // 需要請求的權限
    public static final String EXTRA_PERMISSION_REQUESTED_PERMISSIONS = "requested_permissions";
    // 請求權限的requestCode
    public static final String EXTRA_PERMISSION_REQUEST_CODE = "request_code";
    // 預設無效requestCode
    private static final int INVALID_REQUEST_CODE = -;

    private int mPendingRequestCode = INVALID_REQUEST_CODE;


    /**
     * 啟動一個權限Activity,并立即請求權限
     *
     * @param context
     * @param requestCode
     * @param permissionStrings
     */
    public static void run(
            @NonNull Context context, int requestCode, @NonNull String... permissionStrings) {
        Intent intent = new Intent(context.getApplicationContext(), PermissionsActivity.class);
        intent.putExtra(EXTRA_PERMISSION_REQUESTED_PERMISSIONS, permissionStrings);
        intent.putExtra(EXTRA_PERMISSION_REQUEST_CODE, requestCode);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
        context.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mPendingRequestCode = (savedInstanceState != null)
                ? savedInstanceState.getInt(EXTRA_PERMISSION_REQUEST_CODE, INVALID_REQUEST_CODE)
                : INVALID_REQUEST_CODE;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt(EXTRA_PERMISSION_REQUEST_CODE, mPendingRequestCode);
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (mPendingRequestCode == INVALID_REQUEST_CODE) {
            final Bundle extras = getIntent().getExtras();
            final String[] permissionsToRequest =
                    extras.getStringArray(EXTRA_PERMISSION_REQUESTED_PERMISSIONS);
            mPendingRequestCode = extras.getInt(EXTRA_PERMISSION_REQUEST_CODE);
            PermissionsController.requestPermissions(this, getString(R.string.permission_msg), mPendingRequestCode,
                    permissionsToRequest);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[]
            grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        PermissionsController.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
        mPendingRequestCode = INVALID_REQUEST_CODE;
    }

    @Override
    public void onPermissionsGranted(int requestCode, List<String> perms) {
        PermissionsController.get(this).onPermissionsGranted(requestCode, perms);
        finish();
    }

    @Override
    public void onPermissionsDenied(int requestCode, List<String> perms) {
        PermissionsController.get(this).onPermissionsDenied(requestCode, perms);
        if (PermissionsController.somePermissionPermanentlyDenied(this, perms)) {
            if (BuildConfig.DEBUG) {
                Log.d("PermissionsTag", "存在一些權限被永久的拒絕");
            }
            PermissionsController.showAppSettingDialog(this);
        }
        finish();
    }
}
           

以上代碼還存在優化空間,但是思路就是這樣。

Android8.0權限适配

我們之前在Android6.0權限申請時介紹過開發者申請危險權限時,隻要獲得過屬于同一組的其他權限的授權,即可立即獲得系統對該權限的授權,無需重複申請這一特性。

但是Android8.0對此行為發生了變化,具體表現為:系統隻會授予應用明确請求的權限。然而一旦使用者為應用授予某個權限,則所有後續對該權限組中權限的請求都将被自動準許,不會顯示彈框。

差別:

1. Android6.0在申請權限成功後,屬于同一組的其他權限會自動授權,無需申請。

2. Android8.0在申請權限成功後,在使用同一組權限時還是需要進行申請操作,隻是申請會被自動授權。不申請則不會獲得授權。

是以為了相容8.0的變化,開發者在申請權限時最好一同申請權限組内其他權限,做好權限檢查。更多内容可參考:Android8.0運作時權限政策變化和适配方案

參考

Android權限機制與适配經驗

Android 開發者必知必會的權限管理知識

Android8.0運作時權限政策變化和适配方案