分析Android中应用的启动流程

前言

在我们开始之前,希望您能最好已经满足以下条件:

1、有一份编译后的Android源码(亲自动手实践才会有更深入的理解)

2、对Binder机制有一定的了解

本文启动流程分析基于Android 5.1的源码。为什么是5.1的源码呢?因为手边编译完的代码只有这个版本…另外,用什么版本的源码并不重要,大体的流程并无本质上的区别,仅仅是实现细节的调整,找一个你熟悉的版本就好。

1、启动时序图

作为一个轻微强迫症的人,整理的时序图,相信大家按图索骥,一定能搞明白整个启动流程:

说明:为了让大家更清楚的理解整个过程,将时序图中划分为三个部分:Launcher进程System进程App进程,其中有涉及共用的类以L / A进行区分表示跟哪个进程有关,便于理解。

2、关键类说明

整个启动流程因为会涉及到多次Binder通信,这里先简要说明一下几个类的用途,方便大家理解整个交互流程:

1、ActivityManagerService:AMS是Android中最核心的服务之一,主要负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,其职责与操作系统中的进程管理和调度模块相类似,因此它在Android中非常重要,它本身也是一个Binder的实现类。

2、Instrumentation:顾名思义,它用来监控应用程序和系统的交互。

3、ActivityThread:应用的入口类,系统通过调用main函数,开启消息循环队列。ActivityThread所在线程被称为应用的主线程(UI线程)。

4、ApplicationThread:ApplicationThread提供Binder通讯接口,AMS则通过代理调用此App进程的本地方法。

5、ActivityManagerProxy:AMS服务在当前进程的代理类,负责与AMS通信。

6、ApplicationThreadProxy:ApplicationThread在AMS服务中的代理类,负责与ApplicationThread通信。

3、流程分析

首先交代下整个流程分析的场景:用户点击Launcher上的应用图标到该应用主界面启动展示在用户眼前

这整个过程涉及到跨进程通信,所以我们将其划分为时序图中所展示三个进程:Launcher进程System进程App进程。为了不贴过长的代码又能说清楚进程间交互的流程,这里简述几个重要的交互点。

从时序图上大家也可以看到调用链相当长,对应的代码量也比较大,而且时序图只是分析了这个一个场景下的流程。道阻且长,行则将至!

3.1 Launcher响应用户点击,通知AMS

Launcher做为应用的入口,还是有必要交代一下的,我们来看看Launcher的代码片段,Launcher使用的是packages/apps/Launcher3的的源码。

public class Launcher extends Activity
  implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
     View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
 ...
 /**
  * Launches the intent referred by the clicked shortcut.
  *
  * @param v The view representing the clicked shortcut.
  */
 public void onClick(View v) {
  // Make sure that rogue clicks don't get through while allapps is launching, or after the
  // view has detached (it's possible for this to happen if the view is removed mid touch).
  if (v.getWindowToken() == null) {
   return;
  }

  ...

  Object tag = v.getTag();
  if (tag instanceof ShortcutInfo) {
   onClickAppShortcut(v);
  } else if (tag instanceof FolderInfo) {
   ...
  } else if (v == mAllAppsButton) {
   onClickAllAppsButton(v);
  } else if (tag instanceof AppInfo) {
   startAppShortcutOrInfoActivity(v);
  } else if (tag instanceof LauncherAppWidgetInfo) {
   ...
  }
 }

 private void startAppShortcutOrInfoActivity(View v) {
  ...
  boolean success = startActivitySafely(v, intent, tag);
  ...
 }

 boolean startActivitySafely(View v, Intent intent, Object tag) {
  ...
  try {
   success = startActivity(v, intent, tag);
  } catch (ActivityNotFoundException e) {
   ...
  }
  return success;
 }

 boolean startActivity(View v, Intent intent, Object tag) {
  intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  try {
   ...

   if (user == null || user.equals(UserHandleCompat.myUserHandle())) {
    // Could be launching some bookkeeping activity
    startActivity(intent, optsBundle);
   } else {
    ...
   }
   return true;
  } catch (SecurityException e) {
   ...
  }
  return false;
 }
}

通过starActicity辗转调用到Activity:startActivityForResult而后则调用至Instrumentation:execStartActivity,代码片段如下:

public class Instrumentation {
 ...
 public ActivityResult execStartActivity(
   Context who, IBinder contextThread, IBinder token, Activity target,
   Intent intent, int requestCode, Bundle options) {
  IApplicationThread whoThread = (IApplicationThread) contextThread;
  ...
  try {
   ...
   int result = ActivityManagerNative.getDefault()
    .startActivity(whoThread, who.getBasePackageName(), intent,
      intent.resolveTypeIfNeeded(who.getContentResolver()),
      token, target != null ? target.mEmbeddedID : null,
      requestCode, 0, null, options);
   ...
  } catch (RemoteException e) {
  }
  return null;
 }
 ...
}

这里的ActivityManagerNative.getDefault返回ActivityManagerService的远程接口,即ActivityManagerProxy接口,有人可能会问了为什么会是ActivityManagerProxy,这就涉及到Binder通信了,这里不再展开。通过Binder驱动程序,ActivityManagerProxy与AMS服务通信,则实现了跨进程到System进程。

3.2 AMS响应Launcher进程请求

从上面的流程我们知道,此时AMS应该处理Launcher进程发来的请求,请参看时序图及源码,此时我们来看ActivityStackSupervisor:startActivityUncheckedLocked方法,目测这个方法已经超过600行代码,来看一些关键代码片段:

public final class ActivityStackSupervisor implements DisplayListener {
 ...
 final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord,
   IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
 int startFlags, boolean doResume, Bundle options, TaskRecord inTask) {
 final Intent intent = r.intent;
 final int callingUid = r.launchedFromUid;
 ...
 final boolean launchSingleTop = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP;
 final boolean launchSingleInstance = r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE;
 final boolean launchSingleTask = r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK; 

 int launchFlags = intent.getFlags();
 ...
 // We'll invoke onUserLeaving before onPause only if the launching
 // activity did not explicitly state that this is an automated launch.
 mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0;
 ...

   ActivityRecord notTop =
    (launchFlags & Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null;

 // If the onlyIfNeeded flag is set, then we can do this if the activity
 // being launched is the same as the one making the call... or, as
 // a special case, if we do not know the caller then we count the
 // current top activity as the caller.
 if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) {
 ...
 }
   ...
   // If the caller is not coming from another activity, but has given us an
 // explicit task into which they would like us to launch the new activity,
 // then let's see about doing that.
 if (sourceRecord == null && inTask != null && inTask.stack != null) {
 final Intent baseIntent = inTask.getBaseIntent();
 final ActivityRecord root = inTask.getRootActivity();
 ...
 // If this task is empty, then we are adding the first activity -- it
 // determines the root, and must be launching as a NEW_TASK.
 if (launchSingleInstance || launchSingleTask) {
 ...
 }
 ...
 }
   ...
   if (inTask == null) {
 if (sourceRecord == null) {
 // This activity is not being started from another... in this
 // case we -always- start a new task.
 if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) {
  Slog.w(TAG, "startActivity called from non-Activity context; forcing " +
       "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent);
  launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
 }
 } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) {
 // The original activity who is starting us is running as a single
 // instance... this new activity it is starting must go on its
 // own task.
 launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
 } else if (launchSingleInstance || launchSingleTask) {
 // The activity being started is a single instance... it always
 // gets launched into its own task.
 launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
 }
 }

 ...

   // We may want to try to place the new activity in to an existing task. We always
 // do this if the target activity is singleTask or singleInstance; we will also do
 // this if NEW_TASK has been requested, and there is not an additional qualifier telling
 // us to still place it in a new task: multi task, always doc mode, or being asked to
 // launch this as a new task behind the current one.
 if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 &&
    (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0)
    || launchSingleInstance || launchSingleTask) {
 // If bring to front is requested, and no result is requested and we have not
 // been given an explicit task to launch in to, and
 // we can find a task that was started with this same
 // component, then instead of launching bring that one to the front.
 if (inTask == null && r.resultTo == null) {
 // See if there is a task to bring to the front. If this is
 // a SINGLE_INSTANCE activity, there can be one and only one
 // instance of it in the history, and it is always in its own
 // unique task, so we do a special search.
 ActivityRecord intentActivity = !launchSingleInstance ?
  findTaskLocked(r) : findActivityLocked(intent, r.info);
 if (intentActivity != null) {
  ...
 }
 }
 }

 ...

   if (r.packageName != null) {
   // If the activity being launched is the same as the one currently
   // at the top, then we need to check if it should only be launched
   // once.
   ActivityStack topStack = getFocusedStack();
   ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop);
   if (top != null && r.resultTo == null) {
    if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) {
     ...
    }
   }
   } else{
   ...
   }

 boolean newTask = false;
 boolean keepCurTransition = false;

 TaskRecord taskToAffiliate = launchTaskBehind && sourceRecord != null ?
    sourceRecord.task : null;

 // Should this be considered a new task?
 if (r.resultTo == null && inTask == null && !addingToTask
    && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
 ...
 if (reuseTask == null) {
    r.setTask(targetStack.createTaskRecord(getNextTaskId(),
      newTaskInfo != null ? newTaskInfo : r.info,
      newTaskIntent != null ? newTaskIntent : intent,
      voiceSession, voiceInteractor, !launchTaskBehind /* toTop */),
      taskToAffiliate);
    ...
   } else {
    r.setTask(reuseTask, taskToAffiliate);
   }
    ...
 } else if (sourceRecord != null) {

 } else if (!addingToTask &&
     (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) {

 } else if (inTask != null){

 } else {

 }

 ...

 targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options);

 ...
   return ActivityManager.START_SUCCESS;
 }
 ...
}

