我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android 10 startActivity 源码分析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android 10 startActivity 源码分析

源码基于 Android 10

Android 10.0 startActivity 流程图

此图着重提炼了生命周期的部分,Android 10 中 新增了 ActivityTaskManager ,专门用于管理 Activity,接替了 ActivityManager 的一部分工作

理解 Instrumentation

Activity 首先会通过 Instrumentation 去调用,Instrumentation 中包含 callActivityOnCreate、callActivityOnPause、callApplicationOnCreate 等调用,具有强大的跟踪 Activity 及 Application 生命周期的功能,所以也被作为应用测试框架中的基类使用。一个进程有一个 ActivityThread 对象,ActivityThread 对象持有一个 Instrumentation 对象,每个 Activity 都持有 Instrumentation

IPC 发生在何处?

在 Instrumentation 中通过 IActivityTaskManager.aidl 接口由 App 进程进入到 system_server 进程;在 ClientTransaction 中通过 IApplicationThread.aidl 接口由 system_server 回到 App 进程

Activity 栈是如何管理的?

ActivityRecord:Activity 以 ActivityRecord 形式记录,一个 ActivityRecord 对应一个 Activity 实例
TaskRecord:这个才是一个真正的 Activity 栈,内部持有 ArrayList,记录当前栈中所有的 Activity
ActivityStack:负责管理 Activity 栈,存放了多个 TaskRecord

何处读取 manifest 中 Activity 节点的启动模式等配置

读取 manifest 配置图

如图通过 PackageManagerService#resolveIntentInternal 方法返回 ResolveInfo ,ResolveInfo 包括 ActivityInfo、ServiceInfo、ProviderInfo 等信息,此调用已经处于 system_server 进程了,所以并不是 IPC,PackageManagerService 主要负责解析 AndroidManifest.xml、扫描本地 apk 目录、管理 App 安装删除等

何处检测 Activity 是否在 manifest 注册?

关键代码在 ActivityStarter#startActivity 方法中:

       if (err == ActivityManager.START_SUCCESS && aInfo == null) {
           // We couldn't find the specific class specified in the Intent.
           // Also the end of the line.
           err = ActivityManager.START_CLASS_NOT_FOUND;
       }

aInfo 就是 ActivityInfo ,而 ActivityInfo 为空导致后续报错返回。那 aInfo 是从哪来的呢?当然就是通过 PackageManagerService#resolveIntentInternal 方法解析出来的。然后在 Instrumentation#checkStartActivityResult 方法中检测到 ActivityManager.START_CLASS_NOT_FOUND 返回值后,就抛出 "Unable to find explicit activity class {xxx}; have you declared this activity in your AndroidManifest.xml? "异常了

为什么单独配置 taskAffinity 不会生效

关键代码在 ActivityStarter#startActivityUnchecked 方法中:

  if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
         newTask = true;
         result = setTaskFromReuseOrCreateNewTask(taskToAffiliate);
  } else if (mSourceRecord != null) {
         result = setTaskFromSourceRecord();
  } else if (mInTask != null) {
         result = setTaskFromInTask();
  } else {
         result = setTaskToCurrentTopOrCreateNewTask();
  }

只有 mLaunchFlags 标记为 FLAG_ACTIVITY_NEW_TASK 才会去创建一个新的 Activity 栈即 TaskRecord,而系统并没有对单独配置一个 taskAffinity 的情况做处理。那在 AndroidManifest.xml 中配置的 launchMode 是在何处处理,并反应到 mLaunchFlags 中的呢?
ActivityStarter#startActivityUnchecked 方法中调用了 ActivityStarter#computeLaunchingTaskFlags 方法,该方法中配置 mLaunchFlags 的代码如下:

   if ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 && mInTask == null) {
         Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
                        "Intent.FLAG_ACTIVITY_NEW_TASK for: " + mIntent);
         mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
    }else if (mSourceRecord.launchMode == LAUNCH_SINGLE_INSTANCE) {
         mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
    } else if (isLaunchModeOneOf(LAUNCH_SINGLE_INSTANCE, LAUNCH_SINGLE_TASK)) {
         mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
    }

可以看到 LAUNCH_SINGLE_INSTANCE 模式会在一个新的栈中启动等我们早已熟知的规则

理解 ClientTransactionItem 与 ActivityLifecycleItem

