Android權(quán)限檢查API checkSelfPermission失效問題

Android6.0之后,權(quán)限分為install時的權(quán)限跟運(yùn)行時權(quán)限逊笆,如果我們的targetSdkVersion>=23难裆,install權(quán)限同runtime權(quán)限是分開的乃戈,app也要針對6.0已經(jīng)做適配症虑,沒什么大問題谍憔,無論運(yùn)行在舊版本還是6.0之后的手機(jī)上都o(jì)k习贫,這也是Google推薦的適配方案苫昌。但是如果targetSdkVersion < 23 ,在6.0之后的手機(jī)上就會遇到一些問題蜡歹,因?yàn)樵谶@種情況下默認(rèn)權(quán)限是全部授予的月而,但是可能會被用戶手動取消父款,而Context的checkSelfPermission權(quán)限檢查接口也會失效憨攒,因?yàn)檫@個API接口6.0之后用的是runtime-permission的模型肝集,而targetSdkVersion < 23 時候杏瞻,app只有intalled的權(quán)限,其granted值一直是true浮创,也可以看做是全部是授權(quán)了的斩披,就算在設(shè)置里面取消授權(quán)也不會影響installed權(quán)限的granted,而Context的checkSelfPermission的接口卻是用granted這個值作為授權(quán)與否的參考仍劈,所以如果用這個接口耳奕,那得到的一定是授權(quán)了屋群,是不準(zhǔn)確的芍躏,如下:targetSdkVersion < 23的時候对竣,package信息中的權(quán)限包含app申請的全部權(quán)限,

<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>

這種情況下吕晌,該做法就會引發(fā)問題睛驳,先從源碼看一下為什么targetSdkVersion < 23 Context 的 checkSelfPermission方法失效乏沸,之后再看下在targetSdkVersion < 23 的時候蹬跃,如何判斷6.0的手機(jī)是否被授權(quán)蝶缀。

為什么targetSdkVersion < 23 Context 的 checkSelfPermission失效

跟蹤一下源碼發(fā)現(xiàn)Context 的 checkSelfPermission最終會調(diào)用ContextImp的checkPermission扼劈,最終調(diào)用

@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,經(jīng)過預(yù)處理跟中轉(zhuǎn)最后會調(diào)用PackageManagerService的checkUidPermission

@Override
public int checkUidPermission(String permName, int uid) {
    final int userId = UserHandle.getUserId(uid);
    synchronized (mPackages) {
    <!--查詢權(quán)限-->
        Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
        if (obj != null) {
            final SettingBase ps = (SettingBase) obj;
            final PermissionsState permissionsState = ps.getPermissionsState();
            <!--檢驗(yàn)授權(quán)-->
            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全局變量中獲取權(quán)限先煎,然后進(jìn)一步驗(yàn)證權(quán)限是否被授予

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);
}

這里的檢查點(diǎn)只有兩點(diǎn)薯蝎,第一個是是否有這個權(quán)限,第二是是否是Granted缩筛,對于targetSdkVersion<23的所有的權(quán)限都在packages.xml中瞎抛,grante一直是true桐臊,無法被跟新断凶,為什么無法被更新呢飘弧?看一下6.0之后的授權(quán)與取消授權(quán)的函數(shù),首先看一個變量mAppSupportsRuntimePermissions

    mAppSupportsRuntimePermissions = packageInfo.applicationInfo
            .targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
    mAppOps = context.getSystemService(AppOpsManager.class);

mAppSupportsRuntimePermissions定義在AppPermissionGroup中,6.0之后權(quán)限都是分組的赶撰,對于targetSdkVersion<23的APP來說豪娜,很明顯是不支持動態(tài)權(quán)限管理的瘤载,那么授權(quán)跟取消授權(quán)函數(shù)就很不一樣如下: 授權(quán)函數(shù)

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;
        }

        <!--關(guān)鍵點(diǎn)1 如果支持鸣奔,也即是targetSdkVersion>23那走6.0動態(tài)權(quán)限管理那一套-->
        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);
                <!--關(guān)鍵點(diǎn)2更新其runtime-permission.xml 中g(shù)ranted值-->
                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);
                    <!--關(guān)鍵點(diǎn)3 設(shè)置為AppOpsManager.MODE_ALLOWED-->
                    mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED);
                    killUid = uid;
                }
            }
                <!--關(guān)鍵點(diǎn)4 更新其PermissionFlags-->
            if (mask != 0) {
                mPackageManager.updatePermissionFlags(permission.getName(),
                        mPackageInfo.packageName, mask, 0, mUserHandle);
            }
        }
    }
   return true;
}