函数经过intent的标志值设置,通过findTaskLocked函数来查找存不存这样的Task,这里返回的结果是null,即intentActivity为null,因此,需要创建一个新的Task来启动这个Activity。现在处理堆栈顶端的ActivityLauncher,与我们即将要启动的MainActivity不是同一个Activity,创建了一个新的Task里面来启动这个Activity

经过栈顶检测,则需要将Launcher推入Paused状态,才可以启动新的Activity。后续则调用至ActivityStack:startPausingLocked,我们来看一下这个函数:

final class ActivityStack {
 ...
 final boolean startPausingLocked(boolean userLeaving, boolean uiSleeping, boolean resuming,
   boolean dontWait) {
 if (mPausingActivity != null) {
  ...
 }
 ActivityRecord prev = mResumedActivity;
 if (prev == null) {
  ...
 }
 ...
 mResumedActivity = null;
 mPausingActivity = prev;
 mLastPausedActivity = prev;
 mLastNoHistoryActivity = (prev.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0
    || (prev.info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0 ? prev : null;
 prev.state = ActivityState.PAUSING;
 ...

 if (prev.app != null && prev.app.thread != null) {
  try {
 ...
 prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
      userLeaving, prev.configChangeFlags, dontWait);
  } catch (Exception e) {
 ...
  }
 } else {
  ...
 }
 ...
 }
 ...
}

这里的prev.app.thread是一个ApplicationThread对象的远程接口,通过调用这个远程接口的schedulePauseActivity来通知Launcher进入Paused状态。至此,AMS对Launcher的请求已经响应,这是我们发现又通过Binder通信回调至Launcher进程。

3.3 Launcher进程挂起Launcher,再次通知AMS

这个流程相对会简单一些,我们来看ActivityThread:

public final class ActivityThread {
 ...
 private void handlePauseActivity(IBinder token, boolean finished,
   boolean userLeaving, int configChanges, boolean dontReport) {
  ActivityClientRecord r = mActivities.get(token);
  if (r != null) {
   ...
   performPauseActivity(token, finished, r.isPreHoneycomb());

   // Make sure any pending writes are now committed.
   if (r.isPreHoneycomb()) {
    QueuedWork.waitToFinish();
   }

   // Tell the activity manager we have paused.
   if (!dontReport) {
    try {
     ActivityManagerNative.getDefault().activityPaused(token);
    } catch (RemoteException ex) {
    }
   }
   ...
  }
 }
 ...
}

这部分Launcher的ActivityThread处理页面Paused并且再次通过ActivityManagerProxy通知AMS。

3.4 AMS创建新的进程

创建新进程的时候,AMS会保存一个ProcessRecord信息,如果应用程序中的AndroidManifest.xml配置文件中,我们没有指定Application标签的process属性,系统就会默认使用package的名称。每一个应用程序都有自己的uid,因此,这里uid + process的组合就可以为每一个应用程序创建一个ProcessRecord

public final class ActivityManagerService extends ActivityManagerNative
  implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
 ...
 private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) {
 ...
 try {
  ...
  // Start the process. It will either succeed and return a result containing
  // the PID of the new process, or else throw a RuntimeException.
  boolean isActivityProcess = (entryPoint == null);
  if (entryPoint == null) entryPoint = "android.app.ActivityThread";
  Process.ProcessStartResult startResult = Process.start(entryPoint,
     app.processName, uid, uid, gids, debugFlags, mountExternal,
     app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet,
     app.info.dataDir, entryPointArgs);
  ...
 } catch () {
  ...
 }
 }
 ...
}