ClientLifecycleManager 可以将一或多个生命周期事件组合到一起作为一个事务即 ClientTransaction 来执行,startActivity 时新 Activity 的 onCreate、onStart、onResume 事件就是存放在一个事务中被执行的

startActivity 时新 Activity 的 onCreate 事件存放在 ClientTransaction 的 List 类型的成员变量中,载体为 LaunchActivityItem。 LaunchActivityItem 也是 ClientTransactionItem 的子类,即启动 Activity 事件。另外还有 NewIntentItem(触发 onNewIntent 回调)、ActivityResultItem(触发 onActivityResult)、ConfigurationChangeItem(触发 onConfigurationChanged 回调)等事件

startActivity 时新 Activity 的 onResume 事件存放在 ClientTransaction 的 ActivityLifecycleItem 类型的变量成员变量中,这个变量也表示最终的生命周期状态,载体为 ResumeActivityItem。ActivityLifecycleItem 也是 ClientTransactionItem 的一个子类

为什么不能在 Activity 的 onPause 方法中做耗时操作?

ClientTransaction 为 Parcelable 数据,会通过 IApplicationThread.aidl 的 scheduleTransaction 方法发送到 App 端,然后加入到主线程 ActivityThread.H 消息队列中等待执行。startActivity 时会依次发送前一个 Activity 的 pause 和新 Activity 的 resume 事务,然后这两个事务会通过 ActivityThread.H 依次执行,所以不能在 Activity 的 onPause 方法中做耗时操作,因为只有 onPause 方法执行完后,下一个 Activity 的生命周期事件才能被执行,否则会阻塞新界面显示

activity 实例在何处创建?

在 ActivityThread#performLaunchActivity 方法中,会通过 Instrumentation#newActivity 方法创建 Activity 的实例对象,随后就调用了 Instrumentation#callActivityOnCreate 方法回调 Activity 的 onCreate 方法

Launcher 中点击图标启动 App

Launcher 中点击图标启动同样是调用 startActivity 方法,但需要创建进程,关键代码在 ActivityStackSupervisor#startSpecificActivityLocked 方法中:

   final WindowProcessController wpc =
           mService.getProcessController(r.processName, r.info.applicationInfo.uid);
   if (wpc != null && wpc.hasThread()) {
        //判断进程存在,继续启动
        realStartActivityLocked(r, wpc, andResume, checkConfig);
        return;
   }
   //进程不存在,创建进程
   final Message msg = PooledLambda.obtainMessage(
           ActivityManagerInternal::startProcess, mService.mAmInternal, r.processName,
           r.info.applicationInfo, knownToBeDead, "activity", r.intent.getComponent());
   mService.mH.sendMessage(msg);

startProcess 流程图

与 zygote 进程通信采取了 Socket 方式,为什么不使用更安全、数据只需拷贝一次的 binder 呢?zygote 作为 Android 的受精卵进程,通过 fork 方法创建进程,而 fork 是不允许多线程的,否则会因为 Copy-on-Write 机制导致死锁,而 binder 正是基于多线程运行的

在 ProcessList#startProcessLocked 方法中,传入了值为 “android.app.ActivityThread” 的 entryPoint 参数,后续会透传给 zygote 进程,zygote fork 新进程成功后,新进程的 ActivityThread#main 函数会被调用,即 App 真正的启动入口

ActivityThread.main 流程图

在 ActivityManagerService#attachApplicationLocked 方法中执行了两个关键逻辑,一是通过 IApplicationThread 回到 App 进程中创建 Application 实例并回调 onCreate 方法;二是调用 ActivityTaskManagerService#attachApplication 方法,进一步去启动首页 Activity 。
启动 Activity 和普通的 startActivity 一样,都会调用到 ActivityStackSupervisor#realStartActivityLocked 方法

理解 ActivityThread 与 ApplicationThread

ActivityThread.main() 方法是程序的启动入口,初始化了主线程 Looper,在 ActivityThread.H 中处理消息。ApplicationThread 是 ActivityThread 的内部类,实现了 IApplicationThread.aidl 接口以接受 AMS 等系统服务的回调,而大多数都是四大组件相关的任务,所以发送 Handler 消息到 ActivityThread.H ,即从 binder 线程切换到主线程中处理

如何启动一个未在 manifest 中注册的 Activity ?

