[Android] Wrote a native activity acting as a bootstrap

A current limitation prevents one library from depending on shared libraries.
As we have legal issues here (LGPL wants us to use shared libs of OpenAL-Soft and libsndfile), we're forced to use this homemade native activity which will manually load our shared libraries.
This commit is contained in:
Jonathan De Wachter 2013-09-22 19:15:13 +02:00
parent 60894d1c1a
commit cc0d982f72
6 changed files with 197 additions and 12 deletions

View File

@ -88,6 +88,11 @@ if(ANDROID)
# we install libs in a subdirectory named after the ABI (lib/mips/*.so)
set(LIB_SUFFIX "/${ANDROID_ABI}")
# this is a workaround to compile sfml-activity without stlport_shared as dependency
# we save the original compilation command line to restore it later in Macro.cmake
set(CMAKE_CXX_CREATE_SHARED_LIBRARY_WITH_STLPORT ${CMAKE_CXX_CREATE_SHARED_LIBRARY})
set(CMAKE_CXX_CREATE_SHARED_LIBRARY_WITHOUT_STLPORT "<CMAKE_CXX_COMPILER> <CMAKE_SHARED_LIBRARY_CXX_FLAGS> <LANGUAGE_COMPILE_FLAGS> <LINK_FLAGS> <CMAKE_SHARED_LIBRARY_CREATE_CXX_FLAGS> <SONAME_FLAG><TARGET_SONAME> -o <TARGET> <OBJECTS> <LINK_LIBRARIES>")
endif()
# define SFML_STATIC if the build type is not set to 'shared'

View File

@ -97,6 +97,18 @@ macro(sfml_add_library target)
set_target_properties(${target} PROPERTIES XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES)
endif()
# sfml-activity library is our bootstrap activity and must not depend on stlport_shared
# (otherwise Android will fail to load it)
if (ANDROID)
if (${target} MATCHES "sfml-activity")
set_target_properties(${target} PROPERTIES COMPILE_FLAGS -fpermissive)
set_target_properties(${target} PROPERTIES LINK_FLAGS "-landroid -llog")
set(CMAKE_CXX_CREATE_SHARED_LIBRARY ${CMAKE_CXX_CREATE_SHARED_LIBRARY_WITHOUT_STLPORT})
else()
set(CMAKE_CXX_CREATE_SHARED_LIBRARY ${CMAKE_CXX_CREATE_SHARED_LIBRARY_WITH_STLPORT})
endif()
endif()
# link the target to its external dependencies
if(THIS_EXTERNAL_LIBS)
target_link_libraries(${target} ${THIS_EXTERNAL_LIBS})

View File

@ -16,32 +16,24 @@ include $(CLEAR_VARS)
LOCAL_MODULE := ogg
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libogg.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
TARGET_ARCH_ABI := armeabi armeabi-v7a x86
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := flac
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libFLAC.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
TARGET_ARCH_ABI := armeabi armeabi-v7a x86
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := vorbis
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libvorbis.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
TARGET_ARCH_ABI := armeabi armeabi-v7a x86
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := vorbisenc
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libvorbisenc.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
TARGET_ARCH_ABI := armeabi armeabi-v7a x86
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
@ -49,8 +41,6 @@ LOCAL_MODULE := sndfile
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libsndfile.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES := ogg flac vorbis vorbisenc
TARGET_ARCH_ABI := armeabi armeabi-v7a x86
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
@ -58,6 +48,4 @@ LOCAL_MODULE := openal
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libopenal.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES := ogg flac vorbis vorbisenc
TARGET_ARCH_ABI := armeabi armeabi-v7a x86
include $(PREBUILT_SHARED_LIBRARY)

View File

