文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

浏览器为什么能唤起App的Activity?

2024-12-02 21:18

关注

疑问的开端

大家有没有想过一个问题:在浏览器里打开某个网页,网页上有一个按钮点击可以唤起App。

这样的效果是怎么实现的呢?浏览器是一个app;为什么一个app可以调起其他app的页面?

说到跨app的页面调用,大家是不是能够想到一个机制:Activity的隐式调用?

隐式启动原理

当我们有需要调起其他app的页面时,使用的API就是隐式调用。

比如我们有一个app声明了这样的Activity:

  1. name=".OtherActivity" 
  2.     android:screenOrientation="portrait"
  3.      
  4.         <action android:name="mdove"/> 
  5.         name="android.intent.category.DEFAULT"/> 
  6.      
  7.  

其他App想启动上边这个Activity如下的调用就好:

  1. val intent = Intent() 
  2. intent.action = "mdove" 
  3. startActivity(intent) 

我们没有主动声明Activity的class,那么系统是怎么为我们找到对应的Activity的呢?其实这里和正常的Activity启动流程是一样的,无非是if / else的实现不同而已。

接下来咱们就回顾一下Activity的启动流程,为了避免陷入细节,这里只展开和大家相对“耳熟能详”的类和调用栈,以串流程为主。

跨进程

首先我们必须明确一点:无论是隐式启动还是显示启动;无论是启动App内Activity还是启动App外的Activity都是跨进程的。比如我们上述的例子,一个App想要启动另一个App的页面,至少涉及3个进程。

注意没有root的手机,是看不到系统孵化出来的进程的。也就是我们常见的为什么有些代码打不上断点。

追过startActivity()的同学,应该很熟悉下边这个调用流程,跟进几个方法之后就发现进到了一个叫做ActivityTread的类里边。

ActivityTread这个类有什么特点?有main函数,就是我们的主线程。