可以看出6.0之后的手機(jī)断楷,針對targetSdkVersion是否高于23做了不同處理冬筒,如果targetSdkVersion>=23支持動態(tài)權(quán)限管理舞痰,那就更新動態(tài)權(quán)限匀奏,并將其持久化到runtime-permission.xml中娃善,并更新其granted值聚磺,如果targetSdkVersion<23 ,也即是不知道6.0的動態(tài)管理瘫寝,那就只更新AppOps,這是4.3引入的老的動態(tài)權(quán)限管理模型首启,不過這里主要是將權(quán)限持久化到appops.xml中毅桃,不過對于其granted的值是沒有做任何更新的钥飞,僅僅是更新了packages.xml中的flag读宙,這個flag可以配合appops.xml標(biāo)識是否被授權(quán)(對于targetSdkVersion<23的適用),以上就是為什么context checkSelfPermission會失效的原因膀估,涉及代碼很多耻讽,不一一列舉针肥,對于取消授權(quán)revokeRuntimePermissions函數(shù),模型一樣具帮,不在贅述蜂厅,那下面看第二個問題掘猿,如何檢查targetSdkVersion<23 app 在6.0以上手機(jī)的權(quán)限呢? Google給了一個兼容類PermissionChecker唇跨,這個類可以間接使用AppOpsService那一套邏輯稠通,獲取到權(quán)限是否被授予衬衬。

targetSdkVersion < 23 的時候,如何判斷6.0的手機(jī)是否被授權(quán)

targetSdkVersion < 23的時候改橘,6.0權(quán)限檢查API失效了滋尉,不過通過上面的分析指導(dǎo),在設(shè)置中權(quán)限的操作仍然會被存儲內(nèi)存及持久化到appops.xml文件中兼砖,這里就是走的AppOpsService那一套,AppOpsService可以看做6.0為了兼容老APP而保留的一個附加的權(quán)限管理模型既棺,在6.0之后的系統(tǒng)中讽挟,可以看做runtime權(quán)限管理的補(bǔ)充,其實(shí)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);
        <!--看看這個權(quán)限是不是能夠操作,動態(tài)授權(quán)與取消授權(quán)  如果不能佩番,說明權(quán)限一直有-->
        if (op == null) {
            return PERMISSION_GRANTED;
        }
       <!--如果能夠取消授權(quán)众旗,就看現(xiàn)在是不是處于權(quán)限被允許的狀態(tài),如果不是趟畏,那就是用戶主動關(guān)閉了權(quán)限-->
        if (AppOpsManagerCompat.noteProxyOp(context, op, packageName)
                != AppOpsManagerCompat.MODE_ALLOWED) {
            return PERMISSION_DENIED_APP_OP;
        }
      return PERMISSION_GRANTED;
    }

對于6.0之后的手機(jī)AppOpsManagerCompat.noteProxyOp會調(diào)用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之前對應(yīng)的API,下面的是6.0及其之后對應(yīng)的接口赋秀,AppOpsManagerCompat23.noteProxyOp會進(jìn)一步調(diào)用AppOpsManager的noteProxyOp向AppOpsService發(fā)送請求

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

最后看一下AppOpsService如何檢查權(quán)限

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可以看做每個應(yīng)用對應(yīng)的權(quán)限模型利朵,這里的數(shù)據(jù)是有一部分是從appops.xml恢復(fù)回來,也有部分是在更新權(quán)限時候加進(jìn)去的猎莲,這部分變化最終都要持久化到appops.xml中去绍弟,不過持久化比較滯后,一般要等到手機(jī)更新權(quán)限后30分鐘才會持久化到appops.xml中著洼,這里的數(shù)據(jù)一般是在啟動的時候被恢復(fù)重建樟遣,在啟動ActivityManagerService服務(wù)的時候,會在其構(gòu)造函數(shù)總啟動AppOpsService服務(wù):

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

在AppOpsService的構(gòu)造函數(shù)中會將持久化到appops.xml中的權(quán)限信息恢復(fù)出來身笤,并存到內(nèi)存中去豹悬,

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

readState就是將持久化的UidState數(shù)據(jù)給重新讀取出來,如下mFile其實(shí)就是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;
            ...}

讀取之后展鸡,當(dāng)用戶操作權(quán)限的時候屿衅,也會隨機(jī)的更新這里的標(biāo)記,只看下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;
            }
            <!--關(guān)鍵點(diǎn)1 如果支持涤久,也即是targetSdkVersion>23那走6.0動態(tài)權(quán)限管理那一套-->
            if (mAppSupportsRuntimePermissions) {
                ...
            } else {
                if (!permission.isGranted()) {
                    continue;
                }
                int killUid = -1;
                int mask = 0;
                if (permission.hasAppOp()) {
                    if (!permission.isAppOpAllowed()) {
                        permission.setAppOpAllowed(true);
                        <!--關(guān)鍵點(diǎn)3 設(shè)置為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;
    }

拿授權(quán)的場景來說涡尘,其實(shí)關(guān)鍵就是 mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_ALLOWED)函數(shù),這個函數(shù)會更新AppOpsService中對于權(quán)限的標(biāo)記响迂,并將權(quán)限是否授予的信息持久化到appops.xml及packages.xml考抄,不同版本可能有差別,有可能需要appops.xml跟packages.xml配合才能確定是否授予權(quán)限蔗彤,具體沒深究川梅,有興趣可以自行分析。

@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);
       <!--更新操作權(quán)限-->
        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();
        }
    }
  ...
}

