Skip to content
Go back

ATL深度解析(1)架构概览 — 在 Linux 上运行 Android 应用的外科手术式方案

Android Translation Layer 架构深度解析:在 Linux 上运行 Android 应用的外科手术式方案

引言

Android Translation Layer(ATL)是一个能在 Linux 桌面上原生运行 Android APK 的开源项目。它不是模拟器,不是容器,也不是虚拟机——而是对 Android Framework API 的重新实现。本文将从源码层面分析 ATL 的整体架构设计,对比它与 AOSP(Android Open Source Project)的根本差异,揭示”在 Linux 上跑 Android 应用”这件事的技术本质。

一、AOSP 架构回顾

在分析 ATL 之前,我们需要理解 AOSP 的标准架构。Android 系统从下到上分为五层:

┌─────────────────────────────────────┐
│           Android App (APK)          │ ← Java/Kotlin 字节码 + native .so
├─────────────────────────────────────┤
│        Android Framework API         │ ← Activity, View, Context, Intent...
├─────────────────────────────────────┤
│  System Services (AMS, WMS, PMS...) │ ← 运行在 SystemServer 进程中
│          通过 Binder IPC 访问        │
├─────────────────────────────────────┤
│       HAL (Hardware Abstraction)     │ ← 连接硬件驱动
├─────────────────────────────────────┤
│         Linux Kernel (定制版)        │ ← 含 Binder 驱动、Ashmem 等
└─────────────────────────────────────┘

在这个架构中,运行一个 App 需要几十个进程协同工作:

App 调用一个简单的 getSystemService("audio") 背后,实际经历的是:Java 代码 → Binder 代理 → 内核 Binder 驱动 → SystemServer 中的 AudioService → 返回结果。这套机制为 Android 的多进程安全模型提供了基础,但也意味着运行一个 App 需要启动整个 Android 系统。

二、ATL 的切割点:一刀切在哪里

ATL 的设计哲学在项目文档 doc/Architecture.md 中有明确描述:

We believe that the cleanest approach for supporting android apps on desktop Linux platforms is to make a chirurgical cut on the android platform stack as close to the apps themselves as possible, and sew on a new implementation of whatever we have cut off.

“外科手术式切割”——这个比喻精确地描述了 ATL 的技术方案。切割点选在 App 和 Framework API 之间

┌─────────────────────────────────────┐
│           Android App (APK)          │ ← 保持不变
├═════════════════════════════════════╡ ← 切割线
│   ATL: Android Framework API 重实现  │ ← 用 GTK4/GLib/EGL 等重写
│   ATL: NDK API 重实现 (libandroid)   │
│   ATL: 事件循环融合                   │
├─────────────────────────────────────┤
│   art_standalone: ART 虚拟机         │ ← 从 AOSP 提取
│   bionic_translation: Bionic 兼容层  │ ← ABI 翻译层
├─────────────────────────────────────┤
│   标准 Linux 桌面环境                 │ ← GTK4, Wayland/X11, PipeWire...
│   标准 Linux 内核(无需修改)         │
└─────────────────────────────────────┘

切割线以上:APK 的 Java 字节码和 native .so 文件完全不需要修改。它们调用的是标准 Android API(android.app.Activityandroid.view.ViewANativeWindow_* 等),ATL 保证这些 API 的存在和基本行为正确。

切割线以下:AOSP 中从 Framework 实现到 Linux 内核的所有层级都被替换。没有 SystemServer,没有 Binder 驱动,没有 SurfaceFlinger——取而代之的是 Linux 桌面已有的基础设施。

三、ATL 的五个子系统

切割后的下半部分由五个子系统组成,每个子系统解决一个特定问题:

3.1 Java Framework 重实现(api-impl.jar)

ATL 用 784 个 Java 文件 + 82 个 C(JNI)文件重写了 Android Framework 的核心 API。这些代码被编译为 api-impl.jar,在运行时作为 ART 虚拟机的 classpath 加载,优先于 APK 中的类。

