女儿拿着小天才电话手表问我App启动流程(上)

简介: “爸爸爸爸,你说我玩的这个小天才电话手表怎么这么厉害,随便点一下这个小图片,这个应用就冒出来了,就可以听儿歌了。好神奇啊。”

前言


首先,new一个女儿,


var mDdaughter = new 女儿("6岁",“漂亮可爱”,“健康乖巧”,“最喜欢玩小天才电话手表和她的爸爸”)


好了,女儿有了,有一天,女儿问我:


“爸爸爸爸,你说我玩的这个小天才电话手表怎么这么厉害,随便点一下这个小图片,这个应用就冒出来了,就可以听儿歌了。好神奇啊。”


我心里一惊:


6.jpg


小天才电话手表的系统就是Android,所以这不就是。。面试官常考的应用启动流程嘛!


女儿也要来面试我了吗!😭


好了,既然女儿问了,那就答吧。


但是,对付这个小小的0经验面试官,我该咋说呢?


解答小小面试官


女儿,你可以把手表里面想象成一个幼儿园,里面有一个老师,一个班长,一个班干部,以及一大堆小朋友。


  • 一个老师:Z老师(Zygote进程)
  • 一个班长:小A(ActivityManagerService)
  • 一个班干部:小L(Launcher桌面应用)
  • 一大堆小朋友:所有应用,包括音乐小朋友,聊天小朋友,日历小朋友等等。


7.jpg


应用启动过程就像一个小朋友被叫醒一样,开机之后呢,Z老师会依次叫醒班长和班干部(SystemServer#ActivityManagerService,Launcher),小L醒了之后就会去了解手表里有哪些小朋友,长什么样(icon,name),家庭信息(包名,androidmanifest)等等,然后一个个把小朋友的照片(icon)贴到自己的身上。比如有音乐小朋友,聊天小朋友,日历小朋友,其实也就是你手表上这个桌面啦。


这时候你要点开一个音乐小朋友呢(startActivity),小L就会通知班长小A(Binder),小A知道了之后,让小L自己休息下(Paused),然后就去找Z老师了。Z老师就负责叫音乐小朋友起床了(fork进程,启动ActivityThread),音乐小朋友起来后就又找小A带她去洗脸刷牙(启动ApplicationThread,Activity),都弄完了就可以进行各种表演了,唱歌啊,跳舞啊。


不是很明白啊?我们一起聊个天你就懂了,假如我是Launcher


8.png


9.png


女儿似懂非懂的给我点了一个赞👍,爸爸你真棒。


十五年后


mDdaughter.grow(15)
mDdaughter.study("Android")


过了十五年,女儿已经21岁了,正在学习Android,考虑要不要女从父业。


这天,她一脸疑惑的来找我:“爸,这个app启动到底是怎么个流程啊,我看了好久还是不大明白,要不你再跟我详细讲一遍吧?” “好嘞,别担心,我这次详细跟你说说”


解答Android程序媛


还记得我小时候跟你说过的故事吗,Android系统就像一个幼儿园,有一个大朋友叫Launcher,身上会贴很多其他小朋友的名片。这个Launcher就是我们的桌面了,它通过PackageManagerService获知了系统里所有应用的信息,并展示了出来,当然它本身也是一个应用。


通过点击一个应用图标,也就是触发了点击事件,最后会执行到startActivity方法。这里也就和启动Activity步骤重合上了。


那么这个startActivity干了啥?是怎么通过重重关卡唤醒这个应用的?


首先,介绍下系统中那些重要的成员,他们在app启动流程中都担任了重要的角色.


系统成员介绍


  • init进程,Android系统启动后,Zygote并不是第一个进程,而是linux的根进程init进程,然后init进程才会启动Zygote进程。


  • Zygote进程,所有android进程的父进程,当然也包括SystemServer进程


  • SystemServer进程,正如名字一样,系统服务进程,负责系统中大大小小的事物,为此也是启动了三员大将(ActivityManagerService,PackageManagerService,WindowManagerService)以及binder线程池。


  • ActivityManagerService,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,对于一些进程的启动,都会通过Binder通信机制传递给AMS,再处理给Zygote。


  • PackageManagerService,主要负责应用包的一些操作,比如安装,卸载,解析AndroidManifest.xml,扫描文件信息等等。


  • WindowManagerService,主要负责窗口相关的一些服务,比如窗口的启动,添加,删除等。


  • Launcher,桌面应用,也是属于应用,也有自己的Activity,一开机就会默认启动,通过设置Intent.CATEGORY_HOME的Category隐式启动。


搞清楚这些成员,就跟随我一起看看怎么过五关斩六将,最终启动了一个App。


第一关:跨进程通信,告诉系统我的需求


首先,要告诉系统,我Launcher要启动一个应用了,调用Activity.startActivityForResult方法,最终会转到mInstrumentation.execStartActivity方法。由于Launcher自己处在一个单独的进程,所以它需要跨进程告诉系统服务我要启动App的需求。找到要通知的Service,名叫ActivityTaskManagerService,然后使用AIDL,通过Binder与他进行通信。


这里的简单说下ActivityTaskManagerService(简称ATMS)。原来这些通信工作都是属于ActivityManagerService,现在分了一部分工作给到ATMS,主要包括四大组件的调度工作。也是由SystemServer进程直接启动的,相关源码可见ActivityManagerService.Lifecycle.startService方法,感兴趣朋友可以自己看看。


接着说跨进程通信,相关代码如下:


//Instrumentation.java
    int result = ActivityTaskManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
    //ActivityTaskManager.java            
    public static IActivityTaskManager getService() {
        return IActivityTaskManagerSingleton.get();
    }
    private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
            new Singleton<IActivityTaskManager>() {
                @Override
                protected IActivityTaskManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                    return IActivityTaskManager.Stub.asInterface(b);
                }
            };
    //ActivityTaskManagerService.java
    public class ActivityTaskManagerService extends IActivityTaskManager.Stub
    public static final class Lifecycle extends SystemService {
        private final ActivityTaskManagerService mService;
        public Lifecycle(Context context) {
            super(context);
            mService = new ActivityTaskManagerService(context);
        }
        @Override
        public void onStart() {
            publishBinderService(Context.ACTIVITY_TASK_SERVICE, mService);
            mService.start();
        }
    }


