Android許可權檢查API checkSelfPermission失效問題

看書的小蝸牛發表於2017-10-13

Android6.0之後,許可權分為install時的許可權跟執行時許可權,如果我們的targetSdkVersion>=23,install許可權同runtime許可權是分開的,app也要針對6.0已經做適配,沒什麼大問題,無論執行在舊版本還是6.0之後的手機上都ok,這也是Google推薦的適配方案。但是如果targetSdkVersion < 23 ,在6.0之後的手機上就會遇到一些問題,因為在這種情況下預設許可權是全部授予的,但是可能會被使用者手動取消,而Context的checkSelfPermission許可權檢查介面也會失效,因為這個API介面6.0之後用的是runtime-permission的模型,而targetSdkVersion < 23 時候,app只有intalled的許可權,其granted值一直是true,也可以看做是全部是授權了的,就算在設定裡面取消授權也不會影響installed許可權的granted,而Context的checkSelfPermission的介面卻是用granted這個值作為授權與否的參考,所以如果用這個介面,那得到的一定是授權了,是不準確的,如下:targetSdkVersion < 23的時候,package資訊中的許可權包含app申請的全部許可權,

<package name="com.snail.labaffinity" codePath="/data/app/com.snail.labaffinity-1" nativeLibraryPath="/data/app/com.snail.labaffinity-1/lib" publicFlags="944291398" privateFlags="0" ft="15f0f58e548" it="15f0f58e548" ut="15f0f58e548" version="1" userId="10084">
    <perms>
        <item name="android.permission.ACCESS_FINE_LOCATION" granted="true" flags="0" />
        <item name="android.permission.INTERNET" granted="true" flags="0" />
        <item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
        <item name="android.permission.ACCESS_COARSE_LOCATION" granted="true" flags="0" />
        <item name="android.permission.READ_PHONE_STATE" granted="true" flags="0" />
        <item name="android.permission.CALL_PHONE" granted="true" flags="0" />
        <item name="android.permission.CAMERA" granted="true" flags="0" />
        <item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
        <item name="android.permission.READ_CONTACTS" granted="true" flags="0" />
    </perms>
    <proper-signing-keyset identifier="18" />
</package>
複製程式碼

這種情況下,該做法就會引發問題,先從原始碼看一下為什麼targetSdkVersion < 23 Context 的 checkSelfPermission方法失效,之後再看下在targetSdkVersion < 23 的時候,如何判斷6.0的手機是否被授權。

為什麼targetSdkVersion < 23 Context 的 checkSelfPermission失效

跟蹤一下原始碼發現Context 的 checkSelfPermission最終會呼叫ContextImp的checkPermission,最終呼叫

@Override
public int checkPermission(String permission, int pid, int uid) {
    if (permission == null) {
        throw new IllegalArgumentException("permission is null");
    }

    try {
        return ActivityManagerNative.getDefault().checkPermission(
                permission, pid, uid);
    } catch (RemoteException e) {
        return PackageManager.PERMISSION_DENIED;
    }
}
複製程式碼

最終請求ActivityManagerService的checkPermission,經過預處理跟中轉最後會呼叫PackageManagerService的checkUidPermission

@Override
public int checkUidPermission(String permName, int uid) {
    final int userId = UserHandle.getUserId(uid);
    synchronized (mPackages) {
    <!--查詢許可權-->
        Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
        if (obj != null) {
            final SettingBase ps = (SettingBase) obj;
            final PermissionsState permissionsState = ps.getPermissionsState();
            <!--檢驗授權-->
            if (permissionsState.hasPermission(permName, userId)) {
                return PackageManager.PERMISSION_GRANTED;
            }
            if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
                    .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
                return PackageManager.PERMISSION_GRANTED;
            }
        } ...        }

    return PackageManager.PERMISSION_DENIED;
}
複製程式碼

PackageManagerService會從mSettings全域性變數中獲取許可權,然後進一步驗證許可權是否被授予