這里有一點(diǎn)注意:scheduleWriteLocked并不是立即執(zhí)行寫操作然遏,而是比更新內(nèi)存滯后贫途,一般滯后30分鐘

static final long WRITE_DELAY = DEBUG ? 1000 : 30*60*1000;

30分鐘才會去更新 ,不過內(nèi)存中都是最新的 待侵,如果直接刪除appops.xml丢早,然后意外重啟,比如adb reboot bootloader秧倾,那么你的所有AppOpsService權(quán)限標(biāo)記將會被清空怨酝,經(jīng)過驗(yàn)證,是符合預(yù)期的那先,也就說农猬,targetSdkVersion<23的情況下,Android6.0以上的手機(jī)售淡,它的權(quán)限操作是持久化在appops.xml中的斤葱,一般關(guān)機(jī)的時候,會持久化一次勋又,如果還沒來得及持久化苦掘,異常關(guān)機(jī)换帜,就會丟失楔壤,這點(diǎn)同runtime-permission類似,異常關(guān)機(jī)也會丟失惯驼,不信可以試驗(yàn)一下 蹲嚣。

在targetSdkVersion>=23的時候,對于 SDK>=23的機(jī)器如何檢測權(quán)限

targetSdkVersion>=23系統(tǒng)已經(jīng)提供了比較合理的檢測手段祟牲,PermisionChecker的checkPermission就可以隙畜,不過,這里需要注意的是说贝,AppOpsService對于targetSdkVersion>=23的時候就不能用了议惰,這里可能是Android的一個bug,當(dāng)targetSdkVersion>=23而SDK_Version>=23的乡恕,對于AppOpsService言询,權(quán)限的授予跟撤銷不是配對的俯萎,如下,先簡單看下授權(quán):

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

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

            if (mAppSupportsRuntimePermissions) {
                        <!--關(guān)鍵點(diǎn)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;
                <!--關(guān)鍵點(diǎn)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的系統(tǒng)夫啊,無論targetSdkVersion是否>=23,在授權(quán)的時候辆憔,都會更新appops.xml撇眯,那取消授權(quán)呢?

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);
                }
                <!--關(guān)鍵點(diǎn)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()) {
                   <!--關(guān)鍵點(diǎn)2 這里使用mAppOps.setUidMode更新appops.xml文件->
                        mAppOps.setUidMode(permission.getAppOp(), uid, AppOpsManager.MODE_IGNORED);
                        killUid = uid;
                    }
                  ...
            }
        }

        return true;
    }

看關(guān)鍵點(diǎn)1 虱咧,如果targetSdkVersion>=23在取消授權(quán)的時候熊榛,是不會更新appops.xml的,只有在targetSdkVersion<23的時候腕巡,才會向關(guān)鍵點(diǎn)2来候,撤銷授權(quán)。也就是說對于targetSdkVersion>=23的時候逸雹,不要用AppOpsManager了营搅。

對于6.0以下的手機(jī)權(quán)限如何檢測

對于Android6.0以下的手機(jī),不需要關(guān)心targetVersion梆砸。先說個自己驗(yàn)證的結(jié)果:基本沒法檢測转质,同時也不需要檢測,就算檢測出來也沒有多大意義帖世,因?yàn)樾菪罚|發(fā)時機(jī)是在真正的調(diào)用服務(wù)時候。對于4.3到6.0之前的國產(chǎn)ROM日矫,雖然采用AppopsManagerService赂弓,但是并未按照Google的模型對所有權(quán)限進(jìn)行適配,在這個模型下哪轿,也就適配了兩個權(quán)限盈魁,

  • 通知權(quán)限 public static final int OP_POST_NOTIFICATION = 11;
  • 懸浮窗權(quán)限 public static final int OP_SYSTEM_ALERT_WINDOW = 24;

Google發(fā)行版的APPOpsService,基本是把整個鑒權(quán)邏輯給屏蔽了窃诉,通過CM的源碼杨耙,課對這部分代碼窺探一斑,如果整個權(quán)限都采用4.3權(quán)限管理模型飘痛,在拒絕一項(xiàng)權(quán)限的時候珊膜,這個操作會被持久化到appops.xml中去,但是具體看下去宣脉,其實(shí)并不是如此车柠,這種機(jī)制只對以上兩個權(quán)限生效:

    <pkg n="com.xxx">
    <uid n="10988">
    <!--關(guān)鍵點(diǎn)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" />
     <!--關(guān)鍵點(diǎn)2-->
    <op n="11" m="1" t="1510889291834" r="1517279030708" />
    <op n="14" t="1517293452801" />
    <!--關(guān)鍵點(diǎn)3-->
    <op n="24" m="1" />
    <op n="40" t="1513599239364" d="600011" />

