Android - HaxeFoundation/hashlink GitHub Wiki
To run on Android, you have to compile the HL runtime, supporting libraries, and your project's HL/C code (since there's no HL/JIT for ARM as of moment of writing) as a native library using NDK. You then need the Android Java activity to load and call your native code.
You will need to build and link the HL runtime and supporting libraries to your android project. CMakeLists.txt
in the hashlink repo is useful: you can basically copy-paste configurations into CMakeLists.txt
of your android project and get libhl and friends built.
Here are a couple repositories that may act as a useful starting point, though some are quite outdated:
- https://github.com/HeapsIO/heaps-android
- https://github.com/qkdreyer/heaps-android
- https://github.com/altef/heaps-android
If you are using SDL, the SDL docs contain some useful information about including your native library build in your gradle project.
The minSdkVersion requirement you set for your app should be at least 21, or 24 if your app uses uv.hdll
. Setting this to a lower version will result in compilation errors.
The JNI_OnLoad
function located in sys_android.c
(called on library load) imposes some requirements:
- The activity class name should be whatever is defined with
HL_ANDROID_ACTIVITY
(by default:org/haxe/HashLinkActivity
) - The activity class must have a
public static Context getContext()
method that returns the application context.
You can skip to the section below if you are using SDL.
On Android, the app's entry point is a Java program. This means that instead of a standard main
entry-point, you need to provide a special exported function conforming to JNI standards, and then call main
routines from it. Here's what worked for me for a simple HL/C hello world:
package com.example.native_activity;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
public class MainActivity extends Activity {
private static MainActivity instance;
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
instance = this;
System.loadLibrary("hl"); // hl run-time (if build separately), other libs loaded similarly
System.loadLibrary("native-activity"); // application code (linked against the libs above)
startHL();
}
public static Context getContext() {
return instance.getApplicationContext();
}
public native void startHL(); // see comment below
}
the startHL
function is just something like this, which must be built into your native binary:
#include <jni.h>
extern int main(int argc, char *argv[]); // assuming that haxe->hl/c entry point is included (which includes hlc_main.c which includes the main function)
JNIEXPORT jstring JNICALL Java_com_example_native_1activity_MainActivity_startHL(JNIEnv* env, jobject thiz) {
main(0, NULL);
}
SDL provides some java entry point files (as well as an entire gradle template) which are recommended when your app uses SDL.
Copy the android-project
directory from the SDL version you are using into your project directory.
Following the instructions provided in the SDL docs, you can add your own activity class (the one set by HL_ANDROID_ACTIVITY
) that inherits from SDLActivity
. Once again, we must implement the getContext
method. We also override the getLibraries
and getMainFunction
methods from SDLActivity
. See the comments in the code sample for details:
package com.example.native_activity;
import org.libsdl.app.SDLActivity;
import android.content.Context;
import android.os.Bundle;
public class MainActivity extends SDLActivity {
private static MainActivity instance;
public static Context getContext() {
return instance.getApplicationContext();
}
@Override
protected void onCreate(Bundle state) {
instance = this;
super.onCreate(state);
}
@Override
protected String[] getLibraries() {
return new String[]{
/* Any other dynamic libraries required */
"SDL2",
/* Finally, the library containing your entry point */
"native-activity"
};
}
/* This method returns the main function that will be loaded from our native binary. */
@Override
protected String getMainFunction() {
return "main";
}
}
Note:
It is important to link SDL dynamically (as a .so
file) because both libhl and libSDL2 contain their own JNI_OnLoad
functions, and only one can exist per shared object. Otherwise you may run into:
ld.lld: error: duplicate symbol: JNI_OnLoad
Note: these may be outdated.
TurboJPEG (part of fmt
) required setting SIZEOF_SIZE_T
define to compile:
include(CheckTypeSize)
check_type_size(size_t SIZEOF_SIZE_T)
target_compile_definitions(fmt
PRIVATE
SIZEOF_SIZE_T=${SIZEOF_SIZE_T}
)
Also, the bundled libpng lacks ARM NEON optimization source file, so another define is required: PNG_ARM_NEON_OPT=0
Some work has been started on calling Java from HashLink (through JNI): https://github.com/nadako/hljni
Some comments from @rtissera on the topic: https://github.com/HaxeFoundation/hashlink/issues/109#issuecomment-363055631