构建流程定义在 meson.build:218-225

# hax.jar → api-impl.jar (DEX 格式)
custom_target('api-impl.jar',
    build_by_default: true, input: [hax_jar], output: ['api-impl.jar'],
    install: true,
    install_dir: get_option('libdir') / 'java/dex/android_translation_layer',
    command: ['dx', '--dex', '--incremental', '--output=...', hax_jar.full_path()])

784 个 Java 文件使用 javac -bootclasspath core-all_classes.jar 编译(src/api-impl/meson.build:786-791),-bootclasspath 指向 ART 提供的 Java 核心库,确保 java.lang.*java.util.* 等基础类可用。编译后通过 dx 工具转换为 ART 可执行的 DEX 格式。

3.2 NDK API 重实现(libandroid.so)

对于带有 native 代码的 App(游戏、媒体处理等),ATL 重新实现了 libandroid.so——Android NDK 的核心 C 库。构建定义在 meson.build:56-78

libandroid_so = shared_library('android', [
    'src/libandroid/asset_manager.c',
    'src/libandroid/bitmap.c',
    'src/libandroid/egl.c',
    'src/libandroid/input.c',
    'src/libandroid/looper.c',
    'src/libandroid/native_window.c',
    'src/libandroid/sensor.c',
    # ...
], dependencies: [dependency('gtk4'), dependency('jni'), dependency('vulkan'), ...])

这个库提供了 ANativeWindowALooperAAssetManager 等 NDK C 函数的 Linux 原生实现。

3.3 ART 独立化(art_standalone)

ART(Android Runtime)是 Android 的 Java 虚拟机,负责执行 DEX 字节码。art_standalone 项目将 ART 从 AOSP 中提取出来,使其能在标准 Linux 系统上独立编译和运行。ATL 通过 pkg-config 发现并链接 ART(meson.build:12):

libart_dep = dependency('art-standalone')

3.4 Bionic 兼容层(bionic_translation)

Android 使用 Bionic 作为其 C 标准库(替代 Linux 桌面上的 glibc/musl)。APK 中的 native .so 文件是用 Bionic 编译链接的,不能直接在 glibc 上运行——虽然函数名相同(都叫 stat()sigaction() 等),但 Bionic 和 glibc 的结构体内存布局、常量数值、线程模型等存在大量 ABI 级别的差异。

bionic_translation 通过两个核心库解决这个问题(meson.build:13-17):

libdl_bio_dep = [cc.find_library('dl_bio')]   # Bionic 动态链接器 shim
libc_bio_dep = [cc.find_library('c_bio')]     # Bionic libc ABI 翻译

libdl_bio(shim 动态链接器) 是整个兼容层的核心。当 ATL 加载 APK 中的 native .so 时,不能使用 Linux 系统自带的动态链接器(ld-linux.so),因为系统链接器会把 .so 中的 stat() 调用直接解析到 glibc 的 stat()——ABI 不兼容,程序会崩溃。libdl_bio 实现了一个完整的 ELF 动态链接器(2888 行 C 代码),它接管 .so 的加载过程,在符号解析阶段插入判断:如果某个函数存在 ABI 差异(如 stat),就重定向到翻译版本(bionic_stat);如果 ABI 兼容(如 memcpy),则直接使用 glibc 版本,零开销。

libc_bio(ABI 翻译层) 提供所有以 bionic_ 为前缀的翻译函数。例如 bionic_stat() 会先调用 glibc 的 stat() 获取结果,然后把 glibc 格式的 struct stat 逐字段转换为 Bionic 格式后返回给 APK。类似的翻译覆盖了 readdirsigactiongetaddrinfosysconfpthread_mutex_* 等几百个函数。

ATL 还通过路径覆盖机制(libc_bio_path_overrides.c)将 Android 的系统路径(如 /system/fonts/)透明重定向到 Linux 上的对应路径(使用 fontconfig 查找字体)。