public boolean hasPermission(String name, int userId) {
    enforceValidUserId(userId);

    if (mPermissions == null) {
        return false;
    }

    PermissionData permissionData = mPermissions.get(name);
    return permissionData != null && permissionData.isGranted(userId);
}
複製程式碼

這裡的檢查點只有兩點,第一個是是否有這個許可權,第二是是否是Granted,對於targetSdkVersion<23的所有的許可權都在packages.xml中,grante一直是true,無法被跟新,為什麼無法被更新呢?看一下6.0之後的授權與取消授權的函式,首先看一個變數mAppSupportsRuntimePermissions

    mAppSupportsRuntimePermissions = packageInfo.applicationInfo
            .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
    mAppOps = context.getSystemService(AppOpsManager.class);
複製程式碼

mAppSupportsRuntimePermissions定義在AppPermissionGroup中,6.0之後許可權都是分組的,對於targetSdkVersion<23的APP來說,很明顯是不支援動態許可權管理的,那麼授權跟取消授權函式就很不一樣如下: 授權函式

public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
    final int uid = mPackageInfo.applicationInfo.uid;

    for (Permission permission : mPermissions.values()) {
        if (filterPermissions != null
                && !ArrayUtils.contains(filterPermissions, permission.getName())) {
            continue;
        }

        <!--關鍵點1 如果支援,也即是targetSdkVersion>23那走6.0動態許可權管理那一套-->
        if (mAppSupportsRuntimePermissions) {
            // Do not touch permissions fixed by the system.
            if (permission.isSystemFixed()) {
                return false;
            }
           // Ensure the permission app op enabled before the permission grant.
            if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
                permission.setAppOpAllowed(true);
                mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
            }
           // Grant the permission if needed.
            if (!permission.isGranted()) {
                permission.setGranted(true);
                <!--關鍵點2更新其runtime-permission.xml 中granted值-->
                mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                        permission.getName(), mUserHandle);
            }
			...
        } else {
            if (!permission.isGranted()) {
                continue;
            }

            int killUid = -1;
            int mask = 0;
            if (permission.hasAppOp()) {
                if (!permission.isAppOpAllowed()) {
                    permission.setAppOpAllowed(true);
                    <!--關鍵點3 設定為AppOpsManager.MODE_ALLOWED-->
                    mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                    killUid = uid;
                }
            }
				<!--關鍵點4 更新其PermissionFlags-->
            if (mask != 0) {
                mPackageManager.updatePermissionFlags(permission.getName(),
                        mPackageInfo.packageName, mask, 0, mUserHandle);
            }
        }
    }
   return true;
}
複製程式碼

可以看出6.0之後的手機,針對targetSdkVersion是否高於23做了不同處理,如果targetSdkVersion>=23支援動態許可權管理,那就更新動態許可權,並將其持久化到runtime-permission.xml中,並更新其granted值,如果targetSdkVersion<23 ,也即是不知道6.0的動態管理,那就只更新AppOps,這是4.3引入的老的動態許可權管理模型,不過這裡主要是將許可權持久化到appops.xml中,不過對於其granted的值是沒有做任何更新的,僅僅是更新了packages.xml中的flag,這個flag可以配合appops.xml標識是否被授權(對於targetSdkVersion<23的適用),以上就是為什麼context checkSelfPermission會失效的原因,涉及程式碼很多,不一一列舉,對於取消授權revokeRuntimePermissions函式,模型一樣,不在贅述,那下面看第二個問題,如何檢查targetSdkVersion<23 app 在6.0以上手機的許可權呢? Google給了一個相容類PermissionChecker,這個類可以間接使用AppOpsService那一套邏輯,獲取到許可權是否被授予。

targetSdkVersion < 23 的時候,如何判斷6.0的手機是否被授權