加载 manifest 信息及检测注册在 system_server 进程,即无法干扰检测逻辑。常见做法是在 manifest 中注册一个占位 Activity,在进入 system_server 进程之前把未注册的 Activity 修改为占位 Activity,然后等从 system_server 返回到 App 进程后再修改回未注册的 Activity,然后去创建、启动,也就是说需 hook 两处:

hook ActivityTaskManager:

final Field singletonField = ActivityTaskManager.class
             .getDeclaredField("IActivityTaskManagerSingleton");
singletonField.setAccessible(true);
Singleton singleton = (Singleton) singletonField.get(null);
final Object activityTaskManagerObject = singleton.get();
final Field mInstanceField = Singleton.class.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object value = Proxy.newProxyInstance(ActivityTaskManager.class.getClassLoader()
     , new Class[]{Class.forName("android.app.IActivityTaskManager")}
     , new InvocationHandler() {
           @Override
           public Object invoke(Object proxy,
                         Method method, Object[] args) throws Throwable {
              if ("startActivity".equals(method.getName())) {
                  Intent raw;
                  int index = 0;
                  for (int i = 0; i < args.length; i++) {
                     if (args[i] instanceof Intent) {
                        index = I;
                        break;
                     }
                  }
                  if (!(args[index] instanceof Intent)) throw new AssertionError();
                  raw = (Intent) args[index];
                  if (raw.getComponent().getClassName()
                         .equals("com.yinghao.test.UnRegisterActivity")) {
                      ntent newIntent = new Intent();
                      //将未注册的 UnRegisterActivity 替换为占位 FakeActivity
                      newIntent.setComponent(new ComponentName("com.yinghao.test", 
                                   FakeActivity.class.getName()));
                      //记录 UnRegisterActivity
                      newIntent.putExtra(EXTRA_TARGET_INTENT, raw); 
                      args[index] = newIntent;
                  }
              }
              return method.invoke(activityTaskManagerObject, args);
           }
});
mInstanceField.set(singleton, value);

hook ActivityThread:

ActivityThread activityThread = ActivityThread.currentActivityThread();
Field mH1 = activityThread.getClass().getDeclaredField("mH");
mH1.setAccessible(true);
final Handler mH = (Handler) mH1.get(activityThread);
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mH, new Handler.Callback() {
 @Override
 public boolean handleMessage(Message msg) {
  try {
   if (msg.what == 159) { // ActivityThread.H.EXECUTE_TRANSACTION
    final ClientTransaction transaction = (ClientTransaction) msg.obj;
    Field mActivityCallbacksField = transaction
               .getClass().getDeclaredField("mActivityCallbacks");
    mActivityCallbacksField.setAccessible(true);
    List clientTransactionItems = 
        (List) mActivityCallbacksField.get(transaction);
    if (clientTransactionItems != null) {
     for (ClientTransactionItem c : clientTransactionItems) {
      if (c instanceof LaunchActivityItem) {
       //修正 Activity 启动事件实体 LaunchActivityItem
       LaunchActivityItem item = (LaunchActivityItem) c;
       Field intentField = item.getClass().getDeclaredField("mIntent");
       intentField.setAccessible(true);
       Intent intent = (Intent) intentField.get(item);
       Field mInfoField = item.getClass().getDeclaredField("mInfo");
       mInfoField.setAccessible(true);
       ActivityInfo aInfo = (ActivityInfo) mInfoField.get(item);
       Intent realIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT);
       if (realIntent != null) {
         //将占位 FakeActivity 改回未注册的 UnRegisterActivity
         intent.setComponent(realIntent.getComponent());
         aInfo.packageName = realIntent.getComponent().getPackageName();
         aInfo.name = realIntent.getComponent().getClassName();
       }
       }
     }
    }
   }
  } catch (Exception e) {
  }
  return false; //返回 false 正好可以让 ActivityThread 继续处理
 }
});

实现启动未注册 Activity 的前提必然是已掌握 startActivity 流程,这也是插件化的入门,实际应用需要去兼容各个 Android 版本

最后

带着问题去分析、学习源码,自然的就会聚焦出一条主线

关注公众号,Get 更多知识点
作者:王英豪


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android 10 startActivity 源码分析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android 10 startActivity 源码分析

源码基于 Android 10此图着重提炼了生命周期的部分,Android 10 中 新增了 ActivityTaskManager ,专门用于管理 Activity,接替了 ActivityManager 的一部分工作 理解 Instru
2022-06-06