3.5 事件循环融合

Android 的 UI 线程运行一个 Handler/Looper/MessageQueue 消息循环,而 GTK4 运行一个 GMainLoop 事件循环。两者必须在同一个主线程上协作。ATL 将 Android 的 MessageQueue 作为一个 GSource 挂载到 GLib 主循环中,实现了两个事件系统的无缝融合。

四、关键架构差异对比

维度AOSPATL
进程模型几十个进程(Zygote, SystemServer, App, SurfaceFlinger…)1 个进程
IPC 机制Binder 内核驱动进程内直接调用 + DBus(跨应用)
窗口系统SurfaceFlinger(独立进程 + HWC HAL)宿主的 Wayland/X11 合成器
渲染管线hwui → Skia → SurfaceFlingerGTK4 (GSK/Pango) + EGL(用于 GL 应用的三缓冲 ATLSurface)
音频audioserver → AudioFlinger → HALALSA(通过 pipewire-alsa 兼容 PipeWire)
系统服务SystemServer 中 100+ 服务,Binder 访问进程内 new AudioManager()
内核要求Android 定制内核(Binder, Ashmem, ION)标准 Linux 内核
App 隔离独立进程 + SELinux + UID 隔离无隔离,同一进程
启动时间数十秒(完整系统启动)几秒(创建 VM + 启动 Activity)

五、启动流程对比

AOSP 启动一个 App

Linux Kernel 启动
  → init 进程
    → Zygote 进程(预加载 ~4000 个 Java 类)
      → fork SystemServer(启动 100+ 系统服务)
        → AMS.startActivity()
          → Zygote.fork() 创建 App 进程
            → ActivityThread.main()
              → 通过 Binder 连接 AMS
                → AMS 驱动 Activity 生命周期

ATL 启动一个 App

整个启动流程在 src/main-executable/main.cmain() 函数中(第 792-841 行):

int main(int argc, char **argv)
{
    pregrow_stack();       // 1. 预分配 6MB 栈(musl 兼容)
    init__r_debug();       // 2. 初始化调试器支持

    // 3. 创建一个普通的 GTK 应用程序
    app = gtk_application_new("com.example.demo_application", ...);
    g_signal_connect(app, "open", G_CALLBACK(open), callback_data);

    // 4. 进入 GTK 主循环
    status = g_application_run(app, argc, argv);
    return status;
}

当用户传入 APK 文件时,open() 回调触发(第 299 行起),执行以下步骤:

步骤 1:创建 ART 虚拟机create_vm(),第 83-143 行):

JNIEnv *create_vm(char *api_impl_jar, char *apk_classpath, ...) {
    JavaVMInitArgs args = { .version = JNI_VERSION_1_6, .nOptions = 3 };
    // classpath = api-impl.jar:app.apk:framework-res.apk
    options[1].optionString = construct_classpath("-Djava.class.path=",
        (char *[]){api_impl_jar, apk_classpath, framework_res_apk, test_runner_jar}, 4);
    int ret = JNI_CreateJavaVM(&jvm, &env, &args);
}

步骤 2:加载翻译层 native 库(第 418-431 行):

// 通过 ART 的 loadLibrary 加载,绑定到正确的 ClassLoader
jmethodID loadLibrary_with_classloader = _METHOD(java_runtime_class,
    "loadLibrary", "(Ljava/lang/String;Ljava/lang/ClassLoader;)V");
(*env)->CallVoidMethod(env, java_runtime, loadLibrary_with_classloader,
    _JSTRING("translation_layer_main"), class_loader);

在加载 native 库和启动 Android 生命周期之间,还有几个关键的准备步骤:

步骤 3:引导 Android 生命周期(第 472-520 行):

