2024年安卓最新Android权限检查API checkSelfPermission失效问题,字节跳动面试自我介绍

重要知识点

下面是有几位Android行业大佬对应上方技术点整理的一些进阶资料。

高级进阶篇——高级UI,自定义View(部分展示)

UI这块知识是现今使用者最多的。当年火爆一时的Android入门培训,学会这小块知识就能随便找到不错的工作了。不过很显然现在远远不够了,拒绝无休止的CV,亲自去项目实战,读源码,研究原理吧!

  • 面试题部分合集

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

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

if (mAppSupportsRuntimePermissions) {

} else {
if (!permission.isGranted()) {
continue;
}
int killUid = -1;
int mask = 0;
if (permission.hasAppOp()) {
if (!permission.isAppOpAllowed()) {
permission.setAppOpAllowed(true);

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

scheduleWriteLocked();
}
}

}

这里有一点注意:scheduleWriteLocked并不是立即执行写操作,而是比更新内存滞后,一般滞后30分钟

static final long WRITE_DELAY = DEBUG ? 1000 : 30601000;

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

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;

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

国产rom中,假如你拒绝授权位置权限,按照AppOpsService模型,该操作应该被持久化到appops.xml中去,但是,结果并非如此,也就是说,对于其他权限,国产ROM应该是自己糊弄了一套持久管理,持久化Android系统API无法访问的地方,仅仅为自身ROM可见。appops.xml真正被系统使用时从Android6.0开始,其实Android6.0是有两套权限管理的,这其实很混乱,不知道Google怎么想的,不过6.0似乎也有漏洞:权限的授予跟回收权限好像并不配对

最后

感觉现在好多人都在说什么安卓快凉了,工作越来越难找了。又是说什么程序员中年危机啥的,为啥我这年近30的老农根本没有这种感觉,反倒觉得那些贩卖焦虑的都是瞎j8扯谈。当然,职业危机意识确实是要有的,但根本没到那种草木皆兵的地步好吗?

Android凉了都是弱者的借口和说辞。虽然 Android 没有前几年火热了,已经过去了会四大组件就能找到高薪职位的时代了。这只能说明 Android 中级以下的岗位饱和了,现在高级工程师还是比较缺少的,很多高级职位给的薪资真的特别高(钱多也不一定能找到合适的),所以努力让自己成为高级工程师才是最重要的。

所以,最后这里放上我耗时两个月,将自己8年Android开发的知识笔记整理成的Android开发者必知必会系统学习资料笔记,上述知识点在笔记中都有详细的解读,里面还包含了腾讯、字节跳动、阿里、百度2019-2021面试真题解析,并且把每个技术点整理成了视频和PDF(知识脉络 + 诸多细节)。

以上全套学习笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

笔记面试宝典,吃透一半保你可以吊打面试官,只有自己真正强大了,有核心竞争力,你才有拒绝offer的权力,所以,奋斗吧!骚年们!千里之行,始于足下。种下一颗树最好的时间是十年前,其次,就是现在。

最后,赠与大家一句诗,共勉!

不驰于空想,不骛于虚声。不忘初心,方得始终。

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值