Activity 的启动流程源码剖析(一)

简介: 简单分析 Activity 的启动流程(一)这篇主要分析 startActivity 这个方法源码的版本Android 27V4 27.1.1我都是粘的里面比较关键的源码,还希望配合源码阅读第一步先找到源码的切入点我们启动 Activit...

简单分析 Activity 的启动流程(一)
这篇主要分析 startActivity 这个方法

源码的版本

Android 27
V4 27.1.1
我都是粘的里面比较关键的源码,还希望配合源码阅读

第一步先找到源码的切入点

我们启动 Activity 一般都是调用 startActivity() 这个方法 Activity、Context、Fragment 中都有我们分别看一下这几种调用的具体源码

一、Activity 的 startActivity()/startActivityForResult()

public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {
            startActivityForResult(intent, -1, options);
        } else {
            startActivityForResult(intent, -1);
        }
    }

startActivity 是直接调用了 startActivityForResult() 如果不需要请求结果的话 requestCode 直接传 -1 就可以了
在 startActivityForResult 中发现了很关键的代码

if (mParent == null) {
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                mStartedActivity = true;
            }
            cancelInputsAndStartExitTransition(options);
        }
  1. mParent 代表 ActivityGroup,ActivityGroup 已经在 API 13 中废弃了,官方推荐使用 Fragment 来代替 ActivityGroup ,所以这个
    mParent == null 会一直成立
  2. ApplicationThread() 是 ActivityThread 的内部类,通过后面的分析你会发现他对 Activity 的启动过程起着至关重要的作用
  3. Instrumentation 看这个类的注释解释这个类的作用是一个仪器测试类,后面分析中你们会发现 Activity 的主要流程都会在这个类中
    好了我们继续查看 Instrumentation 的 execStartActivity 方法
 int result = ActivityManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target, requestCode, 0, null, options);
checkStartActivityResult(result, intent);

我们看一下 ActivityManager.getService()

public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
}

private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };

再以上代码中我们可以分析出 ActivityManager.getService() 获得的就是一个 IActivityManager 的 Binder 对象,由此推出这里的 Activity 的启动是远程调用的 ActivityManagerService 也就是我们熟知的 AMS,这里就是典型的 Binder 的使用,不熟悉的可以了解下 Binder 机制

如何验证 Context.ACTIVITY_SERVICE 这个 Service 为 ActivityManagerService 呢,看一下 ActivityManagerService 的 setSystemProcess() 就了解了

ServiceManager.addService(Context.ACTIVITY_SERVICE, this, true);

我们再额外说一下 checkStartActivityResult 方法,跟进去可以发现全是报错信息是吧,平常我们常见的也有好多。就比如说这个

throw new ActivityNotFoundException(
                            "Unable to find explicit activity class "
                            + ((Intent)intent).getComponent().toShortString()
                            + "; have you declared this activity in your AndroidManifest.xml?");

二、Context 的 startActivity()

因为 abstract 是一个抽象类,我们找到他的实现类 ContextImpl,那么为什么确定是 ContextImpl 呢,我们提前先看一下 ActivityThread.performLaunchActivity() 的方法里面调用的 createBaseContextForActivity() 方法

ContextImpl appContext = ContextImpl.createActivityContext(
                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);

关键代码是 ContextImpl.createActivityContext() 方法

static ContextImpl createActivityContext(ActivityThread mainThread,
            LoadedApk packageInfo, ActivityInfo activityInfo, IBinder activityToken, int displayId,
            Configuration overrideConfiguration) {
        if (packageInfo == null) throw new IllegalArgumentException("packageInfo");

        String[] splitDirs = packageInfo.getSplitResDirs();
        ClassLoader classLoader = packageInfo.getClassLoader();

        ContextImpl context = new ContextImpl(null, mainThread, packageInfo, activityInfo.splitName,
                activityToken, null, 0, classLoader);
        return context;
    }

这里看到了 activity.attach() 传入的 appContext 实际上就是 ContextImpl 。

我们接下来继续查看 ContextImpl 的 startActivity() 方法

if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);

又看到了熟悉的代码,还有一个地方不知道你们发没发现,使用 Context 会判断你的 Flags 如果不是 FLAG_ACTIVITY_NEW_TASK 就会报错,不知道各位观众老爷有没有遇见过

三、Fragment 的 startActivity()/startActivityForResult()

查看 Fragment 的 startActivity() 方法看到以下关键代码

 mHost.onStartActivityFromFragment(this /*fragment*/, intent, -1, options);

这个地方是通过 mHost(FragmentHostCallback) 对象来执行启动流程的,然而这个类也是抽象的,好歹他就一个实现类 HostCallbacks,查看 HostCallbacks 的 onStartActivityFromFragment() 方法

FragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode);

这个类调用的是 FragmentActivity 的 startActivityFromFragment 方法,我们继续往里跟

if (requestCode == -1) {
        ActivityCompat.startActivityForResult(this, intent, -1, options);
        return;
}
checkForValidRequestCode(requestCode);
int requestIndex = allocateRequestIndex(fragment);
 ActivityCompat.startActivityForResult(
                    this, intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), options);