targetSdkVersion < 23的時候,6.0許可權檢查API失效了,不過通過上面的分析指導,在設定中許可權的操作仍然會被儲存記憶體及持久化到appops.xml檔案中,這裡就是走的AppOpsService那一套,AppOpsService可以看做6.0為了相容老APP而保留的一個附加的許可權管理模型,在6.0之後的系統中,可以看做runtime許可權管理的補充,其實AppOpsService這套在4.3就推出了,不過不太靈活,基本沒啥作用,之前只用到了通知管理。看一下Google提供的一個相容類PermissionChecker如何做的:

public static int checkPermission(@NonNull Context context, @NonNull String permission,
            int pid, int uid, String packageName) {
        <!--對於targetSdkVersion < 23 一定是true-->
        if (context.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_DENIED) {
            return PERMISSION_DENIED;
        }
	        String op = AppOpsManagerCompat.permissionToOp(permission);
        <!--看看這個許可權是不是能夠操作,動態授權與取消授權  如果不能,說明許可權一直有-->
        if (op == null) {
            return PERMISSION_GRANTED;
        }
       <!--如果能夠取消授權,就看現在是不是處於許可權被允許的狀態,如果不是,那就是使用者主動關閉了許可權-->
        if (AppOpsManagerCompat.noteProxyOp(context, op, packageName)
                != AppOpsManagerCompat.MODE_ALLOWED) {
            return PERMISSION_DENIED_APP_OP;
        }
	  return PERMISSION_GRANTED;
    }
複製程式碼

對於6.0之後的手機AppOpsManagerCompat.noteProxyOp會呼叫AppOpsManager23的noteProxyOp,

private static class AppOpsManagerImpl {
    public String permissionToOp(String permission) {
        return null;
    }

    public int noteOp(Context context, String op, int uid, String packageName) {
        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);
    }
}
複製程式碼

上面的是6.0之前對應的API,下面的是6.0及其之後對應的介面,AppOpsManagerCompat23.noteProxyOp會進一步呼叫AppOpsManager的noteProxyOp向AppOpsService傳送請求

public static int noteProxyOp(Context context, String op, String proxiedPackageName) {
    AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
    return appOpsManager.noteProxyOp(op, proxiedPackageName);
}
複製程式碼

最後看一下AppOpsService如何檢查許可權

private int noteOperationUnchecked(int code, int uid, String packageName,
        int proxyUid, String proxyPackageName) {
    synchronized (this) {
        Ops ops = getOpsLocked(uid, packageName, true);
        Op op = getOpLocked(ops, code, true);
        if (isOpRestricted(uid, code, packageName)) {
            return AppOpsManager.MODE_IGNORED;
        }
        op.duration = 0;
        final int switchCode = AppOpsManager.opToSwitch(code);
        UidState uidState = ops.uidState;
        if (uidState.opModes != null) {
            final int uidMode = uidState.opModes.get(switchCode);
                op.rejectTime = System.currentTimeMillis();
                return uidMode;
            }
        }
        final Op switchOp = switchCode != code ? getOpLocked(ops, switchCode, true) : op;
        if (switchOp.mode != AppOpsManager.MODE_ALLOWED) {
            op.rejectTime = System.currentTimeMillis();
            return switchOp.mode;
        }
        op.time = System.currentTimeMillis();
        op.rejectTime = 0;
        op.proxyUid = proxyUid;
        op.proxyPackageName = proxyPackageName;
        return AppOpsManager.MODE_ALLOWED;
    }
}
複製程式碼

UidState可以看做每個應用對應的許可權模型,這裡的資料是有一部分是從appops.xml恢復回來,也有部分是在更新許可權時候加進去的,這部分變化最終都要持久化到appops.xml中去,不過持久化比較滯後,一般要等到手機更新許可權後30分鐘才會持久化到appops.xml中,這裡的資料一般是在啟動的時候被恢復重建,在啟動ActivityManagerService服務的時候,會在其建構函式總啟動AppOpsService服務:

public ActivityManagerService(Context systemContext) {
...
    mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml"), mHandler);
...}    
複製程式碼

在AppOpsService的建構函式中會將持久化到appops.xml中的許可權資訊恢復出來,並存到記憶體中去,

