移动端开发初探_1

移动端开发初探_1

因为一些巧合,现在一直接触 C++ 的我开始了移动端的学习与开发,这篇文档稍微记录一下自己的学习过程,但其实这不是一个笔记记录,算是一点小小感悟吧

前言

早就听闻 Flutter 的大名,没想到我也会有一天有理由去学习这样一个框架,其实在很久以前我也曾是从 Android 开发过来的,不过现在回头看一眼,Android 的结构还是太过复杂了,以当时我的基础,基本上完全不能够理解相关的内容

免责声明:仅由于我暂时没有鸿蒙开发所使用的设备(我穷喵),目前我不考虑鸿蒙的开发,下文就只考虑 Android 与 iOS

学习的 Bootstrap 时期

虽然学习移动端开发,最基本的任务就是去具体尝试一下 Android 和 iOS 上的原生开发,但我的学习路径有一些不同,我使用 Android 也有一段时间了,在大佬云集的许多群聊里听玩机,虽是当时无法理解,但潜移默化之中也知道 Android 里 activity 的概念,知道 apk 是一个压缩包。但同时我也曾玩过很多平台上的 Linux,同为 arm 的树莓派也可以使用 C/C++ 进行愉快的代码编写,那凭什么 Android 那么特殊,封装成了这样呢?

不管如何,让我先来写一些简单的 hello world 吧。在环境安装上纠结了一小段时间后( gradle 坏 ),我成功安装好了 Android Studio,跑通了一些基本的 example,我发现 Android 有个特点,首先,Android 原生开发中,你没有办法在项目里找到 main 函数,而是在 AndroidManifest.xml 里需要注册 MainActivity,如下

1
2
3
4
5
6
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

接着在软件打开的时候 MainActivity 的 onCreate() 函数就会被运行,也许这里就是函数的入口点?只能说如今看来,我感觉背后一定有我不知道的隐藏机制,比如 UI 程序常见的事件循环在哪,Android 的 API 库的实现原理是几何,听说 Android 的内核仍然是 Linux,那么 syscall 以及 C 库都怎么样了,OpenGL 库怎么作用的,函数的入口点如何…一口气可以说好多萦绕在我心头的问题,我当然可以选择先放下他们,直接接着学习原生开发,摆弄那些常见的组件,前端的知识,但如今的我终于知道我该以什么切入点查看这个问题了:1.安卓的系统架构,在过去的时间里面我们见过 Linux 发行版的系统架构,比如 wayland 的运行机制,kde 这样的桌面环境是怎么实现的,所以我们也要由此看一眼安卓的,2.app 具体运行是怎么被启动的,那可是 Java/Kotlin 啊,虚拟机又是在什么时候启动的呢?3.安卓本身系统是怎么被启动的

深入一点

我们都知道 Android 的内核就是 Linux,哪怕它对此进行了一些修改,也不会绕到哪去。那我们就先从安卓本身系统启动来看看,安卓的内核启动了一个类似 systemd 的东西,init 进程,这个是读取 init.rc 配置文件运行一些系统级进程(就是有 root 权限的进程),比如各种硬件服务:如显示服务、音频服务、Wi-Fi服务等,各种系统守护进程:包括 zygote 和 surfaceflinger 等。其中 zygote 进程相当重要,我们知道在 Linux 上,启动一个进程其实是使用 fork + exec syscall 的,而最开始执行 fork syscall 的,在使用 systemd 的情况下就是这个进程执行的,那么其实在这里,zygote 是被 init fork 出来的,但 zygote 翻译过来本意是受精卵,从寓意上看,他就是负责生成一些其它进程的,事实上也确实如此,在 app 启动时,正是它 fork 出新的进程,新进程是一个 JVM,它可以加载 apk 里的 dex 字节码并开始运行。
zygote 会 fork 出其它 Java 的系统级服务,比如 SystemServer,这个进程具有高权限,那么它具体起到什么作用呢,这里先按下不表,请看下文。
最终通过这样的方式 launcher 也就是桌面最终会被启动,整个系统便启动完成。

可以看到 zygote 负责了所有 Java 应用的启动,事实上它自己与之前的一些系统进程并非 Java,肯定是原生的代码,而它开始负责所有 Java 程序的启动,因为它直接复制自己这个虚拟机,从 app 的 AndroidManifest.xml 读取 MainActivity 这个类的然后启动,这里要说明的是启动的时候它直接加载 dex,这个只有一些分散的类实现的字节码。而真实的入口函数,则是被藏在系统的代码中,ActivityThread.main()

细究 app 的启动

当你安装一个 apk 文件的时候,其实做的事情并不多,首先 AndroidManifest.xml 会被系统解析变成系统全局的元数据,一般也会在桌面上留下图标,而当你点击这么一个图标的时候,launcher 通过 startActivity() 发送一个 Intent,包含目标应用的包名和入口 Activity 信息。ActivityManagerService (AMS) 会接受这样的信息,接着,如果应用本身并不存在,则会通过进程间通信(IPC) 告诉 zygote 让他生成应用,然后 ActivityThread.main() 执行:初始化主线程的 Looper 和 Handler(消息循环),调用 attach() 向 AMS 注册当前进程。启动 Looper.loop() 进入消息循环,等待 AMS 的后续指令。这验证了我们的猜想,确实事件循环和入口函数是存在的

Binder - 神秘的IPC

Android 有一个很神奇的东西,它有一个 IPC 的驱动,上文提到的 SystemServer 与此有关,用户的应用可以通过 syscall 调用 Binder,与 SystemServer 进行进程间通信,并通过反射机制获取到其中一些全局的类信息与功能,由于 SystemServer 本身具有比较高的权限,它作为系统应用就可以控制向其通信的用户应用的请求,以确保安全性
我个人学习到此处的时候觉得,这其实是把一些系统的库变成了一个运行时的东西,也就是通过客户端/服务器模型,让服务器占用高权限,强迫用户进程在低权限,以此保障安全性,而其本质,实质上和 wayland compositor 没什么区别。
所以 Android SDK 里的那些库,底层不出意外,除了一些 linux 自己的常用的 read/write 之类的 syscall,如比较上层的电话服务,应该是一个对 IPC 的封装罢了

iOS上呢

iOS 相对来说比较花哨, SwiftUI 我暂时还没有细看,但是 Objective-C 稍微看了一点,与 Android 不同的是,在书写 oc 程序时,有明显的 main 函数,以及用于启动事件循环的类 UIApplication,其机制与 Qt 十分相似,因此我特意去看了一眼 Qt 的实现,确实 Qt 本身的事件循环只是稍稍对 UIApplication 进行了一层包装,而不是像别的平台真要实现一个事件循环。
同时,iOS 本身也有一堆系统进程,出于闭源的原因,只能根据公开的材料猜测,其应当也如同这样,是通过进程间通信来调用通话服务,UI服务等一系列有点高层,但又很底层的东西
iOS 没有跑一个虚拟机,oc 会被直接编译成平台代码,所以我觉得 iOS 应该确实要比 Android 快,即使 Android 引入了 aot,应当也不能完全弥补速度差距

后话

感觉将有些功能变成高权限进程,通过进程间通信来调用这种动态的”库”,而不是直接一个 glibc 静态的库只调用 syscall 实现是常见的,毕竟后者什么都可以写,但是权限要求相当之高了。似乎这与微内核的想法也是相关的,总之还挺有意思,即使是简单的 hello world 背后也有如此复杂的事情,但所有平台的客户端具体实现又具有统一性,这也许就是软件与系统分层架构之间带来的魅力吧。