國產(chǎn)rom中,假如你拒絕授權(quán)位置權(quán)限,按照AppOpsService模型竹祷,該操作應(yīng)該被持久化到appops.xml中去介蛉,但是,結(jié)果并非如此溶褪,也就是說币旧,對于其他權(quán)限,國產(chǎn)ROM應(yīng)該是自己糊弄了一套持久管理猿妈,持久化Android系統(tǒng)API無法訪問的地方吹菱,僅僅為自身ROM可見。appops.xml真正被系統(tǒng)使用時從Android6.0開始彭则,其實(shí)Android6.0是有兩套權(quán)限管理的鳍刷,這其實(shí)很混亂,不知道Google怎么想的俯抖,不過6.0似乎也有漏洞:權(quán)限的授予跟回收權(quán)限好像并不配對输瓜。

那么這就帶來了一個問題,在Android4.3到Android6.0之間的版本芬萍,并沒有同一個API來檢測是否獲取了某種權(quán)限庇茫,因?yàn)槟銊討B(tài)更新的權(quán)限并未持久化到appops.xml中去馋袜。對于Android6.0之前的ROM匆赃,雖然不能檢測师郑,但完全可以直接用服務(wù),不會崩潰漫蛔,因?yàn)槿绻嫘枰b權(quán)嗜愈,它的鑒權(quán)時機(jī)其實(shí)是在服務(wù)使用的時候。AppopsManager在6.0之前莽龟,只能用來檢測通知蠕嫁,可能還有懸浮窗。

檢查權(quán)限的解決方案(除去通知權(quán)限)

全部采用PermissionChecker的checkSelfPermission:(不要提高compileSdkVersion)

public boolean selfPermissionGranted(Context context, String permission) {
   return  PermissionChecker.checkSelfPermission(context, permission) == PermissionChecker.PERMISSION_GRANTED;
}   

總結(jié)

Android6.0系統(tǒng)其實(shí)支持兩種動態(tài)管理毯盈,runtime-permission及被閹割的AppOpsService剃毒,當(dāng)targetSdkVersion>23的時候,采用rumtime-permission奶镶,當(dāng) targetSdkVersion<23的時候迟赃,兩者兼有,其實(shí)targetSdkVersion<23的時候厂镇,仍然可以動態(tài)申請6.0的權(quán)限,前提是你要采用23之后的compileSdkVersion左刽,只有這樣才能用相應(yīng)的API捺信,不過還是推薦升級targetSdkVersion,這才是正道。對于Android6.0以下的手機(jī)迄靠,除了通知(可能還有懸浮窗)秒咨,其他權(quán)限基本都沒有系統(tǒng)的檢測手段,無論Context的checkPermission還是AppopsManager的checkOp掌挚,基本都是對Android6.0之后才有效雨席。

作者:看書的小蝸牛
原文鏈接:Android權(quán)限檢查API checkSelfPermission問題
僅供參考,歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吠式,一起剝皮案震驚了整個濱河市陡厘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌特占,老刑警劉巖糙置,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異是目,居然都是意外死亡谤饭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進(jìn)店門懊纳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揉抵,“玉大人,你說我怎么就攤上這事嗤疯」σǎ” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵身弊,是天一觀的道長辟汰。 經(jīng)常有香客問我,道長阱佛,這世上最難降的妖魔是什么帖汞? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮凑术,結(jié)果婚禮上翩蘸,老公的妹妹穿的比我還像新娘。我一直安慰自己淮逊,他們只是感情好催首,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著泄鹏,像睡著了一般郎任。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上备籽,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天舶治,我揣著相機(jī)與錄音,去河邊找鬼。 笑死霉猛,一個胖子當(dāng)著我的面吹牛尺锚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播惜浅,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼瘫辩,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了坛悉?” 一聲冷哼從身側(cè)響起伐厌,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎吹散,沒想到半個月后弧械,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡空民,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年刃唐,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片界轩。...
    茶點(diǎn)故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡画饥,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出浊猾,到底是詐尸還是另有隱情抖甘,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布葫慎,位于F島的核電站衔彻,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏偷办。R本人自食惡果不足惜艰额,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望椒涯。 院中可真熱鬧柄沮,春花似錦、人聲如沸废岂。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽湖苞。三九已至拯欧,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間袒啼,已是汗流浹背哈扮。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工纬纪, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蚓再,地道東北人滑肉。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像摘仅,于是被迫代替她去往敵國和親靶庙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容