public AppOpsService(File storagePath, Handler handler) {
    mFile = new AtomicFile(storagePath);
    mHandler = handler;
    // 新建的時候就會讀取
    readState();
}
複製程式碼

readState就是將持久化的UidState資料給重新讀取出來,如下mFile其實就是appops.xml的檔案物件

void readState() {
    synchronized (mFile) {
        synchronized (this) {
            FileInputStream stream;
            try {
                stream = mFile.openRead();
            } catch (FileNotFoundException e) {
            }
            boolean success = false;
            mUidStates.clear();
            try {
                XmlPullParser parser = Xml.newPullParser();
                parser.setInput(stream, StandardCharsets.UTF_8.name());
                int type;
                int outerDepth = parser.getDepth();
                while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                        && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
                    if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
                        continue;
                    }
                    String tagName = parser.getName();
                    if (tagName.equals("pkg")) {
                        readPackage(parser);
                    } else if (tagName.equals("uid")) {
                        readUidOps(parser);
                    } else {
                        XmlUtils.skipCurrentTag(parser);
                    }
                }
                success = true;
            ...}
複製程式碼

讀取之後,當使用者操作許可權的時候,也會隨機的更新這裡的標記,只看下targetSdkVersion<23的,

   public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
        final int uid = mPackageInfo.applicationInfo.uid;

        for (Permission permission : mPermissions.values()) {
            if (filterPermissions != null
                    && !ArrayUtils.contains(filterPermissions, permission.getName())) {
                continue;
            }
            <!--關鍵點1 如果支援,也即是targetSdkVersion>23那走6.0動態許可權管理那一套-->
            if (mAppSupportsRuntimePermissions) {
				...
            } else {
                if (!permission.isGranted()) {
                    continue;
                }
                int killUid = -1;
                int mask = 0;
                if (permission.hasAppOp()) {
                    if (!permission.isAppOpAllowed()) {
                        permission.setAppOpAllowed(true);
                        <!--關鍵點3 設定為AppOpsManager.MODE_ALLOWED-->
                        mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                        killUid = uid;
                    }
                }
                if (mask != 0) {
                    mPackageManager.updatePermissionFlags(permission.getName(),
                            mPackageInfo.packageName, mask, 0, mUserHandle);
                }
            }
        }
       return true;
    }
複製程式碼

拿授權的場景來說,其實關鍵就是 mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED)函式,這個函式會更新AppOpsService中對於許可權的標記,並將許可權是否授予的資訊持久化到appops.xml及packages.xml,不同版本可能有差別,有可能需要appops.xml跟packages.xml配合才能確定是否授予許可權,具體沒深究,有興趣可以自行分析。

@Override
public void setUidMode(int code, int uid, int mode) {
    if (Binder.getCallingPid() != Process.myPid()) {
        mContext.enforcePermission(android.Manifest.permission.UPDATE_APP_OPS_STATS,
                Binder.getCallingPid(), Binder.getCallingUid(), null);
    }
    verifyIncomingOp(code);
    code = AppOpsManager.opToSwitch(code);

    synchronized (this) {
        final int defaultMode = AppOpsManager.opToDefaultMode(code);
       <!--更新操作許可權-->
        UidState uidState = getUidStateLocked(uid, false);
        if (uidState == null) {
            if (mode == defaultMode) {
                return;
            }
            uidState = new UidState(uid);
            uidState.opModes = new SparseIntArray();
            uidState.opModes.put(code, mode);
            mUidStates.put(uid, uidState);
            scheduleWriteLocked();
        } else if (uidState.opModes == null) {
            if (mode != defaultMode) {
                uidState.opModes = new SparseIntArray();
                uidState.opModes.put(code, mode);
                scheduleWriteLocked();
            }
        } else {
            if (uidState.opModes.get(code) == mode) {
                return;
            }
            if (mode == defaultMode) {
                uidState.opModes.delete(code);
                if (uidState.opModes.size() <= 0) {
                    uidState.opModes = null;
                }
            } else {
                uidState.opModes.put(code, mode);
            }
            <!--持久化到appops.xml-->
            scheduleWriteLocked();
        }
    }
  ...
}
複製程式碼