startActivity我们都很熟悉,平时启动Activity都会使用,启动应用也是从这个方法开始的,也会同样带上intent信息,表示要启动的是哪个Activity。


另外要注意的一点是,startActivity之后有个checkStartActivityResult方法,这个方法是用作检查启动Activity的结果。当启动Activity失败的时候,就会通过这个方法抛出异常,比如有我们常见的问题:未在AndroidManifest.xml注册。


public static void checkStartActivityResult(int res, Object intent) {
        switch (res) {
            case ActivityManager.START_INTENT_NOT_RESOLVED:
            case ActivityManager.START_CLASS_NOT_FOUND:
                if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                    throw new ActivityNotFoundException(
                            "Unable to find explicit activity class "
                            + ((Intent)intent).getComponent().toShortString()
                            + "; have you declared this activity in your AndroidManifest.xml?");
                throw new ActivityNotFoundException(
                        "No Activity found to handle " + intent);
            case ActivityManager.START_PERMISSION_DENIED:
                throw new SecurityException("Not allowed to start activity "
                        + intent);
            case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
                throw new AndroidRuntimeException(
                        "FORWARD_RESULT_FLAG used while also requesting a result");
            case ActivityManager.START_NOT_ACTIVITY:
                throw new IllegalArgumentException(
                        "PendingIntent is not an activity");
            //...
        }
    }


第二关:通知Launcher可以休息了


ATMS收到要启动的消息后,就会通知上一个应用,也就是Launcher可以休息会了,进入Paused状态。


//ActivityStack.java
    private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        //...
        ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);
        //...
        boolean pausing = getDisplay().pauseBackStacks(userLeaving, next, false);
        if (mResumedActivity != null) {
            if (DEBUG_STATES) Slog.d(TAG_STATES,
                    "resumeTopActivityLocked: Pausing " + mResumedActivity);
            pausing |= startPausingLocked(userLeaving, false, next, false);
        }
        //...
        if (next.attachedToProcess()) {
            //应用已经启动
            try {
                //...
                transaction.setLifecycleStateRequest(
                        ResumeActivityItem.obtain(next.app.getReportedProcState(),
                                getDisplay().mDisplayContent.isNextTransitionForward()));
                mService.getLifecycleManager().scheduleTransaction(transaction);
                //...
            } catch (Exception e) {
                //...
                mStackSupervisor.startSpecificActivityLocked(next, true, false);
                return true;
            }
            //...
            // From this point on, if something goes wrong there is no way
            // to recover the activity.
            try {
                next.completeResumeLocked();
            } catch (Exception e) {
                // If any exception gets thrown, toss away this
                // activity and try the next one.
                Slog.w(TAG, "Exception thrown during resume of " + next, e);
                requestFinishActivityLocked(next.appToken, Activity.RESULT_CANCELED, null,
                        "resume-exception", true);
                return true;
            }
        } else {
            //冷启动流程
            mStackSupervisor.startSpecificActivityLocked(next, true, true);
        }        
    }


