C+
Packages · View as Markdown

android_view

Android UI from C+, the sibling of appkit (macOS) and uikit (iOS). It is layered on jni and validated end to end: the staticlib from cpc build --target android-arm64, linked into libapp.so by the NDK, renders a real View tree on a Pixel emulator. Android is a cross-compile target — see Targets.

Modules mirror the other GUI packages:

  • runtime — JNI environment helpers, method calls, UTF strings, global refs.
  • activity — a borrowed Activity wrapper and setContentView.
  • viewView and LinearLayout.
  • controlsTextView and Button.
  • listener — self-contained click handling (see below); imported separately, since it obligates the app to export the click hook.

Host contract

Android still needs a JVM-side entry component: a tiny Activity that loads the native library and calls one static native method, which C+ exports.

public final class MainActivity extends android.app.Activity {
    static { System.loadLibrary("app"); }
    private static native android.view.View nativeCreateView(MainActivity self);
    @Override protected void onCreate(android.os.Bundle state) {
        super.onCreate(state);
        setContentView(nativeCreateView(this));
    }
}

The C+ side converts the native env pointer, builds the tree, and returns the root as a raw jobject (the JVM parent retains it):

import "android_view/android_view" as av;
import "jni/jni" as jni;

pub extern fn Java_com_example_MainActivity_nativeCreateView(
    envp: *jni::JNIEnv, cls: jni::jobject, activity_obj: jni::jobject,
) -> jni::jobject {
    let env: av::Env = av::from_native(envp);
    let act: av::Activity = av::Activity::from_borrowed(env, activity_obj);

    let mut root: av::LinearLayout = av::LinearLayout::new(env, act.as_context());
    root.set_orientation(av::orientation_vertical());

    let title: av::TextView = av::TextView::new(env, act.as_context());
    title.set_text(#str_ptr("Hello from C+\0"));
    root.add_view(title.as_view_obj());

    return root.into_raw();
}

Clicks: the DEX-adapter trick

Java interfaces can't be implemented from native code, so a click needs an adapter class. android_view/listener ships that adapter inside the package as a pre-compiled DEX (a 976-byte blob embedded with #include_bytes); on first use it loads the DEX with InMemoryDexClassLoader (API 26+) and binds its native method via RegisterNatives. The host app ships no Java beyond MainActivity. This is what made Android event handling in pure C+ possible.

import "android_view/listener" as listener;

listener::set_on_click(env, button.as_view_obj(), 1 as i64);   // route by token

// every adapter click lands here; the token says which control:
pub extern fn cplus_on_click(envp: *jni::JNIEnv, token: i64, view: jni::jobject) { ... }

Building

cpc build --target android-arm64 stops at the staticlib; the NDK's clang links libapp.so (with --whole-archive, so the JNI exports survive), which a Gradle project places under lib/arm64-v8a/ in the APK. The listener module needs minSdk 26+; everything else runs on 24+. The android_hello recipe in the compiler repo takes this all the way to a signed, emulator-validated APK.