這裡有一點注意:scheduleWriteLocked並不是立即執行寫操作,而是比更新記憶體滯後,一般滯後30分鐘

static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;
複製程式碼

30分鐘才會去更新 ,不過記憶體中都是最新的 ,如果直接刪除appops.xml,然後意外重啟,比如adb reboot bootloader,那麼你的所有AppOpsService許可權標記將會被清空,經過驗證,是符合預期的,也就說,targetSdkVersion<23的情況下,Android6.0以上的手機,它的許可權操作是持久化在appops.xml中的,一般關機的時候,會持久化一次,如果還沒來得及持久化,異常關機,就會丟失,這點同runtime-permission類似,異常關機也會丟失,不信可以試驗一下 。

在targetSdkVersion>=23的時候,對於 SDK>=23的機器如何檢測許可權

targetSdkVersion>=23系統已經提供了比較合理的檢測手段,PermisionChecker的checkPermission就可以,不過,這裡需要注意的是,AppOpsService對於targetSdkVersion>=23的時候就不能用了,這裡可能是Android的一個bug,當targetSdkVersion>=23而SDK_Version>=23的,對於AppOpsService,許可權的授予跟撤銷不是配對的,如下,先簡單看下授權:

   public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
        final int uid = mPackageInfo.applicationInfo.uid;

        for (Permission permission : mPermissions.values()) {

            if (mAppSupportsRuntimePermissions) {
 	  					<!--關鍵點1 同時更新runtim-permission及Appops-->
                 if (permission.hasAppOp() && !permission.isAppOpAllowed()) {
                    permission.setAppOpAllowed(true);
                    mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                }
                if (!permission.isGranted()) {
                    permission.setGranted(true);
                    mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                            permission.getName(), mUserHandle);
                }
            } else {
                if (!permission.isGranted()) {
                    continue;
                }

                int killUid = -1;
                int mask = 0;
				<!--關鍵點2 更新Appops-->

                if (permission.hasAppOp()) {
                    if (!permission.isAppOpAllowed()) {
                        permission.setAppOpAllowed(true);
                        // Enable the app op.
                        mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                        killUid = uid;
                    }
              ...
            }
        }

        return true;
    }
複製程式碼

可見,對於6.0的系統,無論targetSdkVersion是否>=23,在授權的時候,都會更新appops.xml,那取消授權呢?

public boolean revokeRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
        final int uid = mPackageInfo.applicationInfo.uid;
        for (Permission permission : mPermissions.values()) {
            ...
            if (mAppSupportsRuntimePermissions) {
                if (permission.isSystemFixed()) {
                    return false;
                }

                // Revoke the permission if needed.
                if (permission.isGranted()) {
                    permission.setGranted(false);
                    mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
                            permission.getName(), mUserHandle);
                }
                <!--關鍵點1 這裡沒有使用mAppOps.setUidMode更新appops.xml檔案->
                
            } else {
                // Legacy apps cannot have a non-granted permission but just in case.
                if (!permission.isGranted()) {
                    continue;
                }

                int mask = 0;
                int flags = 0;
                int killUid = -1;
                if (permission.hasAppOp()) {
                    if (permission.isAppOpAllowed()) {
                   <!--關鍵點2 這裡使用mAppOps.setUidMode更新appops.xml檔案->
                        mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED);
                        killUid = uid;
                    }
                  ...
            }
        }

        return true;
    }
複製程式碼

看關鍵點1 ,如果targetSdkVersion>=23在取消授權的時候,是不會更新appops.xml的,只有在targetSdkVersion<23的時候,才會向關鍵點2,撤銷授權。也就是說對於targetSdkVersion>=23的時候,不要用AppOpsManager了。

對於6.0以下的手機許可權如何檢測