// 创建 Application 对象(触发 AndroidManifest.xml 解析)
application_object = (*env)->CallStaticObjectMethod(env,
    handle_cache.context.class,
    _STATIC_METHOD(handle_cache.context.class, "createApplication", "(J)..."),
    window);
(*env)->CallVoidMethod(env, application_object, on_create_method);

// 创建主 Activity(通过反射)
activity_object = (*env)->CallStaticObjectMethod(env,
    handle_cache.activity.class,
    _STATIC_METHOD(handle_cache.activity.class, "createMainActivity", "..."),
    _JSTRING(activity_class), _INTPTR(window), _JSTRING(uri));

对比总结:AOSP 需要先启动整个系统(Zygote、SystemServer、各种守护进程),然后通过 Binder IPC 驱动 Activity 生命周期;ATL 直接创建一个 ART VM 实例,通过 JNI 反射调用 Java 代码,在一个普通的 GTK 窗口中完成一切。

六、运行时 Classpath 的四层优先级

理解 ATL 的类加载顺序对于理解整个系统至关重要。main.c:114 中组装的 classpath:

options[1].optionString = construct_classpath("-Djava.class.path=",
    (char *[]){api_impl_jar, apk_classpath, framework_res_apk, test_runner_jar}, 4);

加上 ART 自带的 bootclasspath(meson.build:19-21):

bootclasspath = bootclasspath_dir / 'core-all_classes.jar' + ':'
              + bootclasspath_dir / 'core-junit_classes.jar' + ':'
              + bootclasspath_dir / 'junit-runner_classes.jar'

形成四层优先级:

第 1 层 (最高): bootclasspath (ART 提供)
  → java.lang.*, java.util.*, javax.crypto.* ...
  → 来源:art_standalone 编译的 AOSP libcore,未修改

第 2 层: api-impl.jar (ATL 实现)
  → android.app.*, android.view.*, android.os.* ...
  → 来源:ATL 重写的 Android Framework API

第 3 层: app.apk (用户应用)
  → com.example.app.* ...
  → 来源:用户安装的 APK

第 4 层: framework-res.apk (AOSP 裁剪)
  → 主题、样式、属性定义、系统图标等资源
  → 来源:从 AOSP frameworks/base/core/res/ 裁剪

当 APK 中的代码调用 new Activity() 时,ART 在 classpath 中查找 android.app.Activity——第 1 层没有(这不是 Java 核心类),第 2 层的 api-impl.jar 中有 ATL 实现的版本,于是使用它。这就是 ATL 如何在不修改 APK 的情况下”接管”所有 Android Framework 调用。

七、与其他方案的对比

方案技术路线与 ATL 的区别
Android 模拟器 (QEMU)虚拟化完整 Android 系统ATL 不运行 Android 系统,只运行 App
Anbox/WaydroidLinux 容器中运行 Android 系统镜像需要 Android 内核模块和完整系统镜像
Wine (Windows)重新实现 Windows APIATL 的方法论与 Wine 最相似
Darling (macOS)重新实现 macOS API同属”API 翻译层”思路

ATL 与 Wine 最为相似:都是在不运行原始操作系统的情况下,通过重新实现 API 让目标平台的应用程序直接运行。区别在于 Wine 面对的是已有 30 年历史的 Win32 API,而 ATL 面对的是仍在快速演进的 Android API。

八、总结

ATL 的架构设计可以用一句话概括:在 App 和 Framework 之间切一刀,用 Linux 桌面原生技术重新实现下半部分

这个设计选择意味着:

代价是需要逐个重新实现 Android Framework API——这是一个永远做不完的工作,但也让每个实现的 API 都能充分利用 Linux 桌面的原生能力。在后续的 5 篇文章中,我们将深入每个子系统的源码细节。


下一篇:Java Framework 重实现 — 用 GTK4 重写 Android API


Share this post on:

Previous Post
Colima运行linux容器(六):macOS 容器方案横评
Next Post
ATL深度解析(2)Java Framework 重实现 — 用 GTK4 重写 Android API