这个地方挺有意思的一点就是他的 requestCode 经过了简单的修改,这估计就是为了 Fragment 也能收到 onActivityResult 所作的处理,感兴趣可以自己看一下这不是我们这篇文章要讲的。
ActivityCompat 是 v4 包的一个兼容类,包括我们平时写代码的时候也可以用,废话不多说我们继续跟

if (Build.VERSION.SDK_INT >= 16) {
      activity.startActivityForResult(intent, requestCode, options);
} else {
       activity.startActivityForResult(intent, requestCode);
}

又回到了 FragmentActivity 的 startActivityForResult 方法中

super.startActivityForResult(intent, requestCode);

这里他直接调用了 Activity 的启动方法

四、Launcher 启动应用

到这里我们简单的讲了我们开发中所能用到的启动 Activity 的方式,我们在看一个我们没见过的 Launcher 中启动应用
我们找到源码 packages/apps/Launcher3/src/com/android/launcher3/Launcher.java

 public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
        //添加 FLAG_ACTIVITY_NEW_TASK Flags
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        
        try {
            if (Utilities.ATLEAST_MARSHMALLOW
                    && (item instanceof ShortcutInfo)
                    && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
                     || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                    && !((ShortcutInfo) item).isPromise()) {
                // Shortcuts need some special checks due to legacy reasons.
                startShortcutIntentSafely(intent, optsBundle, item);
            } else if (user == null || user.equals(Process.myUserHandle())) {
                // Could be launching some bookkeeping activity
                //启动 Activity
                startActivity(intent, optsBundle);
            } else {
                LauncherAppsCompat.getInstance(this).startActivityForProfile(
                        intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
            }
            return true;
        } catch (ActivityNotFoundException|SecurityException e) {
        }
        return false;
    }

我们再看一下 他也是直接调用的 Activity 的 startActivity() 方法,后面的就和之前分析的 Activity.startActivity() 的启动一样了

img_e89fe89627c41ab8492884c09cb90b5d.jpe
欢迎关注我的公众号
目录
相关文章
|
Rust 算法 数据安全/隐私保护
【密码学】一文读懂XTEA加密
本篇文章,我们来看一下上一次讲过的TEA加密算法的一个升级版XTEA, 相比于TEA, XTEA的安全性显然是更高的,其中的过程要比TEA稍微复杂一点点。
1615 0
【密码学】一文读懂XTEA加密
|
Ubuntu 网络安全 Docker
Ubuntu 安装与配置ssh (docker)
Ubuntu 安装与配置ssh (docker)
354 0
|
开发框架 安全 前端开发
[ 代码审计篇 ] Java web 代码审计 详解(一)
之前写了一篇关于代码审计流程的文章,有小伙伴私聊我说不太好理解,这里开始介绍 Java web 的代码审计,同样的来捋一捋审计思路,这就相对要具体一些。
2421 0
[ 代码审计篇 ] Java web 代码审计 详解(一)
CMake Error: The source “xxx“ does not match the source “yyy“ used to generate cache. Re-run cmake
CMake Error: The source “xxx“ does not match the source “yyy“ used to generate cache. Re-run cmake
1571 0
|
开发工具 git Windows
VSCode下载与安装使用教程【超详细讲解】
VSCode下载与安装使用教程【超详细讲解】
4862 0
VSCode下载与安装使用教程【超详细讲解】
|
缓存 iOS开发
IOS网络编程:使用 URLSession 实现网络请求的步骤是什么?
IOS网络编程:使用 URLSession 实现网络请求的步骤是什么?
285 1
|
存储 人工智能 数据格式
总说具身智能的数据太贵,鹏城实验室开源百万规模标准化数据集
【9月更文挑战第18天】鹏城实验室提出的ARIO(All Robots In One)标准,为具身智能领域带来了统一的数据格式、丰富的感知模态及多样化的真实与模拟数据,显著提升了数据集的质量与规模,助力智能系统更好地与物理世界互动。基于此标准构建的大规模数据集包含约300万个片段,覆盖258个系列和321,064个任务,极大地推动了具身智能的研究与发展。然而,该数据集也面临着存储需求高、系统互操作性及应用场景适应性等挑战。论文详情见:http://arxiv.org/abs/2408.10899。
403 12
|
存储 UED
从零开始构建个人网站:初学者指南
【5月更文挑战第9天】本文是初学者构建个人网站的指南,包括明确目标、选择域名和主机、挑选网站建设工具(如WordPress、Wix、Squarespace)、设计网站结构和布局、创建内容、优化测试以及推广维护。按照这些步骤,新手也能轻松建立自己的个人网站。记得在过程中不断学习和改进,祝你建站成功!
1788 1
|
XML Android开发 数据格式
Android启动页解决攻略最终版
相信很多人都在网上查过关于启动白屏或者黑屏的问题。 一般的App应该是分为两种: 有闪屏页或者启动页(SplashActivity),页面大概会持续2到3秒 没有闪屏页和启动页,打开应用后会直接跳转到应用主界面 不管有没有启动页,如果你不处理,你会发现当你点击桌面上那个icon图标的时候会先闪白屏或者黑屏一下,然后才会进入我们设定的页面。
2457 0