实战|android后台启动activity实践之路续(代码片段)

普通网友 普通网友     2022-12-03     754

关键词:

概述

家人们!后台启动 Activity 的 Demo 工程请私聊我, 如有问题欢迎提出,觉得不错的话就点个赞吧。

前段时间调研 Android 后台启动 Activity 的方案,参考 实战|Android后台启动Activity实践之路 一文,当时的结论如下:

原生Android ROM

Android 原生 ROM 都能正常地从后台启动 Activity 界面,无论是 Android 9(直接启动) 还是 10 版本(借助全屏通知)。

定制化ROM

Android P版本的机型:

  • 通过 moveTaskToFront 方法将应用切换到前台,如果切换失败的话可以多尝试几次调用 moveTaskToFront 方法;
  • 小米机型可以通过Hook相关参数来后台启动Activity;

Android Q版本的机型:

  • 通过系统全屏通知的方式调起后台 Activity;
  • 在一些另作了限制的 ROM 上可能调起失败;

后来又想到如果能够拿到这些机型 ROM 的源码,那么通过阅读 startActivity 以及后台启动权限设置页面的源码,那么就有可能找到破解的方法。至于怎么获取 ROM 的源码,我这里有两种方式:

  • 如果手里有现成的机型,则可以直接将 /system/framework/ 中的内容通过 adb pull 命令拉下来,然后通过一些反编译工具可以查阅相关的源码;
  • 去相关厂商的官网下载对应机型的 ROM 包,通过工具将其解压转换,最终也可以拿到源码。

这篇文章主要是延续上篇文章的内容,介绍一下怎么拿到 ROM 包源码,并以小米某机型为例,找出它们针对后台启动权限所做的定制化。

获取ROM源码

adb pull

我一开始也没想到原来不需要 root 权限即可从手机里 pull /system/framework/ 里的内容,方式也很简单,手机连接电脑后运行命令即可在当前路径下拿到 framework 文件夹内容:

