Skip to content
Go back

ATL深度解析(2)Java Framework 重实现 — 用 GTK4 重写 Android API

Java Framework 重实现 — 用 GTK4 重写 Android API

这是 ATL(Android Translation Layer)系列技术分析的第 2 篇。上一篇介绍了整体架构,本篇将深入探讨 ATL 如何在没有 Android 系统基础设施的情况下,用 GTK4/GLib 重新实现 Android Java Framework 的核心 API。


1. 概念铺垫:Android Framework 是什么

在 AOSP(Android Open Source Project)中,frameworks/base/ 目录包含了 Android 应用开发者最熟悉的那些 API 类:Activity、View、Context、Intent、Canvas、TextView 等等,合计数千个 Java 类。这些类构成了 Android 应用的运行时环境——开发者调用 startActivity()setContentView()canvas.drawRect() 这些方法时,背后都是这些 Framework 类在工作。

然而在 AOSP 中,这些类并不是自包含的。它们严重依赖若干系统级基础设施:

ATL 的核心挑战在于:让 Android APK 中的 Java 字节码能够正常调用这些 API,但运行环境中没有上述任何一项基础设施。取而代之的是 Linux 桌面上的 GTK4、GLib、PipeWire 等组件。


2. ATL 的实现策略:三种文件来源

ATL 的 Java Framework 层位于 src/api-impl/ 目录下,其中的文件按来源可分为三类:

从 AOSP 复制并修改:约 329 个文件保留了 AOSP 的版权头。这些文件通常是数据结构类(如 SparseArrayArrayMap)或纯逻辑类(如 TextUtilsColor),它们在 AOSP 中本身就不依赖系统服务,因此可以较少修改地复用。

全新编写:约 455 个文件没有 AOSP 版权头,是 ATL 为了对接 GTK4 而从头编写的。这些文件涵盖了 Activity、View、Canvas、各种 Widget 以及系统服务等核心类。

自动生成R.javacom/android/internal/R.java 是由 aapt 工具根据 framework 资源自动生成的,合计约 147,606 行,定义了框架资源的 ID 常量。

在 JNI 侧,src/api-impl-jni/ 目录下有 82 个 C 文件,负责将 Java 方法调用桥接到 GTK4/GLib 的原生 API。

总计大约 784 个 Java 文件加 82 个 C 文件,共同组成了 ATL 的 Framework 重实现。

编译流程

编译通过 Meson 构建系统组织,核心步骤如下:

Java 源码 → javac → hax.jar → dx → api-impl.jar (DEX 格式)

构建脚本(src/api-impl/meson.build:786-813)中定义了编译参数:

# src/api-impl/meson.build
java_args = [
    '-bootclasspath', bootclasspath,
    '-source', '1.8', '-target', '1.8',
    '-encoding', 'UTF-8',
    '-Xlint:-deprecation',
    '-h', join_paths(dir_base, 'src/api-impl-jni/generated_headers')
]

编译时同时生成 JNI 头文件(-h 参数),供 C 端实现使用。如果系统安装了 ant,则使用 ant 构建 hax.jar;否则回退到 Meson 内置的 jar() 函数。


3. Activity 生命周期桥接

AOSP 怎么做

在标准 Android 中,Activity 生命周期由 AMS 通过 Binder IPC 远程驱动。当用户点击应用图标时,Launcher 通过 AMS 发起 startActivity,AMS 分配任务栈、管理进程,然后通过 ApplicationThread 这个 Binder 代理回调到应用进程,依次触发 onCreate -> onStart -> onResume 等生命周期方法。整个过程涉及至少两次跨进程 IPC 调用。

ATL 怎么做

ATL 中没有 AMS,也没有 Binder IPC。取而代之的是一个用 GLib 的 GList 实现的 Activity 回退栈,加上 JNI 回调构成的状态机。

核心数据结构定义在 C 侧(src/api-impl-jni/app/android_app_Activity.c):