很快我们能看到一个比较常见类的调用:Instrumentation:

  1. // Activity.java 
  2. public void startActivityForResult(@RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) { 
  3.     mInstrumentation.execStartActivity(this, mMainThread.getApplicationThread(), mToken, this, intent, requestCode, options); 
  4.     // 省略 

注意mInstrumentation#execStartActivity()有一个标黄的入参,它是ActivityThread中的内部类ApplicationThread。

ApplicationThread这个类有什么特点,它实现了IApplicationThread.Stub,也就是aidl的“跨进程调用的客户端回调”。

此外mInstrumentation#execStartActivity()中又会看到一个大名鼎鼎的调用:

  1. public ActivityResult execStartActivity(Context who, IBinder contextThread, IBinder token, Activity target, Intent intent, int requestCode, Bundle options) { 
  2.     // 省略... 
  3.     ActivityManager.getService() 
  4.         .startActivity(whoThread, who.getBasePackageName(), intent, 
  5.                 intent.resolveTypeIfNeeded(who.getContentResolver()), 
  6.                 token, target != null ? target.mEmbeddedID : null
  7.                 requestCode, 0, null, options); 
  8.     return null

我们点击去getService()会看到一个标红的IActivityManager的类。

它并不是一个.java文件,而是aidl文件。

所以ActivityManager.getService()本质返回的是“进程的服务端”接口实例,也就是:

ActivityManagerService

public class ActivityManagerService extends IActivityManager.Stub

所以执行到这就转到了系统进程(system_process进程)。省略一下代码细节,看一下调用栈:

从上述debug截图,看一看到此时已经拿到了我们的目标Activitiy的相关信息。

这里简化一些获取目标类的源码,直接引入结论:

PackageManagerService

这里类相当于解析手机内的所有apk,将其信息构造到内存之中,比如下图这样:

小tips:手机目录中/data/system/packages.xml,可以看到所有apk的path、进程名、权限等信息。

启动新进程

打开目标Activity的前提是:目标Activity的进程启动了。所以第一次想要打开目标Activity,就意味着要启动进程。

启动进程的代码就在启动Activity的方法中:

resumeTopActivityInnerLocked->startProcessLocked。

这里便引入了另一个另一个大名鼎鼎的类:ZygoteInit。这里简单来说会通过ZygoteInit来进行App进程启动的。

ApplicationThread

进程启动后,继续回到目标Activity的启动流程。这里依旧是一系列的system_process进行的转来转去,然后IApplicationThread进入目标进程。

注意看,在这里再次通过IApplicationThread回调到ActivityThread。

  1. class H extends Handler { 
  2.     // 省略 
  3.     public void handleMessage(Message msg) { 
  4.         switch (msg.what) { 
  5.             case EXECUTE_TRANSACTION: 
  6.                 final ClientTransaction transaction = (ClientTransaction) msg.obj; 
  7.                 mTransactionExecutor.execute(transaction); 
  8.                 // 省略 
  9.                 break; 
  10.             case RELAUNCH_ACTIVITY: 
  11.                 handleRelaunchActivityLocally((IBinder) msg.obj); 
  12.                 break; 
  13.         } 
  14.         // 省略... 
  15.     } 
  16.  
  17. // 执行Callback 
  18. public void execute(ClientTransaction transaction) { 
  19.     final IBinder token = transaction.getActivityToken(); 
  20.     executeCallbacks(transaction); 

这里所谓的CallBack的实现是LaunchActivityItem#execute(),对应的实现:

  1. public void execute(ClientTransactionHandler client, IBinder token, 
  2.         PendingTransactionActions pendingActions) { 
  3.     ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo, 
  4.             mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState, 
  5.             mPendingResults, mPendingNewIntents, mIsForward, 
  6.             mProfilerInfo, client); 
  7.     client.handleLaunchActivity(r, pendingActions, null); 

此时就转到了ActivityThread#handleLaunchActivity(),也就转到了咱们日常的生命周期里边,调用栈如下:

上述截图的调用链中暗含了Activity实例化的过程(反射):

  1. public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className, @Nullable Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException { 
  2.  
  3.     return (Activity) cl.loadClass(className).newInstance(); 
  4.  

浏览器启动原理

Helo站内的回流页就是一个标准的,浏览器唤起另一个App的实例。

交互流程

html标签有一个属性href,比如:。

我们常见的一种用法:。也就是点击之后跳转到百度。

因为这个是前端的标签,依托于浏览器及其内核的实现,跳转到一个网页似乎很“顺其自然”(不然叫什么浏览器)。

当然这里和android交互的流程基本一致:用隐式调用的方式,声明需要启动的Activity;然后传入对应的协议(scheme)即可。比如:

前端页面:

  1.  
  2.   "UTF-8"
  3.   name="viewport" content="width=device-width, initial-scale=1.0"
  4.  
  5.  
  6. "mdove1://haha"> 启动OtherActivity  
  7.  

android声明:

  1.     android:name=".OtherActivity" 
  2.     android:screenOrientation="portrait"
  3.      
  4.         
  5.             android:host="haha" 
  6.             android:scheme="mdove1" /> 
  7.         <action android:name="android.intent.action.VIEW" /> 
  8.         name="android.intent.category.BROWSABLE" /> 
  9.         name="android.intent.category.DEFAULT" /> 
  10.      
  11.  

推理实现

浏览器能够加载scheme,可以理解为是浏览器内核做了封装。那么想要让android也能支持对scheme的解析,难道是由浏览器内核做处理吗?

很明显不可能,做了一套移动端的操作系统,然后让浏览器过来实现,是不是有点杀人诛心。

所以大概率能猜测出来,应该是手机中的浏览器app做的处理。我们就基于这个猜想去看一看浏览器.apk的实现。

浏览器实现

基于上边说的/data/system/packages.xml文件,我们可以pull出来浏览器的.apk。

然后jadx反编译一下Browser.apk中WebView相关的源码:

我们可以发现对href的处理来自于隐式跳转,所以一切就和上边的流程串了起来。

尾声

 

结尾留个小问题:如果我自己写个WebView去load一个前端页面,能隐式跳转吗?

 

来源:咸鱼正翻身内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