这里主要是调用Process:start接口来创建一个新的进程,新的进程会导入android.app.ActivityThread类,并且执行它的main函数,这就是每一个应用程序都有一个ActivityThread实例来对应的原因。

3.5 应用进程初始化

我们来看Activitymain函数,这里绑定了主线程的Looper,并进入消息循环,大家应该知道,整个Android系统是消息驱动的,这也是为什么主线程默认绑定Looper的原因:

public final class ActivityThread {
 ...
 public static void main(String[] args) {
  ...
  Looper.prepareMainLooper();

  ActivityThread thread = new ActivityThread();
  thread.attach(false);

  ...

  Looper.loop();

  ...
 }

 private void attach(boolean system) {
  ...
  if (!system) {
   ...
   final IActivityManager mgr = ActivityManagerNative.getDefault();
   try {
    mgr.attachApplication(mAppThread);
   } catch (RemoteException ex) {
    // Ignore
   }
  } else {
   ...
  }
  ...
 }
 ...
}

attach函数最终调用了ActivityManagerService的远程接口ActivityManagerProxy的attachApplication函数,传入的参数是mAppThread,这是一个ApplicationThread类型的Binder对象,它的作用是AMS与应用进程进行进程间通信的。

3.6 在AMS中注册应用进程,启动启动栈顶页面

前面我们提到了AMS负责系统中四大组件的启动、切换、调度及应用进程的管理和调度等工作,通过上一个流程我们知道应用进程创建后通过Binder驱动与AMS产生交互,此时AMS则将应用进程创建后的信息进行了一次注册,如果拿Windows系统程序注册到的注册表来理解这个过程,可能会更形象一些。

mMainStack.topRunningActivityLocked(null)从堆栈顶端取出要启动的Activity,并在realStartActivityLockedhan函数中通过ApplicationThreadProxy调回App进程启动页面。

public final class ActivityStackSupervisor implements DisplayListener {
 ...
 final boolean realStartActivityLocked(ActivityRecord r,
   ProcessRecord app, boolean andResume, boolean checkConfig)
   throws RemoteException {
  ...
  r.app = app;
  ...

  try {
  ...
  app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
     System.identityHashCode(r), r.info, new Configuration(mService.mConfiguration),
     r.compat, r.launchedFromPackage, r.task.voiceInteractor, app.repProcState,
     r.icicle, r.persistentState, results, newIntents, !andResume,
     mService.isNextTransitionForward(), profilerInfo);
  ...
  } catch (RemoteException e) {
  ...
  }
  ...
 }
 ...
}

此时在App进程,我们可以看到,经过一些列的调用链最终调用至MainActivity:onCreate函数,之后会调用至onResume,而后会通知AMS该MainActivity已经处于resume状态。至此,整个启动流程告一段落。

4、总结

通过上述流程,相信大家可以有了一个基本的认知,这里我们忽略细节简化流程,单纯从进程角度来看下图: launch_app_sim

图上所画这里就不在赘述,Activity启动后至Resume状态,此时可交互。以上就是分析Android中应用启动流程的全部内容了,如何有疑问欢迎大家指正交流。

(0)