这里有两个类没有见过:


  • ActivityStack,是Activity的栈管理,相当于我们平时项目里面自己写的Activity管理类,用于管理Activity的状态啊,如栈出栈顺序等等。
  • ActivityRecord,代表具体的某一个Activity,存放了该Activity的各种信息。


startPausingLocked方法就是让上一个应用,这里也就是Launcher进入Paused状态。然后就会判断应用是否启动,如果已经启动了,就会走ResumeActivityItem的方法,看这个名字,结合应用已经启动的前提,是不是已经猜到了它是干吗的?没错,这个就是用来控制Activity的onResume生命周期方法的,不仅是onResume还有onStart方法,具体可见ActivityThread的handleResumeActivity方法源码。


如果应用没启动就会接着走到startSpecificActivityLocked方法,接着看。


第三关:是否已启动进程,否则创建进程


Launcher进入Paused之后,ActivityTaskManagerService就会判断要打开的这个应用进程是否已经启动,如果已经启动,则直接启动Activity即可,这也就是应用内的启动Activity流程。如果进程没有启动,则需要创建进程。


这里有两个问题:


  • 怎么判断应用进程是否存在呢?如果一个应用已经启动了,会在ATMS里面保存一个WindowProcessController信息,这个信息包括processName和uid,uid则是应用程序的id,可以通过applicationInfo.uid获取。processName则是进程名,一般为程序包名。所以判断是否存在应用进程,则是根据processName和uid去判断是否有对应的WindowProcessController,并且WindowProcessController里面的线程不为空。代码如下:


//ActivityStackSupervisor.java
    void startSpecificActivityLocked(ActivityRecord r, boolean andResume, boolean checkConfig) {
        // Is this activity's application already running?
        final WindowProcessController wpc =
                mService.getProcessController(r.processName, r.info.applicationInfo.uid);
        boolean knownToBeDead = false;
        if (wpc != null && wpc.hasThread()) {
            //应用进程存在
            try {
                realStartActivityLocked(r, wpc, andResume, checkConfig);
                return;
            } 
        }
    }
    //WindowProcessController.java
    IApplicationThread getThread() {
        return mThread;
    }
    boolean hasThread() {
        return mThread != null;
    }


  • 还有个问题就是怎么创建进程?还记得Z老师吗?对,就是Zygote进程。之前说了他是所有进程的父进程,所以就要通知Zygote去fork一个新的进程,服务于这个应用。


//ZygoteProcess.java
    private Process.ProcessStartResult attemptUsapSendArgsAndGetResult(
            ZygoteState zygoteState, String msgStr)
            throws ZygoteStartFailedEx, IOException {
        try (LocalSocket usapSessionSocket = zygoteState.getUsapSessionSocket()) {
            final BufferedWriter usapWriter =
                    new BufferedWriter(
                            new OutputStreamWriter(usapSessionSocket.getOutputStream()),
                            Zygote.SOCKET_BUFFER_SIZE);
            final DataInputStream usapReader =
                    new DataInputStream(usapSessionSocket.getInputStream());
            usapWriter.write(msgStr);
            usapWriter.flush();
            Process.ProcessStartResult result = new Process.ProcessStartResult();
            result.pid = usapReader.readInt();
            // USAPs can't be used to spawn processes that need wrappers.
            result.usingWrapper = false;
            if (result.pid >= 0) {
                return result;
            } else {
                throw new ZygoteStartFailedEx("USAP specialization failed");
            }
        }
    }


可以看到,这里其实是通过socket和Zygote进行通信,BufferedWriter用于读取和接收消息。这里将要新建进程的消息传递给Zygote,由Zygote进行fork进程,并返回新进程的pid。


可能又会有人问了?fork是啥?为啥这里又变成socket进行IPC通信,而不是Bindler了?


  • 首先,fork()是一个方法,是类Unix操作系统上创建进程的主要方法。用于创建子进程(等同于当前进程的副本)。
  • 那为什么fork的时候不用Binder而用socket了呢?主要是因为fork不允许存在多线程,Binder通讯偏偏就是多线程。


