从源码看Application的启动流程

开篇废话

开篇废话就真的是废话,强烈建议直接跳过。
一直看重用户体验的我,很想做好一款超级好用的产品,但我只是一个默默开发的开发,一切都要跟产品大佬的奇葩需求走(这里也体谅一下产品吧,产品也是跟用户走的,还要考虑收益等等问题)。好多好多年前就开始用CSDN了,可能是早期用户的原因吧,博客质量不怎么样,但是总排名在前万名,可以看到数字,1w+就不显示数字了。一开始的CSDN是很丑的,没有支持MarkDown,排版不好看,书写也不方便,后来发现了简书,简书是超级好看,虐爆CSDN,所以后来的博客就都发表在简书上面了。但是简书毕竟太杂了,打开首页满屏鸡汤,还是CSDN专业一点,SEO也超好。一不小心回来CSDN看了一下,发现也默认使用MarkDown了啊,排版也超好看啊,甚至比简书好看多了,简书那个黑色的代码框超难看,还不能修改,还是CSDN的灰色好看,于是我又回来了!以后努力写更多有用又好看的博客吧!尽管CSDN的博客页各种广告,留言排版各种难看,个人中心通知各种乱七八糟。

大致流程

image

详细流程

image

查看源码方法

1、直接下载源码,之前写了篇下载源码的文章,不需要全部下载,只下载你需要的部分就行了,这样就不会几个G几个G的了,几百M而已,文章:超级简单的Android源码下载
2、直接在AndroidStudio中双击shift键搜索文件就行了。需要写注释的话,直接在源码上面写,它会弹框问你允不允许修改源码的。推荐这种方法,可以看到基本上你想看的源码了,那些看不到的可以结合线上看,链接 :http://androidxref.com

源码分析(基于API28)

一个应用的入口就是main方法了吧,这里直接从ActivityThreadmain方法开始看吧。

public static void main(String[] args) {
    Looper.prepareMainLooper();
    // ...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);
    // ...
    Looper.loop();
}

main方法重点就是这几行,Looper.prepareMainLooper()Looper.loop()可以看出这些代码就是执行在应用进程了,而且就是主线程。loop之前创建了ActivityThread对象,并调用了attach方法,跟进看一下。

private void attach(boolean system, long startSeq) {
    // ...
    final IActivityManager mgr = ActivityManager.getService();
    mgr.attachApplication(mAppThread, startSeq);
    // ...
}

ActivityManager.getService()拿到的是IActivityManager,它是ActivityManagerService的代理对象,也就是在AMS进程中了。值得注意的是这里传入了个mAppThread,对应的类是ApplicationThread,ActivityThread的一个内部类,后面AMS会回调此类。继续看attachApplication,这部分代码在ActivityManagerService里面。大概意思就是通知AMS获取和生成当前应用进程的相关信息,并进行绑定,以便管理。
附1:IActivityManager.aidl源码
附2:IApplicationThread.aidl源码

@Override
public final void attachApplication(IApplicationThread thread, long startSeq) {
    // ...
    // thread 是前面传过来的ApplicationThread
    attachApplicationLocked(thread, callingPid, callingUid, startSeq);
    // ...
}

private final boolean attachApplicationLocked(IApplicationThread thread,
        int pid, int callingUid, long startSeq) {
    // ...
    // 调用应用自身的进程bindApplication
    thread.bindApplication(processName, appInfo, providers,
            app.instr.mClass,
            profilerInfo, app.instr.mArguments,
            app.instr.mWatcher,
            app.instr.mUiAutomationConnection, testMode,
            mBinderTransactionTrackingEnabled, enableTrackAllocation,
            isRestrictedBackupMode || !normalMode, app.persistent,
            new Configuration(getGlobalConfiguration()), app.compat,
            getCommonServicesLocked(app.isolated),
            mCoreSettingsObserver.getCoreSettingsLocked(),
            buildSerial, isAutofillCompatEnabled);
    // ...
    // 下面的流程是启动Activity的了,此文先不做分析
    if (mStackSupervisor.attachApplicationLocked(app)) {...}
    // ...
}

thread仍然是前面传过来的ApplicationThread,调用bindApplication方法通知应用进程启动Application。我们回到ApplicationThreadbindApplication方法看看,回到了应用自身的进程了。
疑问:bindApplication应该是异步执行的,所以bindApplication和attachApplicationLocked应该是同步执行的。