// src/api-impl-jni/app/android_app_Activity.c
static GList *activity_backlog = NULL;   // Activity 回退栈
static jobject activity_current = NULL;  // 当前前台 Activity

当启动一个新 Activity 时,activity_start() 函数直接在当前进程内依次调用生命周期方法:

// src/api-impl-jni/app/android_app_Activity.c
void activity_start(JNIEnv *env, jobject activity_object)
{
    if (activity_current)
        activity_unfocus(env, activity_current);
    activity_current = NULL;
    (*env)->CallVoidMethod(env, activity_object,
                           handle_cache.activity.onCreate, NULL);
    // ... 异常检查省略 ...
    (*env)->CallVoidMethod(env, activity_object,
                           handle_cache.activity.onPostCreate, NULL);
    activity_backlog = g_list_prepend(activity_backlog,
                                      _REF(activity_object));
    activity_update_current(env);
}

activity_focus() 负责将一个 Activity 推到前台:

// src/api-impl-jni/app/android_app_Activity.c
static void activity_focus(JNIEnv *env, jobject activity)
{
    if (_GET_BOOL_FIELD(activity, "finishing"))
        return;
    (*env)->CallVoidMethod(env, activity,
                           handle_cache.activity.onStart);
    // ... finishing 检查 ...
    (*env)->CallVoidMethod(env, activity,
                           handle_cache.activity.onResume);
    (*env)->CallVoidMethod(env, activity,
                           handle_cache.activity.onPostResume);
    (*env)->CallVoidMethod(env, activity,
                           handle_cache.activity.onWindowFocusChanged, true);
}

Java 侧(src/api-impl/android/app/Activity.java)的 internalCreateActivity() 负责通过反射实例化 Activity 并配置好上下文:

// src/api-impl/android/app/Activity.java
public static Activity internalCreateActivity(String className,
        long native_window, Intent intent)
        throws ReflectiveOperationException {
    Class<? extends Activity> cls =
        Class.forName(className).asSubclass(Activity.class);
    Activity activity = cls.getConstructor().newInstance();
    activity.intent = intent;
    activity.attachBaseContext(
        new ContextImpl(r, pkg.applicationInfo, theme_res));
    activity.window = new Window(activity, activity);
    activity.window.set_native_window(native_window);
    return activity;
}

生命周期状态机

完整的状态转换可以用下面的流程概括:

activity_start():
  旧 Activity: onPause → onStop → onWindowFocusChanged(false)
  新 Activity: onCreate → onPostCreate → 压入 backlog 栈首
               → onStart → onResume → onPostResume
               → onWindowFocusChanged(true)

nativeFinish():
  从 backlog 移除 → onPause → onStop → onWindowFocusChanged(false)
  → onDestroy → 如果栈空则关闭 GTK 窗口

为什么这样做

ATL 运行的所有 Activity 实际上共享同一个 GTK 窗口和同一个操作系统进程。不需要 IPC 就能完成生命周期驱动,用一个简单的 GList 栈和直接的 JNI 方法调用就够了。这大幅简化了实现,同时保持了与 Android API 契约的兼容性。


4. View 体系:WrapperWidget 双层架构

AOSP 怎么做

Android 的 View 体系遵循经典的 measure -> layout -> draw 三阶段管线。每个 View 接收父容器给出的 MeasureSpec(尺寸约束),测量自身大小,然后在 layout 阶段确定位置,最后在 draw 阶段通过 Canvas 绘制到 Surface 上。整个过程由 ViewRootImpl 驱动,最终通过 SurfaceFlinger 合成到屏幕。

ATL 怎么做

ATL 使用一个自定义的 GTK Widget 类——WrapperWidget——作为每个 Android View 在 GTK 侧的代理。架构是双层的:

+--WrapperWidget (外层)---------------+
|  - 负责绘制(snapshot)和事件分发     |
|  - 持有 Java View 的弱引用 (jobj)   |
|  +--JavaWidget (内层, 如 GtkBox)--+ |
|  |  - 持有子 Widget               | |
|  |  - 使用 AndroidLayout 管理布局 | |
|  +--------------------------------+ |
+-------------------------------------+