问题总是在不断产生,总有好奇的朋友会接着问,为什么fork不允许存在多线程?


  • 防止死锁。其实你想想,多线程+多进程,听起就不咋靠谱是不。假设多线程里面线程A对某个锁lock,另外一个线程B调用fork创建了子进程,但是子进程却没有了线程A,但是锁本身却被fork了出来,那么这个锁没人可以打开了。一旦子进程中另外的线程又对这个锁进行lock,就死锁了。


目录
相关文章
|
6月前
|
编解码 iOS开发 开发者
App上架Apple App Store和Google Play流程
App上架Apple App Store和Google Play流程
173 2
|
1月前
|
前端开发 Java 开发工具
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
76 18
【03】完整flutter的APP打包流程-以apk设置图标-包名-签名-APP名-打包流程为例—-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈 章节内容【03】
|
9月前
|
API 数据安全/隐私保护 iOS开发
利用uni-app 开发的iOS app 发布到App Store全流程
利用uni-app 开发的iOS app 发布到App Store全流程
270 3
|
9天前
|
Web App开发 前端开发 安全
语音交友app系统源码功能及技术研发流程剖析
语音交友App核心功能包括语音聊天(一对一、群聊、语音消息)、语音房间(直播、主题房、管理)、社交互动(好友、关注、打赏)、内容发现、音效美化、通知提醒及安全隐私等。开发流程涵盖需求分析、技术选型(前端、后端、数据库、实时通信)、UI/UX设计、前后端开发、实时通信集成、音效处理、测试优化、部署上线及运营维护,确保稳定高效运行并持续优化用户体验。
|
9天前
|
前端开发 安全 开发工具
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
137 90
【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
25天前
|
Dart 前端开发
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
116 75
【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
1月前
|
安全 数据安全/隐私保护 Android开发
【05】2025年1月首发完整版-篇幅较长-苹果app如何上架到app store完整流程·不借助第三方上架工具的情况下无需花钱但需仔细学习-优雅草央千澈详解关于APP签名以及分发-们最关心的一篇来了-IOS上架app
【05】2025年1月首发完整版-篇幅较长-苹果app如何上架到app store完整流程·不借助第三方上架工具的情况下无需花钱但需仔细学习-优雅草央千澈详解关于APP签名以及分发-们最关心的一篇来了-IOS上架app
224 75
|
14天前
|
前端开发 Java Shell
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
113 20
【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
|
22天前
|
Dart 前端开发 容器
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
73 18
【07】flutter完成主页-完成底部菜单栏并且做自定义组件-完整短视频仿抖音上下滑动页面-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
|
12天前
|
Dart 前端开发 Android开发
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
35 4
【09】flutter首页进行了完善-采用android studio 进行真机调试开发-增加了直播间列表和短视频人物列表-增加了用户中心-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex

热门文章

最新文章

  • 1
    MNN-LLM App:在手机上离线运行大模型,阿里巴巴开源基于 MNN-LLM 框架开发的手机 AI 助手应用
  • 2
    【11】flutter进行了聊天页面的开发-增加了即时通讯聊天的整体页面和组件-切换-朋友-陌生人-vip开通详细页面-即时通讯sdk准备-直播sdk准备-即时通讯有无UI集成的区别介绍-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 3
    微信小程序 app.json 配置文件解析与应用
  • 4
    【Azure App Service】基于Linux创建的App Service是否可以主动升级内置的Nginx版本呢?
  • 5
    【05】flutter完成注册页面完善样式bug-增加自定义可复用组件widgets-严格规划文件和目录结构-规范入口文件-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草央千澈
  • 6
    【Azure Function】Function App出现System.IO.FileNotFoundException异常
  • 7
    原生鸿蒙版小艺APP接入DeepSeek-R1,为HarmonyOS应用开发注入新活力
  • 8
    【Azure Logic App】使用MySQL 新增行触发器遇见错误 :“Unknown column 'created_at' in 'order clause'”
  • 9
    【08】flutter完成屏幕适配-重建Android,增加GetX路由,屏幕适配,基础导航栏-多版本SDK以及gradle造成的关于fvm的使用(flutter version manage)-卓伊凡换人优雅草Alex-开发完整的社交APP-前端客户端开发+数据联调|以优雅草商业项目为例做开发-flutter开发-全流程-商业应用级实战开发-优雅草Alex
  • 10
    阿里云APP备案流程图以及备案所需材料整理,跟着教程一步步操作