$ 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.isProcessRecordVisible(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;

    // ...

    public Intent addMiuiFlags(int flags) 
        this.mMiuiFlags |= flags;
        return this;
    

    public Intent setMiuiFlags(int flags) 
        this.mMiuiFlags = flags;
        return this;
    

    public int getMiuiFlags() 
        return this.mMiuiFlags;
    

果然,Intent 中被增加了一个标志位,那么我们估计就知道怎么去解决这个问题了,那就是在小米平台上通过反射将我们的 Intent 参数中的 mMiuiFlags 设置成 0x2 即可绕过这两个权限的认证!

另外这里要注意一个问题:在 Android 9 以上 Intent 类中的属性是不能被反射的,因此我们需要想办法解决这个问题,网上已经有了许多现成的方式,这里我就不做展开了,想了解具体原理的直接 Google 即可。我借用了 Github 上的一个开源库——FreeReflection,通过它可以方便地防止反射 Intent 抛出异常崩溃。

经过实际测试,当我将 Intent 中的这个属性修改成 0x2 以后,可以直接从后台或在锁屏时启动我们应用的 Activity。

总结

其实整体来说这套解决方案还是挺简单的,在找到工具反编译 ROM 代码后,熟悉 Activity 启动源码的同学还是能比较轻松地找到其中的突破点的,当然中间可能会走错方向,像我有时候就容易盯着一个跟目标毫无关联的方法看,因为不能确定到底哪里才是真正的关键点。猜测其他版本的小米机器应该都是用的这种方式,毕竟同一个厂商没必要弄多套方案去做这个权限的功能。

参考上述方式,如果对于一些厂商 ROM 的定制化功能有疑问或者开发中有这种奇怪Bug(与厂商定制相关)的,都可以从它们的源码中找到蛛丝马迹,也算是一种解决思路吧,时间足够的话,可以自己直接从源码中寻找答案,不然在网上搜来搜去的,有的能找到答案那是万幸,有的则完全不知所云晕头转向的。

最后

为了让大家更好的去学习和提升自己,在这里我也分享一份由几位大佬一起收录整理的 Flutter进阶资料以及Android学习PDF+架构视频+面试文档+源码笔记 ,并且还有 高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料……

这些都是我闲暇时还会反复翻阅的精品资料。可以有效的帮助大家掌握知识、理解原理。当然你也可以拿去查漏补缺,提升自身的竞争力。
如果你有需要的话,可以前往 GitHub 自行查阅。

超强干货来袭 云风专访:近40年码龄,通宵达旦的技术人生

android实战——activity超详细学习笔记(代码片段)

一、Activity简介Activity是四大组件中最重要的一个,也是平时开发中接触最多的。与Activity启动行为相关的就是它的启动模式,Standard、SingleTop、SingleTask、SingleInstance这4种launchMode相信大家不陌生。OK,熟悉了4种launchMode... 查看详情

framework之activity启动流程(基于android11源码)(代码片段)

...看,你就会对activity的启动流程有深刻的认知。引言Android11上,Activity的启动流程与Android10的实现(可以参考Activity的启动过程详解(基于10.0源码))又不一样了,但是万变不离其中,变的更多是代... 查看详情

activity启动模式之singletop

activity启动模式之singleTop一、简介 二、设置方法在AndroidManifest.xml中将要设置为singleTop启动模式的页面进行配置 <activityandroid:name="activityLaunchSingleTop.ActivityB2"android:launchMode="singleTop"></activity&g 查看详情

android之launchmode(启动模式)(代码片段)

Android之LaunchMode(启动模式)文章目录Android之LaunchMode(启动模式)Activity的4种启动模式1.1standard------标准模式1.2singleTop1.3singleTask1.3.1什么是Activity想要的任务栈1.4singleInstanceLauncherModel的设定方式2.1通过Flag动态设定2.2在AndroidMainfest文件... 查看详情

android6.0源码分析之activity启动过程

Activity最为android开发者最熟悉的组件,由ActivityManagerService服务进行调度管理,而ActivityManagerService的启动过程在activitymanagerservice服务源码分析一文中进行了详细分析,本文基于其对Activity的启动过程进行分析,同... 查看详情

android之service(一)启动,绑定服务

...是独立的进程,对应进程名格式为所在包名加上你指定的android:process字符串。一般定义方式android:process=":service"由于是独立的进程,因此在Activity所在进程被Kill的时候,该服务依然在运行,不受其他进程影响,有利于为多个进程... 查看详情

android总结之activity

参考技术AGUI,界面,与用户交互的可视接口。当然Fragment,View也是,不过我把它看作是控件,Activity是开发中最基本的容器(窗口)。目的是便于管理Activity生命周期。新启动的Activity会压入栈顶,而处于栈顶的Activty处于Active活跃... 查看详情

activiti自己定义流程之spring整合activiti-modeler实例:启动流程

...流程节点的信息。也用到了repositoryService及相关方法。2.后台业务代码, (1)自己定义的申请单实体类(为的目的仅 查看详情

android面试之必问android基础(代码片段)

在上一篇文章Android面试之必问Java基础一文中,我们介绍了Java面试的一些常见的基础面试题,下面我们来介绍Android开发的一些必问知识点。1,Activity1.1生命周期正常情况系,Activity会经历如下几个阶段:onCreate:表示Activity正在... 查看详情

android四大组件之service的介绍

Service的基本认识Service是一个可以在后台执行长时间运行操作而不使用用户界面的应用组件.Service可由其他应用组件启动,而且即使用户切换到其他应用,Service仍将在后台继续运行.Service主要用于在后台处理一些耗时的逻辑,或者去... 查看详情

android之activity全面解析,有些知识点容易忘记

...何连续退出多个Activity?如何把Acitivty设置成Dialog样式,android:theme="@android:style/Theme.Dialog"关于横竖屏切换的生命周期,对应不同的手机,由于厂商定制的原因,会有不同的效果,如设置了configChanges="orientation”在有些手机会执行... 查看详情

framework之activity启动流程(基于android11源码)(代码片段)

...看,你就会对activity的启动流程有深刻的认知。引言Android11上,Activity的启动流程与Android10的实现(可以参考Activity的启动过程详解(基于10.0源码))又不一样了,但是万变不离其中,变的更多是代... 查看详情

Android后台处理、处理结果和Activity生命周期

】Android后台处理、处理结果和Activity生命周期【英文标题】:Androidbackgroundprocessing,handleresultandActivitylifecycle【发布时间】:2016-12-1409:56:28【问题描述】:我遇到了一个Android后台处理的普遍问题,如何正确处理?想象一下这个场... 查看详情

最新android大厂高频面试题解析大全

第一章Android相关1.Android进程间通信(IPC)机制Binder简要介绍和学习计划2.Activity的启动方式和flag详解3.Android源码分析-资源加载机制4.Android中Thread、Handler、Looper、MessageQueue的原理分析5.Android源码解析之setContentView6.AndroidAsyn... 查看详情

android小技巧之如何构建完全透明的activity

...我们只需要一个样式就能完美解决:在theme.xml中添加:在AndroidManifest.xml中给你的Activity设置一下theme:启动一下这个Activity,是不是什么都看不见,就只能看到桌面。感谢各位读者,喜欢的话点个赞吧,有哪里不懂的也可以在下... 查看详情

android性能优化之启动加速35%

...表现紧密相关,从本篇文章开始,我将开启一个Android应用性能优化的专题,从理论到实战,从入门到深挖,手把手将性能优化实践到项目中,欢迎持续关注!那么第一篇文章我就从应用的启动优化开始... 查看详情

android性能优化之启动加速35%

...表现紧密相关,从本篇文章开始,我将开启一个Android应用性能优化的专题,从理论到实战,从入门到深挖,手把手将性能优化实践到项目中,欢迎持续关注!那么第一篇文章我就从应用的启动优化开始... 查看详情

androidstudio菜鸟实战项目之spnner实现

1.简单实现效果图:activity_main.xml文件:<TextViewandroid:id="@+id/text"android:layout_width="wrap_content"android:layout_height="wrap_content"/><Spinnerandroid:id="@+id/spinner"android:layout_width="wr 查看详情