WrapperWidget 是一个通过 GLib 类型系统注册的自定义 GTK Widget(src/api-impl-jni/widgets/WrapperWidget.c):

// src/api-impl-jni/widgets/WrapperWidget.c
G_DEFINE_TYPE(WrapperWidget, wrapper_widget, GTK_TYPE_WIDGET)

核心的 wrapper_widget_set_jobject() 函数负责将 Java View 对象与 GTK Widget 关联,并根据 Java 类是否重写了特定方法来按需注册事件控制器:

// src/api-impl-jni/widgets/WrapperWidget.c
void wrapper_widget_set_jobject(WrapperWidget *wrapper,
                                JNIEnv *env, jobject jobj)
{
    wrapper->jobj = _WEAK_REF(jobj);
    // 检测是否重写了 onDraw/draw/dispatchDraw 方法
    jmethodID draw_method = _METHOD(_CLASS(jobj), "draw",
        "(Landroid/graphics/Canvas;)V");
    jmethodID dispatch_draw_method = _METHOD(_CLASS(jobj),
        "dispatchDraw", "(Landroid/graphics/Canvas;)V");
    if (on_draw_method != handle_cache.view.onDraw
        || draw_method != handle_cache.view.draw
        || dispatch_draw_method != handle_cache.view.dispatchDraw) {
        wrapper->draw_method = draw_method;
        // 创建 GskCanvas 给 Java 侧使用
        wrapper->canvas = _REF((*env)->NewObject(env,
            canvas_class, canvas_constructor, 0));
    }
    // 检测是否重写了 onTouchEvent
    // 若是,注册 GtkEventControllerLegacy
    // 检测是否重写了 dispatchKeyEvent
    // 若是,注册 GtkEventControllerKey
    g_signal_connect(wrapper, "map", G_CALLBACK(map_cb),
        handle_cache.view.onAttachedToWindow);
    g_signal_connect(wrapper, "unmap", G_CALLBACK(unmap_cb),
        handle_cache.view.onDetachedFromWindow);
}

这个设计非常精巧:只有当 Java 子类实际重写了某个回调方法时,才会注册对应的 GTK 事件控制器。这避免了不必要的开销。

布局参数映射

Android 的布局参数需要映射到 GTK 的对应概念。核心映射逻辑在 android_view_View.cnative_setLayoutParams 中:

Android LayoutParamsGTK4 等价物
width = MATCH_PARENThexpand = TRUE, halign = FILL
height = MATCH_PARENTvexpand = TRUE, valign = FILL
wrap_content默认(不设置 expand)
gravity = CENTERhalign = CENTER, valign = CENTER, expand = TRUE
gravity = RIGHThalign = END
gravity = TOPvalign = START
weight > 0hexpand = TRUE, vexpand = TRUE

触摸事件管线

触摸事件从 GTK 事件系统流向 Java View 的完整路径:

GdkEvent (BUTTON_PRESS / TOUCH_BEGIN / MOTION_NOTIFY)
  → GtkEventControllerLegacy (on_pointer_event)
    → 坐标变换到 Widget 相对坐标
      → 构造 Java MotionEvent (JNI NewObject)
        → view_dispatch_motionevent()
          → View.dispatchTouchEvent() 或 View.onTouchEvent()

在 C 侧(src/api-impl-jni/views/android_view_View.c),view_dispatch_motionevent() 实现了完整的事件分发逻辑,包括对 onInterceptTouchEvent 的支持和事件取消机制:

// src/api-impl-jni/views/android_view_View.c
bool view_dispatch_motionevent(JNIEnv *env, WrapperWidget *wrapper,
    GtkPropagationPhase phase, jobject motion_event,
    gpointer event, int action)
{
    if (wrapper->custom_dispatch_touch) {
        ret = (*env)->CallBooleanMethod(env, this,
            handle_cache.view.dispatchTouchEvent, motion_event);
    } else if (phase == GTK_PHASE_CAPTURE
               && !wrapper->intercepting_touch) {
        wrapper->intercepting_touch = (*env)->CallBooleanMethod(
            env, this,
            handle_cache.view.onInterceptTouchEvent, motion_event);
        // ...
    }
    // ...
}

5. Canvas 绘制:即时模式 -> 保留模式

AOSP 怎么做

Android 的 Canvas API 是一个经典的即时模式(immediate-mode)绘图接口。开发者在 onDraw(Canvas) 中直接调用 drawRect()drawBitmap()drawText() 等方法,这些调用立即记录到底层的 Skia 绘图引擎中。

ATL 怎么做

GTK4 的渲染模型完全不同——它使用保留模式(retained-mode)的 GtkSnapshot(也叫 GskSnapshot)。开发者不是发出绘图命令,而是构建一棵渲染节点树,GTK 的渲染器随后遍历这棵树来绘制。

ATL 通过 android_atl_GskCanvas 这个 JNI 桥梁将 Android Canvas 的即时模式调用翻译为 GTK4 的 Snapshot 操作。核心映射关系如下:

Android Canvas 方法GTK4 Snapshot 操作
drawBitmap()gtk_snapshot_append_texture()
drawRect()gtk_snapshot_append_color()
drawPath() (fill)gtk_snapshot_append_fill()
drawPath() (stroke)gtk_snapshot_append_stroke()
drawText()gtk_snapshot_append_layout() (Pango)
drawRoundRect()gtk_snapshot_push_rounded_clip() + append_color/border
drawLine()坐标变换 + append_color()
save() / restore()gtk_snapshot_save() / gtk_snapshot_restore()
clipRect()gtk_snapshot_push_clip()
translate()gtk_snapshot_translate()
rotate()gtk_snapshot_rotate()
scale()gtk_snapshot_scale()
concat(Matrix)gtk_snapshot_transform_matrix()

drawRect 为例(src/api-impl-jni/graphics/android_atl_GskCanvas.c):

// src/api-impl-jni/graphics/android_atl_GskCanvas.c
JNIEXPORT void JNICALL
Java_android_atl_GskCanvas_native_1drawRect(JNIEnv *env,
    jclass this_class, jlong snapshot_ptr,
    jfloat left, jfloat top, jfloat right, jfloat bottom,
    jlong paint_ptr)
{
    GtkSnapshot *snapshot = GTK_SNAPSHOT(_PTR(snapshot_ptr));
    struct AndroidPaint *paint = _PTR(paint_ptr);
    graphene_rect_t bounds = GRAPHENE_RECT_INIT(
        left, top, right - left, bottom - top);
    gtk_snapshot_append_color(snapshot, &paint->color, &bounds);
}

drawText 的实现使用了 Pango 进行文本排版:

// src/api-impl-jni/graphics/android_atl_GskCanvas.c
JNIEXPORT void JNICALL
Java_android_atl_GskCanvas_native_1drawText(JNIEnv *env,
    jclass this_class, jlong snapshot_ptr, jstring text,
    jfloat x, jfloat y, jlong paint_ptr)
{
    struct AndroidPaint *paint = _PTR(paint_ptr);
    PangoLayout *layout = pango_layout_new(
        gtk_widget_get_pango_context(window));
    pango_layout_set_font_description(layout, paint->font);
    const char *str = (*env)->GetStringUTFChars(env, text, NULL);
    pango_layout_set_text(layout, str, -1);
    // ... 基线和对齐计算 ...
    gtk_snapshot_append_layout(snapshot, layout, &paint->color);
}

Snapshot 安全性问题

这里存在一个微妙但关键的问题:GTK4 严格禁止在 snapshot(绘制)过程中执行布局操作(如改变 label 文本、请求重新分配大小等)。但 Android 并没有这个限制——应用可以在 onDraw() 里随意调用 setText() 或者触发布局变更。

ATL 通过 tick callback 机制解决这个问题(src/api-impl-jni/util.c):

// src/api-impl-jni/util.c
extern int snapshot_in_progress;

void atl_safe_gtk_label_set_text(GtkLabel *label, const char *str)
{
    if (!snapshot_in_progress) {
        gtk_label_set_text(label, str);
    } else {
        gtk_widget_add_tick_callback(GTK_WIDGET(label),
            queue_set_text, (gpointer)strdup(str), NULL);
    }
}

void atl_ensure_widget_snapshotability(GtkWidget *widget)
{
    if (snapshot_in_progress) {
        GtkAllocation allocation;
        gtk_widget_get_allocation(widget, &allocation);
        gtk_widget_get_request_mode(widget);
        gtk_widget_size_allocate(widget, &allocation,
            gtk_widget_get_baseline(widget));
        GtkWidget *parent = gtk_widget_get_parent(widget);
        if (parent)
            atl_ensure_widget_snapshotability(parent);
    }
}

snapshot_in_progress 是一个全局计数器,在 WrapperWidgetsnapshot 回调中递增/递减。当检测到处于 snapshot 阶段时,布局变更操作会被推迟到下一个 tick callback 中执行。对于已经产生的脏标记,atl_ensure_widget_snapshotability() 会向上遍历 widget 树,逐层清理,防止 GTK 的内部断言失败。


6. Widget 实现模式:以 TextView 为例

AOSP 怎么做

AOSP 的 TextView 是一个约 12,000 行的庞然大物,内部实现了文本编辑、富文本渲染、输入法交互、自动链接检测等大量功能,底层使用 Skia 进行文字绘制。

ATL 怎么做

ATL 将 TextView 映射为一个 GtkLabel(包裹在 GtkBox 中以支持 compound drawables),通过 JNI 桥接 Java 属性设置到 GTK/Pango API。

Java 侧(src/api-impl/android/widget/TextView.java)的构造器从 XML 属性中提取配置:

// src/api-impl/android/widget/TextView.java
public TextView(Context context, AttributeSet attrs,
                int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
    TypedArray a = context.obtainStyledAttributes(attrs,
        com.android.internal.R.styleable.TextView, defStyleAttr, 0);
    try {
        if (a.hasValue(R.styleable.TextView_text))
            setText(a.getText(R.styleable.TextView_text));
        if (a.hasValue(R.styleable.TextView_textColor))
            setTextColor(a.getColorStateList(
                R.styleable.TextView_textColor));
        if (a.hasValue(R.styleable.TextView_textSize))
            setTextSize(a.getDimensionPixelSize(
                R.styleable.TextView_textSize, 10));
        // ... textStyle, textAllCaps ...
    } catch (Exception e) { e.printStackTrace(); }
    a.recycle();
}

C 侧的 native_constructorsrc/api-impl-jni/widgets/android_widget_TextView.c)创建 GTK 组件:

// src/api-impl-jni/widgets/android_widget_TextView.c
JNIEXPORT jlong JNICALL
Java_android_widget_TextView_native_1constructor(
    JNIEnv *env, jobject this, jobject context, jobject attrs)
{
    const char *text = attribute_set_get_string(
        env, attrs, "text", NULL);
    GtkWidget *wrapper = g_object_ref(wrapper_widget_new());
    GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0);
    GtkWidget *label = gtk_label_new(text);
    gtk_label_set_wrap(GTK_LABEL(label), TRUE);
    gtk_label_set_xalign(GTK_LABEL(label), 0.f);
    gtk_widget_set_hexpand(label, TRUE);
    gtk_box_append(GTK_BOX(box), label);
    wrapper_widget_set_child(WRAPPER_WIDGET(wrapper), box);
    wrapper_widget_set_jobject(WRAPPER_WIDGET(wrapper), env, this);
    PangoAttrList *pango_attrs = pango_attr_list_new();
    pango_attr_list_insert(pango_attrs,
        pango_attr_font_features_new("tnum"));
    gtk_label_set_attributes(GTK_LABEL(label), pango_attrs);
    return _INTPTR(box);
}

完整的初始化流程如下:

XML 布局解析
  → TypedArray 提取属性 (text, textColor, textSize...)
    → Java 构造器调用 native_constructor (JNI)
      → 创建 WrapperWidget + GtkBox + GtkLabel
      → PangoAttrList 配置字体特性 (tnum)
      → wrapper_widget_set_jobject() 关联 Java 对象
    → setText() → native_setText() → atl_safe_gtk_label_set_text()
    → setTextColor() → native_setTextColor() → GtkCssProvider
    → setTextSize() → PangoAttribute (size)

文字颜色设置通过动态创建 CSS 实现:

// src/api-impl-jni/widgets/android_widget_TextView.c
GtkCssProvider *css_provider = gtk_css_provider_new();
char *css_string = g_markup_printf_escaped(
    "* { color: #%06x%02x; }",
    color & 0xFFFFFF, (color >> 24) & 0xFF);
gtk_css_provider_load_from_string(css_provider, css_string);
gtk_style_context_add_provider(style_context,
    GTK_STYLE_PROVIDER(css_provider),
    GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);

其他 Widget(Button、ImageView、EditText、ListView 等)遵循相同的模式:Java 侧提取属性,JNI 侧创建对应的 GTK Widget 组合,通过 WrapperWidget 包装。


7. 系统服务:从 Binder IPC 到 new

AOSP 怎么做

在标准 Android 中,getSystemService("audio") 会通过 ServiceManager.getService() 获取一个 Binder 代理对象,这个代理对象与运行在 SystemServer 进程中的 AudioService 通信。几乎所有系统服务都是跨进程的,客户端持有的只是一个 Binder 代理。

App 进程                       SystemServer 进程
getSystemService("audio")
  → ServiceManager               AudioService
      .getService("audio")   ←→    (真正的实现)
        → Binder 代理

ATL 怎么做

ATL 没有 SystemServer 进程,所有”系统服务”都在应用进程内直接实例化。ContextImpl.java 中的 getSystemService() 就是一个朴素的 switch 语句(src/api-impl/android/app/ContextImpl.java):

// src/api-impl/android/app/ContextImpl.java
public Object getSystemService(String name) {
    switch (name) {
        case "window":       return new WindowManagerImpl();
        case "audio":        return new AudioManager();
        case "sensor":       return new SensorManager();
        case "connectivity": return new ConnectivityManager();
        case "vibrator":
            return (vibrator != null) ? vibrator
                                      : (vibrator = new Vibrator());
        case "clipboard":    return new ClipboardManager();
        case "notification": return new NotificationManager();
        case "input_method": return new InputMethodManager();
        case "layout_inflater": return layout_inflater;
        case "jobscheduler": return job_scheduler;
        // ... 总计 30+ 个 case ...
        default:
            Slog.e(TAG, "getSystemService: case >"
                        + name + "< is not implemented yet");
            return null;
    }
}

当前已实现的系统服务包括:

服务名ATL 实现类说明
audioAudioManager音频管理
sensorSensorManager传感器(部分桩实现)
connectivityConnectivityManager网络状态
clipboardClipboardManager剪贴板
vibratorVibrator震动反馈
notificationNotificationManager通知
input_methodInputMethodManager输入法
displayDisplayManager显示管理
windowWindowManagerImpl窗口管理
locationLocationManager位置(桩实现)
wifiWifiManagerWiFi(桩实现)
bluetoothBluetoothManager蓝牙(桩实现)
cameraCameraManager相机
storageStorageManager存储管理

为什么这样做

对于单应用场景,跨进程隔离毫无意义。直接 new 一个对象比建立 Binder 连接简单几个数量级,而且避免了序列化/反序列化的开销。部分服务(如 WifiManagerLocationManager)主要提供桩实现——返回合理的默认值以避免应用崩溃,但不提供真实功能。


8. Binder/Parcel/Intent 的处理

Binder:全部桩实现

Android 的 Binder 是整个系统的通信脊柱,但在 ATL 中完全是空壳。

Binder.javasrc/api-impl/android/os/Binder.java)仅 29 行:

// src/api-impl/android/os/Binder.java
public class Binder implements IBinder {
    public void attachInterface(IInterface owner, String descriptor) {}
    public static void flushPendingCommands() {}
    public static long clearCallingIdentity() { return 0; }
    public IInterface queryLocalInterface(String descriptor) {
        return null;
    }
    public boolean transact(int code, Parcel data, Parcel reply,
                            int flags) { return false; }
}

IBinder.java 仅 10 行,ServiceManager.java 仅 7 行——getService() 永远返回 null

Parcel:纯 Java 字节数组序列化

虽然 Binder 被桩掉了,但 Parcel 类被完整重写了。AOSP 中 Parcel 依赖 native 代码操作共享内存,ATL 中则用纯 Java 的 byte[]DataInputStream/DataOutputStream 实现(src/api-impl/android/os/Parcel.java)。

writeValue()/readValue() 使用类型标签来区分不同数据类型:

标签值类型标签值类型
0null8String
1Byte9byte[]
2Short10int[]
3Integer14Bundle
4Long15Parcelable
5Float18List
6Double19Map
7Boolean22Serializable
// src/api-impl/android/os/Parcel.java (writeValue 片段)
public void writeValue(Object value) {
    if (value == null)        { writeInt(0); }
    else if (value instanceof Integer) {
        writeInt(3);
        writeInt(((Integer)value).intValue());
    } else if (value instanceof String) {
        writeInt(8);
        writeString((String)value);
    } else if (value instanceof Parcelable) {
        writeInt(15);
        writeParcelable((Parcelable)value, 0);
    }
    // ... 共 23 种类型 ...
}

这使得应用中使用 Bundle(底层依赖 Parcel)传递数据的代码可以正常工作。

Intent 跨应用通信:GVariant + DBus

对于同一进程内的 Intent(如启动同一 APK 内的另一个 Activity),ATL 直接通过反射实例化目标 Activity。

但对于跨应用 Intent,ATL 使用 Freedesktop 的 DBus ActivateAction 接口。Intent 被序列化为类型为 (sssa{sv}s) 的 GVariant:

(action: s,        // Intent action 字符串
 className: s,     // 目标组件类名
 data: s,          // Intent data URI
 extras: a{sv},    // 额外数据(键值对)
 sender_dbus_name: s)  // 发送方 DBus 名称

详见 doc/DBusIntentApi.md

startService / bindService:进程内反射

Context.javasrc/api-impl/android/content/Context.java)中的 startService()bindService() 使用反射在当前进程内实例化 Service:

// src/api-impl/android/content/Context.java
public ComponentName startService(Intent intent) {
    // ... 解析 component ...
    new Handler(Looper.getMainLooper()).post(() -> {
        Class<? extends Service> cls =
            Class.forName(className).asSubclass(Service.class);
        if (!runningServices.containsKey(cls)) {
            Service service = cls.getConstructor().newInstance();
            service.attachBaseContext(new ContextImpl(...));
            service.onCreate();
            runningServices.put(cls, service);
        }
        runningServices.get(cls).onStartCommand(intent, 0, 0);
    });
    return component;
}

bindService() 遵循相同的模式——通过反射实例化 Service 后,直接调用 onBind() 获取 IBinder,然后同步调用 ServiceConnection.onServiceConnected()。唯一的区别是这些调用被 post 到主 Looper 中异步执行,以模拟 Android 中 Service 绑定的异步语义。


9. framework-res.apk 裁剪

AOSP 怎么做

frameworks/base/core/res/ 目录包含了 Android 框架的资源文件:主题定义(themes.xml)、系统 Drawable(图标、背景等)、布局文件、动画定义等。编译后生成 framework-res.apk,其中包含上万个资源。

ATL 怎么做

ATL 不需要 SystemUI 相关的资源(状态栏图标、导航栏等),但需要保留属于公开 API 的资源(如默认主题、标准颜色、系统 Drawable 等)。

res/framework-res/remove_unused_resources.py 实现了一个可达性分析算法:

# res/framework-res/remove_unused_resources.py
# 从 public.xml、themes、styles、colors、xml 资源及 Manifest 等入口开始
xml_files_new = glob.glob("res/values/public*.xml")
xml_files_new += glob.glob("res/values*/themes*.xml")
xml_files_new += glob.glob("res/values*/styles*.xml")
xml_files_new += glob.glob("res/values*/colors*.xml")
xml_files_new += glob.glob("res/xml*/*.xml")
xml_files_new += glob.glob("AndroidManifest.xml")
# 递归查找所有被引用的资源
while len(xml_files_new) > 0:
    xml_files += xml_files_new
    for f in xml_files_old:
        # 解析 @drawable/xxx, @layout/xxx 等引用
        for pattern in ['name="(.*?)"',
                        '"@drawable/(.*?)"', ...]:
            # 如果引用的资源文件存在,加入待扫描队列
# 删除所有未被引用的 layout 和 drawable
for f in glob.glob("res/drawable*/*"):
    if not os.path.basename(f).split(".")[0] in resources:
        os.remove(f)

算法从 public.xmlthemes*.xmlstyles*.xmlcolors*.xmlres/xml*/ 以及 AndroidManifest.xml(声明了所有公开资源 ID 和配置入口)出发,递归追踪 @drawable/@layout/@color/ 等资源引用,构建可达资源集合,然后删除所有不可达的 layout 和 drawable 文件。

效果:原始框架资源包含一万多个资源文件,裁剪后保留约 2,571 个文件,APK 从约 14MB 缩减到 3.9MB。

构建逻辑(res/framework-res/meson.build):

# res/framework-res/meson.build
aapt = find_program('aapt', required: false)
if aapt.found()
  aapt_command = [aapt, 'package', '-x', '-f',
    '--custom-package', 'com.android.internal',
    '-S', join_paths(dir_base, 'res/framework-res/res'),
    '-M', join_paths(dir_base, 'res/framework-res/AndroidManifest.xml'),
    '-J', join_paths(dir_base, 'src/api-impl/com/android/internal'),
    '-F', '@OUTPUT@']
else
  aapt_command = ['cp', join_paths(dir_base,
    'res/framework-res/framework-res.apk'), '@OUTPUT@']
endif

如果系统安装了 aapt,则从源码资源编译生成 APK 并同时生成 R.java;否则回退到使用预构建的 APK 文件。


总结

ATL 的 Java Framework 重实现展示了一个关键洞察:Android Framework 的大部分复杂性来自多进程架构的需要(Binder IPC、SystemServer、AMS 等),而在单进程翻译层场景下,这些复杂性可以被大幅简化。

核心技术决策包括:

  1. Activity 生命周期:GList 栈 + JNI 直接调用,替代 AMS + Binder IPC
  2. View 体系:WrapperWidget 双层架构,按需注册事件控制器
  3. Canvas 绘制:即时模式到保留模式的逐方法翻译,加上 snapshot 安全机制
  4. 系统服务switch + new 替代 ServiceManager + Binder 代理
  5. Binder/Parcel:Binder 桩掉,Parcel 用纯 Java 重写
  6. 框架资源:可达性分析裁剪,从 14MB 到 3.9MB

下一篇将探讨 ATL 如何重实现 Android NDK(C/C++ 原生)API:NDK API 重实现


Share this post on:

Previous Post
ATL深度解析(1)架构概览 — 在 Linux 上运行 Android 应用的外科手术式方案
Next Post
ATL深度解析(3)NDK API 重实现 — 让 Native 游戏跑在 Linux 上