Android AsyncTask源码分析

Android中只能在主线程中进行UI操作,如果是其它子线程,需要借助异步消息处理机制Handler。除此之外,还有个非常方便的AsyncTask类,这个类内部封装了Handler和线程池。本文先简要介绍AsyncTask的用法,然后分析具
2022-06-06

Android LayoutInflater.inflate源码分析

LayoutInflater.inflate源码详解 LayoutInflater的inflate方法相信大家都不陌生,在Fragment的onCreateView中或者在BaseAdapter的getView方法中我们都会经常用这个方法来
2022-06-06

0xA03 Android 10 源码分析:APK 加载流程之资源加载

引言 这是 Android 10 源码分析系列的第 3 篇 分支:android-10.0.0_r14 全文阅读大概 15 分钟 首发于掘金:https://juejin.im/post/5e6c8c14f265da57… 通过这篇文章你将
2022-06-06

Android AOSP 6.0.1 常规startActivity启动流程分析

在App开发过程中,界面之间的跳转非常频繁,在一个Activity中启动另一个Activity一般都是通过startActivity方法实现的。Activity如何在Framework中运作这是我多年以来的困惑之一。以下代码分析基于Andr
2022-06-06

Android ArrayMap源代码分析

分析源码之前先来介绍一下ArrayMap的存储结构,ArrayMap数据的存储不同于HashMap和SparseArray。Java提供了HashMap,但是HashMap对于手机端而言,对空间的利用太大,所以Android提供
2022-06-06

Android内核wake_up源码分析

今天小编给大家分享一下Android内核wake_up源码分析的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。内核中通常用法:
2023-07-05

Android ViewPager源码详细分析

1.问题 由于Android Framework源码很庞大,所以读源码必须带着问题来读!没有问题,创造问题再来读!否则很容易迷失在无数的方法与属性之中,最后无功而返。 那么,关于ViewPager有什么问题呢? 1). setOffsre
2022-06-06

【Android】CalledFromWrongThreadException 深入源码分析

先上结论 出现此问题的原因是:在非 UI 线程中创建了 Dialog,而在 UI 线程中调用了 show() 方法 问题还原 在使用 dialog 的时候,因为线程问题,在调用 dismiss() 方法的时候,出现如下常见的 crash–O
2022-06-06

源码分析Android rinflate的使用

这篇文章主要将从源码的角度带大家一起分析Android rinflate的使用,文中的示例代码讲解详细,具有一定的学习价值,感兴趣的可以了解一下
2023-05-16

源码分析Android LayoutInflater的使用

简单来说,LayoutInflater的工作就是将使用xml文件编写的布局转换成Android里的View对象,并且这也是Android中将xml布局转换成View的唯一方式。本文将从源码带大家了解一下LayoutInflater的具体使用
2023-05-16

Android Jetpack组件LiveData源码分析

本篇内容主要讲解“Android Jetpack组件LiveData源码分析”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Android Jetpack组件LiveData源码分析”吧!基本使用
2023-07-05

源码分析Android的消息机制

一、引言 ​Android消息机制主要指的是Handler的运行机制,是一块很有意思,也很有研究意义的内容。本文计划在较短的篇幅内,通过一定的源码,分析Android消息机制,并在结尾说点”题外话“,帮助我们理解消息机制在安卓应用中的作用。
2022-06-06

【Android】SharedPreferences源码分析,全网最全!

SharedPreferences源码分析一.SharedPreferences的创建:1.获取SharedPreferences对象1)getSharedPreferencesCacheLocked方法2)checkMode方法3)And
2022-06-06

Android SDK 之TTS源码及流程分析

TTS全称Text  To Speech ,是文本转语音服务,本文将从TTS的简单demo使用,对TTS进行源码分析涉及的Android SDK 核心源码路径如下: android.speech.tts.TextToSpeech andro
2022-06-06

Android context源码详解及深入分析

Android context详解 前言: Context都没弄明白,还怎么做Android开发? Activity mActivity =new Activity()作为Android开发者,不知道你有没有思考过这个问题,Activity
2022-06-06

【Android】事件分发机制源码解析

文章目录1. 分发顺序2.源码分析2.1 Activity中的分发流程dispatchTouchEventonTouchEvent总结2.2 ViewGroup中的分发流程dispatchTouchEventonInterceptTouch
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录