前言
对于我们做电商APP的开发人员来说,Activity是四大组件中使用最多的,当然也是最复杂的。那Activity是如何启动的?点击手机上APP图标又发生了什么呢?今天我们就一一解析。
APP是怎么启动的?
当我们点击某个APP图标的时候,这里假设是电商APP-永辉生活。这个APP的首页(或启动页)就展示在我们面前了。看似简单的操作,其实里面是Activity和AMS反反复复的通信过程。
这里补充一下,我们点击的应用图标的界面其实是一个Activity,官方用语Launcher。launcher其实就是一个app,它的作用用来显示和管理手机上其他App。目前市场上有很多第三方的launcher应用,比如“小米桌面”、“91桌面”等等(因此我们可以对桌面图标进行分组,调用网络请求等)。
首先回顾一下我们是怎么定义默认启动的Activity的呢?
<activity
android:name=".guide.NullDefaultActivity"
android:configChanges="keyboardHidden|orientation"
android:screenOrientation="landscape"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
这些配置信息在APP安装的时候(或者Android系统启动的时候),通过PMS(PackageManagerService)从永辉生活APK的配置文件(AndroidManifest)文件中读取到的。所以点击永辉生活应用图标就启动了永辉生活。
启动APP就这么简单吗?
并非如此。上述只是一个最简单描述。我们通过Launcher启动一个新的Activity是启动一个新的进程,Launcher和永辉生活 APP属于不同的进程。它们之间的通信是通过Binder完成通信的。我们的AMS登场了。
我们这里就以永辉生活APP为例,整体流程分为7个阶段。
- Launcher通知AMS,要启动永辉生活APP,并告诉要启动哪个页面(这里我们是引导页)
- AMS通知Launcher,“好了,我知道了,谢谢你了,你可以休息了”。同时把要启动的引导页信息记录下来。
- Launcher进入paused状态,同时通知AMS,“那我休息了,接下来我就不管了”。
1-3阶段上面是Launcher和AMS之间的通信。
- AMS检查永辉生活APP是否已经启动了。是的话,就唤起永辉生活APP,如果不是的话,就开启一个新的进程。AMS在新进程创建一个ActivityThread对象,启动其中的main函数。
- 永辉生活APP启动后,通知AMS,“我启动好了”。
- AMS翻出第二阶段的保存的值,告诉永辉生活APP,启动哪个页面。
- 永辉生活APP启动引导页,创建Context并与引导页Activity相关联。然后调用引导页onCreate相关联。
4-7阶段,永辉生活APP与AMS相互通信。
这边涉及到一堆类。
- ActivityThread: 应用的启动入口类,当应用启动,会首先执行其main方法,开启主线程消息循环机制。
- ApplicationThread: ActivityThread的内部类,主要与系统进程AMS通信,从而对应用进程的具体Activity操作进行管理。
- Instrumentation: ActivityThread的属性变量,主要辅助ActivityThread类调用Activity的生命周期相关方法。
- ActivityManagerService(AMS): Activity管理系统服务类,主要是对所有的Activity进行管理。
- ActivityStack,Activity在AMS的栈管理,用来记录已经启动的Activity的先后关系,状态信息等。通过ActivityStack决定是否需要启动新的进程。
- ActivityRecord,ActivityStack的管理对象,每个Activity在AMS对应一个ActivityRecord,来记录Activity的状态以及其他的管理信息。其实就是服务器端的Activity对象的映像。
- TaskRecord,AMS抽象出来的一个“任务”的概念,是记录ActivityRecord的栈,一个“Task”包含若干个ActivityRecord。AMS用TaskRecord确保Activity启动和退出的顺序。如果你清楚Activity的4种launchMode,那么对这个概念应该不陌生。
第1阶段:Launcher通知AMS
Launcher通知AMS的流程图:
image/**
* Launches the intent referred by the clicked shortcut.
*
* @param v The view representing the clicked shortcut.
*/
public void onClick(View v) {
Object tag = v.getTag();
if (tag instanceof ApplicationInfo) {
// Open shortcut
final Intent intent = ((ApplicationInfo) tag).intent;
startActivitySafely(intent);
} else if (tag instanceof FolderInfo) {
handleFolderClick((FolderInfo) tag);
}
}
void startActivitySafely(Intent intent) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
try {
startActivity(intent);
} catch (ActivityNotFoundException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
} catch (SecurityException e) {
Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
e(LOG_TAG, "Launcher does not have the permission to launch " + intent +
". Make sure to create a MAIN intent-filter for the corresponding activity " +
"or use the exported attribute for this activity.", e);
}
}
继续跟进:
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
startActivityForResult(intent, -1);
}
}
后调用的还是这个重载方法:
public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mParent == null) {
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);
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
可以发现这里调用了mInstrumentation.execStartActivity方法,这里先简单介绍一下Instrumentation对象,他是Android系统中应用程序端操作Activity的具体操作类,这里的操作段是相对于ActivityManagerService服务端来说的。也就是说当我们在执行对Activity的具体操作时,比如回调生命周期的各个方法都是借助于Instrumentation类来实现的。
我们发现有个mMainThread变量,这是一个ActivityThread变量,就是主线程,也就是UI线程,它是APP启动时候创建的,它代表了一个应用程序。
它里面有个main函数,这个是由Android系统底层提供的。源码如下:
public static void main(String[] args) {
//省略部分代码
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
上面代码传递了两个很重要的参数:
- 通过ActivityThread的getApplicationThread方法取到一个Binder对象,它的类型为ApplicationThread,它代表着Launcher所在的App进程。
- mToken,这也是个Binder对象,它代表了Launcher这个Activity,这里也通过Instrumentation传给AMS,AMS一查电话簿,就知道是谁向AMS发起请求了。
这两个参数是伏笔,传递给AMS,以后AMS想反过来通知Launcher,就能通过这两个参数,找到Launcher。
Instrumentation的execStartActivity方法:
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
....................................................
try {
....................................
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
AMN的getDefault方法
- ServiceManager是一个容器类。
- AMN的getDefault方法返回类型为IActivityManager,而不是AMP。IActivityManager是一个实现了IInterface的接口,里面定义了四大组件所有的生命周期。
AMP的startActivity方法
看到这里,你会发现AMP的startActivity方法,和AIDL的Proxy方法,是一模一样的,写入数据到另一个进程,也就是AMS,然后等待AMS返回结果。
第2阶段 AMS处理Laucner传递过来的信息
- 首先Binder,也就是AMN/AMP,和AMS通信,肯定每次是做不同的事情,就比如说这次Launcher要启动斗鱼App,那么会发送类型为START_ACTIVITY——TRANSACTION的请求给AMS,同时会告诉AMS要启动哪个Activity。
- AMS说,好,我知道了,然后它会干一件很有趣的事情,就是检查永辉生活App中的Manifest文件,是否存在要启动的Activity。如果不存在,就抛出Activity not found的错误,各位做App的同学对这个异常应该再熟悉不过了,经常写了个Activity而忘记在Manifest中声明了,就报这个错,就是因为AMS在这里做检查。不管是新启动一个App的首页,还是在App内部跳转到另一个Activity,都会做这个检查。
- 接下来AMS会通知Launcher
Binder的双方进行通信是平等的,谁发消息,谁就是Client,接收的一方就是Server。Client这边会调用Server的代理对象。
那么当AMS想给Launcher发消息,又该怎么办呢?前面不是把Launcher以及它所在的进程给传过来了吗?它在AMS这边保存为一个ActivityRecord对象,这个对象里面有一个ApplicationThreadProxy,单单从名字看就出卖了它,这就是一个Binder代理对象。它的Binder真身,也就是ApplicationThread。
站在AIDL的角度,来画这张图,是这样的:
image第3阶段:Launcher开始休息,然后通知AMS,“我休息了”
先来感受一下:
image // we use token to identify this activity without having to send the
// activity itself back to the activity manager. (matters more with ipc)
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
r.intent = intent;
r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
r.persistentState = persistentState;
r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;
r.startsNotResumed = notResumed;
r.isForward = isForward;
r.profilerInfo = profilerInfo;
r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
AMS给Activity发送的所有消息,以及给其它三大组件发送的所有消息,都从H这里经过。插件化就可以在这里动手脚。
H对于PAUSE_ACTIVITY消息的处理,如上面的代码,是调用ActivityThread的handlePauseActivity方法。这个方法干两件事:
- ActivityThread里面有一个mActivities集合,保存当前App也就是Launcher中所有打开的Activity,把它找出来,让它休眠。
- 通过AMP通知AMS,我真的休眠了。
你可能会找不到H和APT这两个类文件,那是因为它们都是ActivityThread的内嵌类。
第4阶段:AMS启动新的进程
AMS接下来要启动永辉生活App的首页,因为永辉生活App不在后台进程中,所以要启动一个新的进程。这里调用的是Process.start方法,并且指定了ActivityThread的main函数为入口函数。
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);
第5阶段:新的进程启动,以ActivityThread的main函数为入口
启动新进程,其实就是启动一个新的APP。
image在启动新进程的时候,为这个进程创建ActivityThread对象,这就是我们耳熟能详的主线程(UI线程)。
创建好UI线程后,立刻进入ActivityThread的main函数,接下来要做2件具有重大意义的事情:
1)创建一个主线程Looper,也就是MainLooper。看见没,MainLooper就是在这里创建的。
2)创建Application。记住,Application是在这里生成的。
第6阶段 AMS告诉新App启动哪个Activity
还记得第1阶段,Launcher发送给AMS要启动斗鱼App的哪个Activity吗?这个信息被AMS存下来了。
那么在第6阶段,AMS从过去的记录中翻出来要启动哪个Activity,然后通过ActivityThreadProxy告诉App。
第7阶段 启动斗鱼首页Activity
万事俱备只欠东风,收场的来了
imagehandleLaunchActivity方法都做哪些事呢?
- 通过Instrumentation的newActivity方法,创建出来要启动的Activity实例。
- 为这个Activity创建一个上下文Context对象,并与Activity进行关联。
- 通过Instrumentation的callActivityOnCreate方法,执行Activity的onCreate方法,从而启动Activity。看到这里是不是很熟悉很亲切?
至此,App启动完毕。这个流程是经过了很多次握手, App和ASM,频繁的向对方发送消息,而发送消息的机制,是建立在Binder的基础之上的。
参考:
- Android系统源代码情景分析(第三版) 罗升阳著
- Android插件化开发指能 包建强著
- 项目源码代码分析
- launcher 启动流程解析
- 部分图片和内容直接借用网上搜集过来的,如若侵权,请联系作者
声明:此为原创,转载请联系作者
作者:微信公众号添加公众号-遛狗的程序员 ,或者可以扫描以下二维码关注相关技术文章。
qrcode_for_gh_1ba0785324d6_430.jpg当然喜爱技术,乐于分享的你也可以可以添加作者微信号:
WXCD.jpeg
网友评论