Skip to content

android: Let debug builds of host-driven Activities load a pushed library without reinstalling - #39

Open
Bombatomica64 wants to merge 1 commit into
longbridge:mainfrom
Bombatomica64:upstream/android-hot-loader
Open

Bombatomica64 wants to merge 1 commit into
longbridge:mainfrom
Bombatomica64:upstream/android-hot-loader

Conversation

@Bombatomica64

Copy link
Copy Markdown

Implements the loader discussed in #34.

A Java helper, GpuiLibraryLoader, for host-driven Activities (android::host) to call instead of System.loadLibrary. In a debuggable build, if a dev tool has pushed a newer build of the Rust library to the app's data directory, it loads that one, so a Rust change no longer needs Gradle and an APK install. This PR adds no Rust code and no dependency. Nothing calls the loader unless a host chooses to.

Change

  • New example/android/gradle/app/src/main/java/dev/gpui/mobile/GpuiLibraryLoader.java (47 lines), next to the other Java helpers that hosts copy. GpuiLibraryLoader.load(context, name) loads lib<name>.so once per process:
    • if the app is debuggable (ApplicationInfo.FLAG_DEBUGGABLE) and files/dev/lib<name>.so exists, it calls System.load on that file and logs loading dev library …;
    • otherwise it calls System.loadLibrary(name), as today.
  • The host.rs module doc ("What the host has to do") now starts with loading the library, and mentions the loader.

Development only

The loader is for development loops only. Store policy forbids downloading executable code, so this is not, and cannot become, an over-the-air update path (see the comment on #34). Two things keep it out of release builds:

  • At run time, a build that is not debuggable always loads the packaged library. The files/dev path is not even checked.
  • On the device, a dev tool can only write into the app's private files/ with adb shell run-as <package>, and run-as only works for debuggable packages.

NativeActivity hosts can't use it: NativeActivity loads the packaged library itself. The example app is a NativeActivity, so it doesn't call the loader. The class is there for host-driven apps to copy, like GpuiInputActivity.

How it is used

cargo-gpui uses it for cargo gpui reload. The tool rebuilds the library, pushes a zstd delta, copies it read-only into files/dev/ with run-as (Android 14 requires dynamically loaded code to be read-only), and restarts the app on the screen it was on. On a OnePlus CPH2581 (Android 16) reached over an adb tunnel, a reload with a 1.4 MB delta takes about 8.3 s, with no install.

The same tool's hot patching (cargo gpui dev) doesn't need this loader. It is built on Subsecond from Dioxus, and its patch builder adapts parts of the Dioxus CLI (dx serve --hotpatch), credited in that repo. A reload with this loader covers the changes a patch can't make, such as a struct's layout or the dependencies.

Patches applied on the phone

From the hot-patching prototype: a title, a colour and a layout changed while the app was running, and the click counter kept its value (3).

Testing

  • The loader has been used by the host-driven Activity of Bombatomica64/Gpui-android with cargo gpui reload on the OnePlus CPH2581 (Android 16). After a reload the log shows loading dev library …. Without a pushed file the packaged library loads.
  • On this branch: javac --release 17 against android-34/android.jar with -Xlint:all gives no warnings. cargo fmt --all -- --check, cargo check --target aarch64-linux-android --all-targets (crate and example/), cargo clippy --target aarch64-linux-android (no new warnings) and cargo test --lib all pass. Only host.rs docs change on the Rust side.

One change from the fork version: the "already loaded" flag is now set after a successful load, not before.

Open questions

#34 also asked where the tool should live (stay separate, move under longbridge, or come into gpui-mobile). This PR only adds the loader, which is useful whatever you decide. If you would rather keep the loader in cargo-gpui as well, that works too.

🤖 Generated with Claude Code

Loads lib<name>.so once per process. In a debuggable build it prefers
files/dev/lib<name>.so in the app's data directory, so a dev tool can push
a new build of the Rust code and restart the app without reinstalling.
Release builds always load the packaged library. Opt-in: nothing calls it
unless the host does. NativeActivity hosts can't use it (NativeActivity
loads the packaged library itself).

Implements the loader discussed in longbridge#34.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant