写点什么

实战|Android 后台启动 Activity 实践之路续,2021 年 Android 者未来的出路在哪里

用户头像
Android架构
关注
发布于: 23 小时前

$ adb pull /system/framework


这种方式做过 Android 开发的应该都知道,就不再多说了。不过需要注意的是在一些机器里 pull 下来的 framework 文件夹下的 jar 文件可能都是只有 1Kb 的大小,这种 jar 文件里不含有源代码,在 framework 下还有一些 odex 文件,需要将其转换成 dex 等格式才更好反编译,具体怎么转换的可以网上搜,貌似还有挺多教程的。


解压 ROM


=====


首先去对应厂商的官网下载 ROM 包,以小米为例是在 MIUI 下载 里下载,下载了目标 ROM.zip 后将其解压缩,我下载的是小米 Max3(Android 9) 的 ROM 包,解压后我们需要的有两个文件: system.new.dat.br 和 system.transfer.list。接下来分步骤看看怎么反编译出它的源码:


  1. 下载 ROM 制作工具: 下载地址,下载安装打开后,选择其 实用工具 栏,然后打开 new.dat 编辑 功能,如下图:



  1. 按照下图的两个步骤转换。首先第一步选择 system.new.dat.br 文件转换,得到 .new.dat 后缀的 system.new.dat 文件;然后第二步选择这个 system.new.dat 文件转换,可能提示需要 .transfer.list 文件,直接选择上面的 system.transfer.list 文件即可,转换后会得到一个 img 后缀的文件,将其解压缩。



  1. 打开解压缩后的文件夹,进入 /system/framework/ 目录下,即可看到我们需要的 jar 文件们。


反编译 ROM 源码


========


这一步是要将上面得到的 jar 或者 dex 文件反编译得到源码,网上有很多介绍反编译的文章,也有很多工具比如说 apktool, dex2jar, jd-gui 等,这里介绍一个傻瓜式操作的工具——jadx,如果想要省事的话可以直接使用这个工具,它可以直接打开 jar, dex, apk 等后缀的文件,直接查看反编译后的源码,是不是很方便呢?


另外如果要使用 jd-gui 查看的话,网上有很多教程了,或者直接 --help 查看相关工具的 Usage。


后台启动权限做了什么?


===========


经过上面的步骤我们得到了 ROM 反编译后的源码,这一章开始进入具体的源码分析流程。从之前 实战|Android 后台启动 Activity 实践之路 可以知道,当我们调用 startActivity 后,会来到 AMS 这一端,AMS 进行了一些处理后,会调用到 ActivityStarter.startActivity 方法,对这个流程有疑问的可以看看 Activity 的启动流程,可以参考 Android-Activity 启动流程。查看反编译后的代码,发现里面调用了小米自定义的 Inject 类中的静态方法(在 services.jar 中):


private int startActivity(IApplicationThread paramIApplicationThread, Intent paramIntent1, ...) {


// ...


// 这个 i 在 AOSP 源码中原名叫 boolean abort


i = activityStackSupervisor.checkStartAnyActivityPermission(...) ^ true | this.mService.mIntentFirewall.checkStartActivity(...) ^ true;


paramInt1 = i;


if (i == 0) { // 表示 !abort


paramInt1 = i;


if (!ActivityTaskManagerServiceInjector.isAllowedStartActivity(this.mService, this.mSupervisor, paramIntent1, ...))


paramInt1 = 1;


}


// 根据上面的 bool 值判断是否接着执行 startActivity 流程


// ...


}


其实这样的 XXXInject 类在源码中还有很多,都是用来做一些自定义逻辑的,我们重点看下这个?ActivityTaskManagerServiceInjector.isAllowedStartActivity()?方法的逻辑:


static boolean isAllowedStartActivity(..., Intent paramIntent, ...) {


StringBuilder stringBuilder;


// 1


if (UserHandle.getAppId(paramInt) == 1000 || (paramIntent.getMiuiFlags() & 0x2) != 0 || PendingIntentRecordInjector.containsPendingIntent(paramString) || PendingIntentRecordInjector.containsPendingIntent(paramActivityInfo.applicationInfo.packageName) || paramInt == mLastStartActivityUid || paramActivityTaskManagerService.isUidForeground(paramInt)) {


return true;


}


// 2


if (paramActivityTaskManagerService.mWindowManager.isKeyguardLocked() && paramActivityTaskManagerService.getAppOpsService().noteOperation(10020, paramInt, paramString) != 0) {


stringBuilder = new StringBuilder();


stringBuilder.append("MIUILOG- Permission Denied Activity KeyguardLocked: ");


// ...


Slog.d("ActivityTaskManagerServiceInjector", stringBuilder.toString());


return false;


}


// ...


// 3


if (stringBuilder.getAppOpsService().checkOperation(10021, paramInt, paramString) != 0) {


SparseArray<WindowProcessController> sparseArray = ((ActivityTaskManagerService)stringBuilder).mProcessMap.getPidMap();


for (int i = sparseArray.size() - 1; i >= 0; i--) {


int j = sparseArray.keyAt(i);


WindowProcessController windowProcessController = (WindowProcessController)sparseArray.get(j);


if (windowProcessController != null && windowProcessController.mUid == paramInt && (windowProcessController.hasForegroundActivities() || (ExtraActivityManagerService.isPr


《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》
浏览器打开:qq.cn.hn/FTe 免费领取
复制代码


ocessRecordVisible(j, paramInt) && windowProcessController.hasActivities() && paramInt == activityRecord.launchedFromUid))) {


mLastStartActivityUid = paramActivityInfo.applicationInfo.uid;


return true;


}


}


stringBuilder.getAppOpsService().noteOperation(10021, paramInt, paramString);


stringBuilder = new StringBuilder();


stringBuilder.append("MIUILOG- Permission Denied Activity : ");


// ...


Slog.d("ActivityTaskManagerServiceInjector", stringBuilder.toString());


return false;


}


return true;


}


接下来我们从上面标的数字讲起:


  • 首先看看数字 2 的部分:我们看到了这里有一个 OpCode=10020, 这个 Code 对应的权限也是小米增加的,看下面的日志可以知道这个 Code 就是我们常看到的小米锁屏显示的权限,由此可以知道如果我们调用 startActivity 时手机没有解锁,那么会走到这个流程,判断应用有没有这个 10020 的权限,如果有则接着往下走,如果没有权限则直接返回 false 表示不能启动目标 Activity。

  • 然后看数字 3 的部分:跟上面类似,它处理 OpCode=10021 的权限鉴定,这个值跟我们在前一篇文章里讲到的后台启动权限的 Code 是一样的!也就是说这段代码就是用来判断应用有没有后台启动的权限的。


至于这两个权限相关的日志:Permission Denied Activity KeyguardLocked: ... 和 Permission Denied Activity: ...,我们在遇到这两种场景后,在 logcat 中过滤 MIUILOG Tag 是可以看到这两种日志输出的,有兴趣的同学可以验证一下~


接下来再看数字 1 部分,这里就是我们绕过这两个权限的关键!它在这个方法的开头,如果这个 if 判断为真的话则会直接返回 true,从而跳过后面权限认证的逻辑,我们重点关注 (paramIntent.getMiuiFlags() & 0x2) != 0 这个判断条件:由此可以看出小米在 Intent 类中增加了一个形如 MiuiFlags 的标志位,我们打开 Intent 类看看具体情况,Intent 类在 framework.jar 中:


public class Intent implements Parcelable, Cloneable {


private int mMiuiFlags;


// ...

用户头像

Android架构

关注

还未添加个人签名 2021.10.31 加入

还未添加个人简介

评论

发布
暂无评论
实战|Android后台启动Activity实践之路续,2021年Android者未来的出路在哪里