對於Android6.0以下的手機,不需要關心targetVersion。先說個自己驗證的結果:基本沒法檢測,同時也不需要檢測,就算檢測出來也沒有多大意義,因為,觸發時機是在真正的呼叫服務時候。對於4.3到6.0之前的國產ROM,雖然採用AppopsManagerService,但是並未按照Google的模型對所有許可權進行適配,在這個模型下,也就適配了兩個許可權,

  • 通知許可權 public static final int OP_POST_NOTIFICATION = 11;
  • 懸浮窗許可權 public static final int OP_SYSTEM_ALERT_WINDOW = 24;

Google發行版的APPOpsService,基本是把整個鑑權邏輯給遮蔽了,通過CM的原始碼,課對這部分程式碼窺探一斑,如果整個許可權都採用4.3許可權管理模型,在拒絕一項許可權的時候,這個操作會被持久化到appops.xml中去,但是具體看下去,其實並不是如此,這種機制只對以上兩個許可權生效:

	<pkg n="com.xxx">
	<uid n="10988">
	<!--關鍵點1-->
	<op n="11" m="1" t="1513145979969" r="1521550658067" />
	<op n="12" t="1521550651593" />
	<op n="29" t="1521550682769" />

	<pkg n="com.wandoujia.phoenix2.usbproxy">
	<uid n="10969">
	<op n="4" t="1517279031173" />
	 <!--關鍵點2-->
	<op n="11" m="1" t="1510889291834" r="1517279030708" />
	<op n="14" t="1517293452801" />
	<!--關鍵點3-->
	<op n="24" m="1" />
	<op n="40" t="1513599239364" d="600011" />
複製程式碼

國產rom中,假如你拒絕授權位置許可權,按照AppOpsService模型,該操作應該被持久化到appops.xml中去,但是,結果並非如此,也就是說,對於其他許可權,國產ROM應該是自己糊弄了一套持久管理,持久化Android系統API無法訪問的地方,僅僅為自身ROM可見。appops.xml真正被系統使用時從Android6.0開始,其實Android6.0是有兩套許可權管理的,這其實很混亂,不知道Google怎麼想的,不過6.0似乎也有漏洞:許可權的授予跟回收許可權好像並不配對

那麼這就帶來了一個問題,在Android4.3到Android6.0之間的版本,並沒有同一個API來檢測是否獲取了某種許可權,因為你動態更新的許可權並未持久化到appops.xml中去。對於Android6.0之前的ROM,雖然不能檢測,但完全可以直接用服務,不會崩潰,因為如果真需要鑑權,它的鑑權時機其實是在服務使用的時候。AppopsManager在6.0之前,只能用來檢測通知,可能還有懸浮窗。

檢查許可權的解決方案(除去通知許可權)

public boolean selfPermissionGranted(Context context, String permission) {

	boolean ret = true;
	if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
		if (targetSdkVersion >= Build.VERSION_CODES.M) {
			ret = context.checkSelfPermission(permission) == PackageManager.PERMISSION_GRANTED;
		} else {
	      ret = PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED;
		}
         	}else{
              return true;
          }
	return ret;
}	    
複製程式碼

或者全部採用PermissionChecker的checkSelfPermission:

public boolean selfPermissionGranted(Context context, String permission) {
   return  PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED;
}	
複製程式碼

總結

Android6.0系統其實支援兩種動態管理,runtime-permission及被閹割的AppOpsService,當targetSdkVersion>23的時候,採用rumtime-permission,當 targetSdkVersion<23的時候,兩者兼有,其實targetSdkVersion<23的時候,仍然可以動態申請6.0的許可權,前提是你要採用23之後的compileSdkVersion,只有這樣才能用相應的API,不過還是推薦升級targetSdkVersion,這才是正道。對於Android6.0以下的手機,除了通知(可能還有懸浮窗),其他許可權基本都沒有系統的檢測手段,無論Context的checkPermission還是AppopsManager的checkOp,基本都是對Android6.0之後才有效。

作者:看書的小蝸牛 原文連結:Android許可權檢查API checkSelfPermission問題 僅供參考,歡迎指正

相關文章