相关推荐

  • 解析android创建快捷方式会启动两个应用的问题

    接下来讲一种完美的方案.因为第一个界面是欢迎界面,所以快捷方式启动的界面也是欢迎界面,刚开始就怀疑快捷方式创建的代码有问题,但不知道问题出在哪儿.猜测可能会是快捷方式的错误导致系统创建两个应用,就也是同一个应用会有两个PID,结果在控制台查看打印的LOG,不同方式启动的LOG如下: 复制代码 代码如下: 09-22 09:39:11.929: INFO/ActivityManager(61): Starting: Intent { act=android.intent.action.MAIN c

  • Android 启动activity的4种方式及打开其他应用的activity的坑

    Android启动的四种方式分别为standard,singleTop,singleTask,singleInstence. standard是最常见的activity启动方式,也是默认的启动的方式.当启动一个activity的时候他将进入返回栈的栈顶.系统不会管栈内是否有相同的activity,方式像后入先出. singleTop方式是在活动启动的时候,系统先判定栈顶是否有相同的活动,如果没有则新建活动,否则将不新建活动.而是直接使用他. singleTask方式在活动启动的时候,系统先判定栈

  • 解析Android应用启动后自动创建桌面快捷方式的实现方法

    要不怎么说Android特别开放呢,在Android开发中,只要发送一个广播,就可以实现这种需求了. 废话不多说,以下是封装好的一段代码. 复制代码 代码如下: public class ShortcutUtil { public static void createShortCut(Activity act, int iconResId,              int appnameResId) { // com.android.launcher.permission.INSTALL_SH

  • Android笔记之:App应用之启动界面SplashActivity的使用

    当前比较成熟一点的应用基本上都会在进入应用之显示一个启动界面.这个启动界面或简单,或复杂,或简陋,或华丽,用意不同,风格也不同.下面来观摩几个流行的应用的启动界面. 1. 货比三家以腾讯qq,新浪weibo,UC浏览器,游戏神庙逃亡等7个应用为例,比比看:(我认为最精美的界面应该是qq2012,虽然只有一张图,基本的应用名称,版本,图标这些信息都有,但是看着舒服,觉得美.) 2. 元素启动界面的本意是以友好用户界面来掩饰后台缓冲加载,让用户用平和等待的心情进入正常应用界面.但是因为启动界面是放在

  • Android应用框架之应用启动过程详解

    在Android的应用框架中,ActivityManagerService是非常重要的一个组件,尽管名字叫做ActivityManagerService,但通过之前的博客介绍,我们知道,四大组件的创建都是有AMS来完成的,其实不仅是应用程序中的组件,连Android应用程序本身也是AMS负责启动的.AMS本身运行在一个独立的进程中,当系统决定要在一个新的进程中启动一个Activity或者Service时就会先启动这个进程.而AMS启动进程的过程是从startProcessLocked启动的. 1

  • Android使用Intent启动其他非系统应用程序的方法

    本文实例讲述了Android使用Intent启动其他非系统应用程序的方法.分享给大家供大家参考,具体如下: android应用程序内部通过Intent来实现Activity间的跳转.也知道通过Intent调用系统程序.但若想在应用程序A内开启应用程序B(前提是A.B均已安装),该如何去实现? 记录下实现过程. 在应用程序A内添加如下代码: Intent i = new Intent(); i.setClassName("com.example.a", "com.example

  • Android优化应用启动速度

    一.应用的启动 启动方式 通常来说,在安卓中应用的启动方式分为两种:冷启动和热启动. 1.冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式就是冷启动. 2.热启动:当启动应用时,后台已有该应用的进程(例:按back键.home键,应用虽然会退出,但是该应用的进程是依然会保留在后台,可进入任务列表查看),所以在已有进程的情况下,这种启动会从已有的进程中来启动应用,这个方式叫热启动. 特点 1.冷启动:冷启动因为系统会重新创建一个新的进程分配给它,

  • Android应用启动另外一个apk应用的方法

    本文实例讲述了Android应用启动另外一个apk应用的方法.分享给大家供大家参考,具体如下: 在开发的过程中,经常会遇到在一个应用中启动另外一个apk应用的情况 问题的核心点在于我们要拿到第三方apk的package名称跟class名称, 如:package名称是com.funcity.taxi.passenger,class名称是com.funcity.taxi.passenger.activity.LoadActivity. 从一个apk启动到另外一个apk,当然也是通过发送intent了

  • Android Intent启动别的应用实现方法

    我们知道Intent的应用,可以启动别一个Activity,那么是否可以启动别外的一个应用程序呢,答案是可以的. 1.首先我们新建一个Android应用,名为AnotherPro,此应用什么内容都没有,用于被另外一个程序打开. 2.新建一个工程用于打开上面的应用,程序界面如下 3.修改程序代码,在onCreate中添加如下代码 anotherPro = (Button) findViewById(R.id.startAnotherPro);calendar = (Button) findView

  • android应用实现开机自动启动方法

    原理:Android系统在开机的时候会发出一个广播.这样我们就可以接收这个广播,然后启动我们的应用.广播接收器必须在xml里面配置,因为xml里面配置的广播接收器  是不随着应用的退出而退出的. 广播接收器: package com.yangshidesign.boot; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; public c

随机推荐