大家有没有想过一个问题:在浏览器里打开某个网页,网页上有一个按钮点击可以唤起App。 这样的效果是怎么实现的呢?浏览器是一个app;为什么一个app可以调起其他app的页面? 说到跨app的页面调用,大家是不是能够想到一个机制:Activity的隐式调用? 当我们有需要调起其他app的页面时,使用的API就是隐式调用。 比如我们有一个app声明了这样的Activity: 其他App想启动上边这个Activity如下的调用就好: 我们没有主动声明Activity的class,那么系统是怎么为我们找到对应的Activity的呢?其实这里和正常的Activity启动流程是一样的,无非是if / else的实现不同而已。 接下来咱们就回顾一下Activity的启动流程,为了避免陷入细节,这里只展开和大家相对“耳熟能详”的类和调用栈,以串流程为主。 首先我们必须明确一点:无论是隐式启动还是显示启动;无论是启动App内Activity还是启动App外的Activity都是跨进程的。比如我们上述的例子,一个App想要启动另一个App的页面,至少涉及3个进程。 注意没有root的手机,是看不到系统孵化出来的进程的。也就是我们常见的为什么有些代码打不上断点。 追过 很快我们能看到一个比较常见类的调用: 注意 此外 我们点击去 它并不是一个.java文件,而是aidl文件。 所以 public class ActivityManagerService extends IActivityManager.Stub 所以执行到这就转到了系统进程(system_process进程)。省略一下代码细节,看一下调用栈: 从上述debug截图,看一看到此时已经拿到了我们的目标Activitiy的相关信息。 这里简化一些获取目标类的源码,直接引入结论: 这里类相当于解析手机内的所有apk,将其信息构造到内存之中,比如下图这样: 小tips:手机目录中/data/system/packages.xml,可以看到所有apk的path、进程名、权限等信息。 打开目标Activity的前提是:目标Activity的进程启动了。所以第一次想要打开目标Activity,就意味着要启动进程。 启动进程的代码就在启动Activity的方法中: 这里便引入了另一个另一个大名鼎鼎的类: 进程启动后,继续回到目标Activity的启动流程。这里依旧是一系列的system_process进行的转来转去,然后 注意看,在这里再次通过 这里所谓的CallBack的实现是 此时就转到了 上述截图的调用链中暗含了Activity实例化的过程(反射): Helo站内的回流页就是一个标准的,浏览器唤起另一个App的实例。 html标签有一个属性href,比如: 我们常见的一种用法: 因为这个是前端的标签,依托于浏览器及其内核的实现,跳转到一个网页似乎很“顺其自然”(不然叫什么浏览器)。 当然这里和android交互的流程基本一致:用隐式调用的方式,声明需要启动的Activity;然后 前端页面: android声明: 浏览器能够加载scheme,可以理解为是浏览器内核做了封装。那么想要让android也能支持对scheme的解析,难道是由浏览器内核做处理吗? 很明显不可能,做了一套移动端的操作系统,然后让浏览器过来实现,是不是有点杀人诛心。 所以大概率能猜测出来,应该是手机中的浏览器app做的处理。我们就基于这个猜想去看一看浏览器.apk的实现。 基于上边说的 然后jadx反编译一下Browser.apk中WebView相关的源码: 我们可以发现对href的处理来自于隐式跳转,所以一切就和上边的流程串了起来。 结尾留个小问题:如果我自己写个WebView去load一个前端页面,能隐式跳转吗?疑问的开端
隐式启动原理
<activity android:name=".OtherActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="mdove"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>val intent = Intent()
intent.action = "mdove"
startActivity(intent)跨进程
startActivity()
的同学,应该很熟悉下边这个调用流程,跟进几个方法之后就发现进到了一个叫做ActivityTread
的类里边。ActivityTread
这个类有什么特点?有main函数,就是我们的主线程。Instrumentation
:// Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options);
// 省略
}mInstrumentation#execStartActivity()
有一个标黄的入参,它是ActivityThread
中的内部类ApplicationThread
。ApplicationThread
这个类有什么特点,它实现了IApplicationThread.Stub
,也就是aidl的“跨进程调用的客户端回调”。mInstrumentation#execStartActivity()
中又会看到一个大名鼎鼎的调用:public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) {
// 省略...
ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
return null;
}getService()
会看到一个标红的IActivityManager
的类。ActivityManager.getService()
本质返回的是“进程的服务端”接口实例,也就是:ActivityManagerService
PackageManagerService
启动新进程
resumeTopActivityInnerLocked
->startProcessLocked
。ZygoteInit
。这里简单来说会通过ZygoteInit
来进行App进程启动的。ApplicationThread
IApplicationThread
进入目标进程。IApplicationThread
回调到ActivityThread
。class H extends Handler {
// 省略
public void handleMessage(Message msg) {
switch (msg.what) {
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
// 省略
break;
case RELAUNCH_ACTIVITY:
handleRelaunchActivityLocally((IBinder) msg.obj);
break;
}
// 省略...
}
}
// 执行Callback
public void execute(ClientTransaction transaction) {
final IBinder token = transaction.getActivityToken();
executeCallbacks(transaction);
}LaunchActivityItem#execute()
,对应的实现:public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
mProfilerInfo, client);
client.handleLaunchActivity(r, pendingActions, null);
}ActivityThread#handleLaunchActivity()
,也就转到了咱们日常的生命周期里边,调用栈如下:public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className, @Nullable Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Activity) cl.loadClass(className).newInstance();
}浏览器启动原理
交互流程
<a href="...">
。<a href="``https://www.baidu.com``">
。也就是点击之后跳转到百度。<a href="">
传入对应的协议(scheme)即可。比如:<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<a href="mdove1://haha"> 启动OtherActivity </a>
</body><activity
android:name=".OtherActivity"
android:screenOrientation="portrait">
<intent-filter>
<data
android:host="haha"
android:scheme="mdove1" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>推理实现
浏览器实现
/data/system/packages.xml
文件,我们可以pull出来浏览器的.apk。尾声
评论