Instant code reload in Android



Introduction

Here I am presenting one possible way to do “on the fly code reloading” of JNI code. The whole code is C/C++ compiled in 2 shared libraries and realoded on demand without restaring the application.

This example uses a game tiny “game” where you just walk around space. This is just a placeholder idea borrowed from Casey Muratori’s Handmade Hero youtube series.

The build process used in this project is described in: Build android apk from command line without gradle post.

Disclaimer: This solution requires the phone to be rooted. It is still possible to do it without root by writing a tiny server that would listen for new changes and reload uploaded shared object but I was too lazy to do that since I already had a rooted phone and was most easy thing to do at that time.

Method

The idea behind the method is pretty simple. There is a java wrapper to load code from the apk which loads an intermediary shared code which in turn loads real game.

JNI loading comparisons

In the activity that I want to load the code I just statically load my intermediary libandroid_handmade.so shared library and just one exported function drawStuff.

static {
    System.loadLibrary("android_handmade");
}

public native void drawStuff(Bitmap bitmap, int width, int height, MyState state);

Inside my activity I create a canvas and bitmap that will be fully drawn on the canvas. It does not have to be this way to instant compilation to work but I did it this way because I am doing software rendered game and for that I need a place draw and a memory area where to “put pixels”.

For this example I am filling the bitmap with pixel and then drawing them on the canvas about 30 times a second. This is all done on a separate thread where java call drawStuff fuction from JNI and passes to it a bitmap - an memory area where to put data. One pixels are put in place that thread just blits those pixels on the canvas. Nothing compilicated here.

There is also a ‘state’ class used to store and simplify passing around data between activity and game code. It is needed just to pack all required data.

class MyState {
    public float touchX;
    public float touchY;

    public int canvasWidth;
    public int canvasHeight;
    public double dtForFrame;

    public String packageDirectory;

    public AssetManager assetManager;
}

Here I pass just some utility data and touch positions.

After the code is passed to libandroid_handmade it unpacks all the required data from the activity, prepares required data for the game, creates some virtual buttons and other not so important tasks.

The main magic happens in dlopen. Everytime this function is called it closes the game code, loads the code again and manually maps required functions into correct places. In this example there is only one function called GameUpdateAndRender.

if(GameLibrary) {
    dlclose(GameLibrary);
}
GameLibrary = dlopen("libhandmade.so", RTLD_LAZY);
if(GameLibrary) {
    GameUpdateAndRender = (game_update_and_render *)dlsym(GameLibrary, "GameUpdateAndRender");
}

I understand that this way of loading is inefficent and just a bad practice. But here I am doing it this way just to stress test the system and see how far I can push it. But if you look at the video you will see that a very underpowered phone can unload a library, load it again, map functions and then do CPU rendering and other stuff. And this is all in 1/30th of a second. I think it is a good test to see even old phones can do quite a bit and when normal apps cannot handle smooth working with the help of GPU and not being handicaped by loading and loading libraries it puzzles me.

So the correct way of realding this in debug build should have been to store and check timestamp of the last change to the library and if it changed do the above code. But it is a good ‘homework’ task for the reader.

The other purpose of this intermedeiate library is to store state and when we reload real game it will pass it old state and it will continues as if nothing happened. When the state is passed to libhandmade.so library it will do it game stuff, do physysics, collision, rendering and create and image that is then rendered inside the activity on a canvas. And then the process starts over, does it 30 times a second.

Reloading

The real magic happens when we push new code to the device. First we need to recompile these two libraries. This task is easy. Next we need to somehow deliver to the application so that it can reload it.

If we go the usual route of building the apk and pushing to the device then first off all it will be pretty slow (just below 10 seconds) and then we will lose all previous state which will destroy all the magic.

The trick is to get the location where current loaded .so files are located and just replace them at runtime. First let’s find that location as it will be slightly different each time we install the app. For we need to run this command:

adb shell dumpsys package com.hereket.handmade_hero | grep legacyNativeLibraryDir | cut -d "=" -f2 | tr -d '\r'

When I just run it on my machine it gave me this location: /data/app/com.hereket.handmade_hero-_nc-h7D8I7uAnTYNp3WFUw. It will contain base.apk file and lib, oat directories. We are interested in ’lib’ directory. Inside it will have arm directory which will contain our shared libraries that we created for the app. Next we just need to push our newly created libhandmade.so game code to that folder and overwrite old one and we are done. (This step requries root)

adb push \
    __build/apk/lib/armeabi-v7a/libhandmade.so \
    /data/app/com.hereket.handmade_hero-_nc-h7D8I7uAnTYNp3WFUw/arm

Result

Conclusion

You can check out resulting code on github: https://github.com/hereket/handmade_instant_reload

Here in this article I tried to show how to achieve instant code reloading using just java and C/C++. This type of realding is faster that flutter and react native and shows that this is possible to do even when you are ahead of time compilation into machine code.

Here for this project I am not using standart android build system system of gradle with cmake but rather a custom script which does pretty much the same thing but heavily stripped down. I did this just to test different ideas and gradle with cmake were standing on my way most of the time. But you could replicate this process with those tools if needed.

I am not advocating that this is the superior way to build as this is just and exploration into the places that are usually left behind. I used code and ideas from Casey Muratori. Go buy his course - it is awesome.