注册

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



前言

首先,new一个女儿,

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

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

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

我心里一惊: img

小天才电话手表的系统就是Android,所以这不就是。。面试官常考的应用启动流程嘛!
女儿也要来面试我了吗!😭
好了,既然女儿问了,那就答吧。
但是,对付这个小小的0经验面试官,我该咋说呢?

解答小小面试官

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

  • 一个老师:Z老师(Zygote进程)

  • 一个班长:小A(ActivityManagerService)

  • 一个班干部:小L(Launcher桌面应用)

  • 一大堆小朋友:所有应用,包括音乐小朋友,聊天小朋友,日历小朋友等等。

img

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

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

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

img

img

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

十五年后

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不允许存在多线程?

0 个评论

要回复文章请先登录注册