public final void bindApplication(String processName, ApplicationInfo appInfo,
        List<ProviderInfo> providers, ComponentName instrumentationName,
        ProfilerInfo profilerInfo, Bundle instrumentationArgs,
        IInstrumentationWatcher instrumentationWatcher,
        IUiAutomationConnection instrumentationUiConnection, int debugMode,
        boolean enableBinderTracking, boolean trackAllocation,
        boolean isRestrictedBackupMode, boolean persistent, Configuration config,
        CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
        String buildSerial, boolean autofillCompatibilityEnabled) {
    // ...
    AppBindData data = new AppBindData();
    // ...
    sendMessage(H.BIND_APPLICATION, data);
}

bindApplication是AMS进程远程调用,有oneway修饰,应该是执行在子线程的,这里通过Handler转到主线程执行,发送了H.BIND_APPLICATION消息,继续进入handleMessage看看。

public void handleMessage(Message msg) {
    switch (msg.what) {
        case BIND_APPLICATION:
            AppBindData data = (AppBindData)msg.obj;
            handleBindApplication(data);
            // ...
            break;
        // ...
    }
}

private void handleBindApplication(AppBindData data) {
    // ...
    mBoundApplication = data;
    // ...
    // data.info就是一个LoadedApk,虽然我也不知道是干嘛用的
    data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
    // ...
    Application app;
    app = data.info.makeApplication(data.restrictedBackupMode, null);
    // ...
}

public Application makeApplication(boolean forceDefaultAppClass,
        Instrumentation instrumentation) {
    // ...
    Application app = null;
    // 在manifest里面配置的Application类,没配就是默认的android.app.Application
    String appClass = mApplicationInfo.className;
    if (forceDefaultAppClass || (appClass == null)) {
        appClass = "android.app.Application";
    }
    // ...
    try {
        java.lang.ClassLoader cl = getClassLoader();
        // ...
        // 这个就是ApplicationContext
        ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
        // 这里创建了Application对象
        app = mActivityThread.mInstrumentation.newApplication(
                cl, appClass, appContext);
    } catch (Exception e) {...}
    // ...
    if (instrumentation != null) {
        try {
            // 调用Application的onCreate方法
            instrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {...}
    }
    // ...
}

从这里可以看出,Application对象是用反射创建出来的。而且是在应用进程的主线程上创建的。先继续进入InstrumentationnewApplication方法看看,再回来看instrumentation.callApplicationOnCreate(app);

public Application newApplication(ClassLoader cl, String className, Context context)
        throws InstantiationException, IllegalAccessException, 
        ClassNotFoundException {
    Application app = getFactory(context.getPackageName())
            .instantiateApplication(cl, className);
    app.attach(context);
    return app;
}

final void attach(Context context) {
    attachBaseContext(context);
    // ...
}

这里调用了attachBaseContext方法,这个方法也是常用的,比如:MultiDex.install(base)就是写在这个方法里面。
接下来我们回到刚才那个instrumentation.callApplicationOnCreate(app);,instrumentation是ActivityThread的一个变量,对应的类是Instrumentation,直接进入这个类的callApplicationOnCreate方法。

public void callApplicationOnCreate(Application app) {
    app.onCreate();
}

到这里就是我们熟悉的Application.onCreate()了,对于一个Android应用的入口就是这里了,跑完onCreate()就会启动Activity,或者Service等。
顺便说一个初学者大多不知道的点,Application和应用不是一一对应的,如果一个应用有多个进程,那么每个进程都会有一个Application,进程启动的时候都会先创建Application,先跑onCreate()方法。所以初始化一些东西的时候,最好判断一下当前是不是主进程,避免重复进行一些不必要的初始化。还有就是在应用没起来的时候启动Service或者收到广播,都会先创建Application,再做其它的事情。

写在最后

以前看源码总是乱七八糟的,跳来跳去,还没跟到最后就跟丢了,就算勉强看完整个流程了,过两天又忘记了,再次看依然要花很大的力气,一直都在心里骂谷歌的工程师写的都是什么破代码,后来发现用时序图把源码流程画出来之后清晰多了,一目了然,需要的时候看一下图就清楚了,了解某一个点的细节直接从指定位置开始看源码就行。时序图,以后你就是我女朋友了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 230,622评论 6 544
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 99,716评论 3 429
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 178,746评论 0 383
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 63,991评论 1 318
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 72,706评论 6 413
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 56,036评论 1 329
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 44,029评论 3 450
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 43,203评论 0 290
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 49,725评论 1 336
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 41,451评论 3 361
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 43,677评论 1 374
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 39,161评论 5 365
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 44,857评论 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 35,266评论 0 28
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 36,606评论 1 295
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 52,407评论 3 400
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 48,643评论 2 380

推荐阅读更多精彩内容