@ -55,4 +55,11 @@ LOCAL_SHARED_LIBRARIES := sfml-system sfml-window
include $(PREBUILT_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := sfml-activity
LOCAL_SRC_FILES := lib/$(TARGET_ARCH_ABI)/libsfml-activity.so
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/include
include $(PREBUILT_SHARED_LIBRARY)
$(call import-module,sfml/extlibs)

View File

@ -28,3 +28,10 @@ set_target_properties(sfml-main PROPERTIES FOLDER "SFML")
# setup the install rule
install(TARGETS sfml-main ARCHIVE DESTINATION lib${LIB_SUFFIX} COMPONENT devel)
# because of a current limitation on Android (which prevents one library
# from depending on shared libraries), we need a boostrap activity which
# will load our shared libraries manually
if(ANDROID)
sfml_add_library(sfml-activity SOURCES ${PROJECT_SOURCE_DIR}/src/SFML/Main/SFMLActivity.cpp)
endif()

View File

@ -0,0 +1,166 @@
////////////////////////////////////////////////////////////
//
// SFML - Simple and Fast Multimedia Library
// Copyright (C) 2013 Jonathan De Wachter (dewachter.jonathan@gmail.com)
//
// This software is provided 'as-is', without any express or implied warranty.
// In no event will the authors be held liable for any damages arising from the use of this software.
//
// Permission is granted to anyone to use this software for any purpose,
// including commercial applications, and to alter it and redistribute it freely,
// subject to the following restrictions:
//
// 1. The origin of this software must not be misrepresented;
// you must not claim that you wrote the original software.
// If you use this software in a product, an acknowledgment
// in the product documentation would be appreciated but is not required.
//
// 2. Altered source versions must be plainly marked as such,
// and must not be misrepresented as being the original software.
//
// 3. This notice may not be removed or altered from any source distribution.
//
////////////////////////////////////////////////////////////
#include <android/native_activity.h>
#include <android/log.h>
#include <dlfcn.h>
#include <errno.h>
#include <stdlib.h>
#include <jni.h>
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_INFO, "sfml-activity", __VA_ARGS__))
void* loadLibrary(const char* libraryName, JNIEnv* lJNIEnv, jobject& ObjectActivityInfo)
{
// Find out the absolute path of the library
jclass ClassActivityInfo = lJNIEnv->FindClass("android/content/pm/ActivityInfo");
jfieldID FieldApplicationInfo = lJNIEnv->GetFieldID(ClassActivityInfo, "applicationInfo", "Landroid/content/pm/ApplicationInfo;");
jobject ObjectApplicationInfo = lJNIEnv->GetObjectField(ObjectActivityInfo, FieldApplicationInfo);
jclass ClassApplicationInfo = lJNIEnv->FindClass("android/content/pm/ApplicationInfo");
jfieldID FieldNativeLibraryDir = lJNIEnv->GetFieldID(ClassApplicationInfo, "nativeLibraryDir", "Ljava/lang/String;");
jobject ObjectDirPath = lJNIEnv->GetObjectField(ObjectApplicationInfo, FieldNativeLibraryDir);
jclass ClassSystem = lJNIEnv->FindClass("java/lang/System");
jmethodID StaticMethodMapLibraryName = lJNIEnv->GetStaticMethodID(ClassSystem, "mapLibraryName", "(Ljava/lang/String;)Ljava/lang/String;");
jstring LibNameObject = lJNIEnv->NewStringUTF(libraryName);
jobject ObjectName = lJNIEnv->CallStaticObjectMethod(ClassSystem, StaticMethodMapLibraryName, LibNameObject);
jclass ClassFile = lJNIEnv->FindClass("java/io/File");
jmethodID FileConstructor = lJNIEnv->GetMethodID(ClassFile, "<init>", "(Ljava/lang/String;Ljava/lang/String;)V");
jobject ObjectFile = lJNIEnv->NewObject(ClassFile, FileConstructor, ObjectDirPath, ObjectName);
// Get the library absolute path and convert it
jmethodID MethodGetPath = lJNIEnv->GetMethodID(ClassFile, "getPath", "()Ljava/lang/String;");
jstring javaLibraryPath = static_cast<jstring>(lJNIEnv->CallObjectMethod(ObjectFile, MethodGetPath));
const char* libraryPath = lJNIEnv->GetStringUTFChars(javaLibraryPath, NULL);
// Manually load the library
void * handle = dlopen(libraryPath, RTLD_NOW | RTLD_GLOBAL);
if (!handle)
{
LOGE("dlopen(\"%s\"): %s", libraryPath, dlerror());
exit(1);
}
// Release the Java string
lJNIEnv->ReleaseStringUTFChars(THESTRING, pathStr);
return handle;
}
void ANativeActivity_onCreate(ANativeActivity* activity, void* savedState, size_t savedStateSize)
{
// Before we can load a library, we need to find out its location. As
// we're powerless here in C/C++, we need the JNI interface to communicate
// with the attached Java virtual machine and perform some Java calls in
// order to retrieve the absolute path of our libraries.
//
// Here's the snippet of Java code it performs:
// --------------------------------------------
// ai = getPackageManager().getActivityInfo(getIntent().getComponent(), PackageManager.GET_META_DATA);
// File libraryFile = new File(ai.applicationInfo.nativeLibraryDir, System.mapLibraryName(libname));
// String path = libraryFile.getPath();
//
// With libname being the library name such as "jpeg".
// Initialize JNI
jint lResult;
jint lFlags = 0;
JavaVM* lJavaVM = activity->vm;
JNIEnv* lJNIEnv = activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
// Attach the current thread to the JAva virtual machine
lResult=lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
if (lResult == JNI_ERR) {
LOG("Couldn't attach the current thread to the Java virtual machine");
exit(1);
}
// Retrieve the NativeActivity
jobject ObjectNativeActivity = activity->clazz;
jclass ClassNativeActivity = lJNIEnv->GetObjectClass(ObjectNativeActivity);
// Retrieve the ActivityInfo
jmethodID MethodGetPackageManager = lJNIEnv->GetMethodID(ClassNativeActivity, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject ObjectPackageManager = lJNIEnv->CallObjectMethod(ObjectNativeActivity, MethodGetPackageManager);
jmethodID MethodGetIndent = lJNIEnv->GetMethodID(ClassNativeActivity, "getIntent", "()Landroid/content/Intent;");
jobject ObjectIntent = lJNIEnv->CallObjectMethod(ObjectNativeActivity, MethodGetIndent);
jclass ClassIntent = lJNIEnv->FindClass("android/content/Intent");
jmethodID MethodGetComponent = lJNIEnv->GetMethodID(ClassIntent, "getComponent", "()Landroid/content/ComponentName;");
jobject ObjectComponentName = lJNIEnv->CallObjectMethod(ObjectIntent, MethodGetComponent);
jclass ClassPackageManager = lJNIEnv->FindClass("android/content/pm/PackageManager");
//jfieldID FieldGET_META_DATA = lJNIEnv->GetStaticFieldID(ClassPackageManager, "GET_META_DATA", "L");
//jobject GET_META_DATA = lJNIEnv->GetStaticObjectField(ClassPackageManager, FieldGET_META_DATA);
// getActivityInfo(getIntent().getComponent(), PackageManager.GET_META_DATA) -> ActivityInfo object
jmethodID MethodGetActivityInfo = lJNIEnv->GetMethodID(ClassPackageManager, "getActivityInfo", "(Landroid/content/ComponentName;I)Landroid/content/pm/ActivityInfo;");
// todo: do not hardcode the GET_META_DATA integer value but retrieve it instead
jobject ObjectActivityInfo = lJNIEnv->CallObjectMethod(ObjectPackageManager, MethodGetActivityInfo, ObjectComponentName, (jint)128);
// Load our libraries in reverse order
loadLibrary("stlport_shared", lJNIEnv, ObjectActivityInfo);
loadLibrary("sfml-system", lJNIEnv, ObjectActivityInfo);
loadLibrary("sfml-window", lJNIEnv, ObjectActivityInfo);
loadLibrary("jpeg", lJNIEnv, ObjectActivityInfo);
loadLibrary("freetype", lJNIEnv, ObjectActivityInfo);
loadLibrary("sfml-graphics", lJNIEnv, ObjectActivityInfo);
loadLibrary("ogg", lJNIEnv, ObjectActivityInfo);
loadLibrary("FLAC", lJNIEnv, ObjectActivityInfo);
loadLibrary("vorbis", lJNIEnv, ObjectActivityInfo);
loadLibrary("vorbisenc", lJNIEnv, ObjectActivityInfo);
loadLibrary("sndfile", lJNIEnv, ObjectActivityInfo);
loadLibrary("openal", lJNIEnv, ObjectActivityInfo);
loadLibrary("sfml-audio", lJNIEnv, ObjectActivityInfo);
loadLibrary("sfml-network", lJNIEnv, ObjectActivityInfo);
void* handle = loadLibrary("sfml-example", lJNIEnv, ObjectActivityInfo);
// todo: should we detach the current thread ? because if we do, it
// crashes (lJavaVM->DetachCurrentThread();)
// Call the original ANativeActivity_onCreate function
void (*ANativeActivity_onCreate)(ANativeActivity*, void*, size_t) = dlsym(handle, "ANativeActivity_onCreate");
if (!ANativeActivity_onCreate)
{
LOGE("sfml-activity: Undefined symbol ANativeActivity_onCreate");
exit(1);
}
ANativeActivity_onCreate(activity, savedState, savedStateSize);
}