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稍微复杂一点点。
1569 0
【密码学】一文读懂XTEA加密
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
1522 0
|
缓存 程序员 定位技术
Android Studio 插件,那些被大厂优化的程序员们
Android Studio 插件,那些被大厂优化的程序员们
|
缓存 iOS开发
IOS网络编程:使用 URLSession 实现网络请求的步骤是什么?
IOS网络编程:使用 URLSession 实现网络请求的步骤是什么?
262 1
|
存储 算法 数据安全/隐私保护
【密码学】一文读懂白盒AES(Chow方案)(一)
本文主要参考了文献^[1], 代码参考了^[2], 这里感谢文献作者和代码作者,如果有能力的大佬,可以自行查看原文献,个人水平有限,有哪里写的不对的地方,也欢迎读者指正。
4764 0
【密码学】一文读懂白盒AES(Chow方案)(一)
|
开发工具 git
|
XML Android开发 数据格式
Android启动页解决攻略最终版
相信很多人都在网上查过关于启动白屏或者黑屏的问题。 一般的App应该是分为两种: 有闪屏页或者启动页(SplashActivity),页面大概会持续2到3秒 没有闪屏页和启动页,打开应用后会直接跳转到应用主界面 不管有没有启动页,如果你不处理,你会发现当你点击桌面上那个icon图标的时候会先闪白屏或者黑屏一下,然后才会进入我们设定的页面。
2437 0
|
SQL
SQL 转义字符
SQL 的转义字符是:'(单引号) 例:select * from tbl where uyear='''06' 请注意其中红色背景的单引号,它即表示转义字符,如果我们省略,则整个语句会出错,转义字符不会输出,上例中 uyear 的实际条件值为 '06,而不是 ''06 为什么不能省略呢,假如我们省略,上句变成:select * from tbl where uyear=''
4079 0
|
前端开发 Java 数据库连接
超详细讲解SpringMVC三层架构
超详细讲解SpringMVC三层架构
949 0
|
缓存 前端开发
phpcms之 轮播图的添加
phpcms之 轮播图的添加
241 1