Skip to content

Commit

Permalink
Merge pull request #24 from thedilletante/android_multithread
Browse files Browse the repository at this point in the history
[Android] Added newGuid() overloading to support multithread invocations
  • Loading branch information
graeme-hill committed Aug 4, 2017
2 parents 71c709e + 40c3dde commit 5b45cdd
Show file tree
Hide file tree
Showing 45 changed files with 618 additions and 93 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -4,3 +4,4 @@ build/
*.swp
android/libs/
android/obj/
cmake_build
2 changes: 2 additions & 0 deletions CMakeLists.txt
Expand Up @@ -24,6 +24,8 @@ elseif(APPLE)
target_link_libraries(xg ${CFLIB})
add_definitions(-DGUID_CFUUID)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -pedantic")
elseif(ANDROID)
target_compile_definitions(xg PRIVATE GUID_ANDROID)
else()
find_package(Libuuid REQUIRED)
if (NOT LIBUUID_FOUND)
Expand Down
24 changes: 18 additions & 6 deletions Guid.cpp
Expand Up @@ -39,6 +39,7 @@ THE SOFTWARE.

#ifdef GUID_ANDROID
#include <jni.h>
#include <cassert>
#endif

BEGIN_XG_NAMESPACE
Expand All @@ -50,13 +51,16 @@ AndroidGuidInfo AndroidGuidInfo::fromJniEnv(JNIEnv *env)
{
AndroidGuidInfo info;
info.env = env;
info.uuidClass = env->FindClass("java/util/UUID");
auto localUuidClass = env->FindClass("java/util/UUID");
info.uuidClass = (jclass)env->NewGlobalRef(localUuidClass);
env->DeleteLocalRef(localUuidClass);
info.newGuidMethod = env->GetStaticMethodID(
info.uuidClass, "randomUUID", "()Ljava/util/UUID;");
info.mostSignificantBitsMethod = env->GetMethodID(
info.uuidClass, "getMostSignificantBits", "()J");
info.leastSignificantBitsMethod = env->GetMethodID(
info.uuidClass, "getLeastSignificantBits", "()J");
info.initThreadId = std::this_thread::get_id();
return info;
}

Expand Down Expand Up @@ -340,13 +344,15 @@ Guid newGuid()

// android version that uses a call to a java api
#ifdef GUID_ANDROID
Guid newGuid()
Guid newGuid(JNIEnv *env)
{
jobject javaUuid = androidInfo.env->CallStaticObjectMethod(
assert(env != androidInfo.env || std::this_thread::get_id() == androidInfo.initThreadId);

jobject javaUuid = env->CallStaticObjectMethod(
androidInfo.uuidClass, androidInfo.newGuidMethod);
jlong mostSignificant = androidInfo.env->CallLongMethod(javaUuid,
jlong mostSignificant = env->CallLongMethod(javaUuid,
androidInfo.mostSignificantBitsMethod);
jlong leastSignificant = androidInfo.env->CallLongMethod(javaUuid,
jlong leastSignificant = env->CallLongMethod(javaUuid,
androidInfo.leastSignificantBitsMethod);

std::array<unsigned char, 16> bytes =
Expand All @@ -369,12 +375,18 @@ Guid newGuid()
(unsigned char)((leastSignificant) & 0xFF)
};

androidInfo.env->DeleteLocalRef(javaUuid);
env->DeleteLocalRef(javaUuid);

return bytes;
}

Guid newGuid()
{
return newGuid(androidInfo.env);
}
#endif


END_XG_NAMESPACE

// Specialization for std::swap<Guid>() --
Expand Down
5 changes: 5 additions & 0 deletions Guid.hpp
Expand Up @@ -25,6 +25,7 @@ THE SOFTWARE.
#pragma once

#ifdef GUID_ANDROID
#include <thread>
#include <jni.h>
#endif

Expand Down Expand Up @@ -84,11 +85,15 @@ struct AndroidGuidInfo
jmethodID newGuidMethod;
jmethodID mostSignificantBitsMethod;
jmethodID leastSignificantBitsMethod;
std::thread::id initThreadId;
};

extern AndroidGuidInfo androidInfo;

void initJni(JNIEnv *env);

// overloading for multi-threaded calls
Guid newGuid(JNIEnv *env);
#endif

END_XG_NAMESPACE
Expand Down
3 changes: 1 addition & 2 deletions README.md
Expand Up @@ -207,8 +207,7 @@ following requirements:
* Android emulator is already running (or you have physical device connected).
* You're using bash.
* adb is in your path.
* ndk-build and other ndk cross-compile tools are in your path.
* You have a jdk setup including `JAVA_HOME` environment variable.
* You have an Android sdk setup including `ANDROID_HOME` environment variable.
## License
Expand Down
8 changes: 4 additions & 4 deletions android.sh
@@ -1,10 +1,10 @@
#!/usr/bin/env bash

export LC_NUMERIC="en_US.UTF-8"

pushd android
ndk-build clean || { exit 1; }
ndk-build || { exit 1; }
ant debug || { exit 1; }
./gradlew clean assembleDebug
adb uninstall ca.graemehill.crossguid.testapp || { exit 1; }
adb install bin/TestApp-debug.apk || { exit 1; }
adb install app/build/outputs/apk/debug/app-debug.apk || { exit 1; }
adb shell am start -n ca.graemehill.crossguid.testapp/ca.graemehill.crossguid.testapp.MainActivity
popd
10 changes: 10 additions & 0 deletions android/.gitignore
@@ -0,0 +1,10 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild
.idea
1 change: 1 addition & 0 deletions android/app/.gitignore
@@ -0,0 +1 @@
/build
18 changes: 18 additions & 0 deletions android/app/CMakeLists.txt
@@ -0,0 +1,18 @@
cmake_minimum_required(VERSION 3.4.1)

set(LIB_NAME crossguidtest)
set(XG_DIR ${CMAKE_CURRENT_LIST_DIR}/../..)
set(XG_TEST_DIR ${XG_DIR}/test)

add_library(${LIB_NAME} SHARED src/main/cpp/jnitest.cpp ${XG_TEST_DIR}/Test.cpp)

target_include_directories(${LIB_NAME} PRIVATE
${XG_DIR}
${XG_TEST_DIR})

target_compile_definitions(${LIB_NAME} PRIVATE GUID_ANDROID)

set(XG_TESTS OFF CACHE BOOL "disable tests")
add_subdirectory(${XG_DIR} ${XG_DIR}/cmake_build)

target_link_libraries(${LIB_NAME} xg)
39 changes: 39 additions & 0 deletions android/app/build.gradle
@@ -0,0 +1,39 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 25
buildToolsVersion "26.0.0"
defaultConfig {
applicationId "ca.graemehill.crossguid.testapp"
minSdkVersion 14
targetSdkVersion 25
versionCode 1
versionName "1.0"
externalNativeBuild {
cmake {
cppFlags "-std=c++11 -frtti -fexceptions"
arguments "-DANDROID_TOOLCHAIN=clang"
}
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
androidTestImplementation('com.android.support.test.espresso:espresso-core:3.0.0', {
exclude group: 'com.android.support', module: 'support-annotations'
})
implementation 'com.android.support:appcompat-v7:25.4.0'
testImplementation 'junit:junit:4.12'
}
25 changes: 25 additions & 0 deletions android/app/proguard-rules.pro
@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /usr/local/Cellar/android-sdk/24.4.1_1/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -1,9 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="ca.graemehill.crossguid.testapp"
android:versionCode="1"
android:versionName="1.0">
<application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
package="ca.graemehill.crossguid.testapp">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name="MainActivity"
android:label="@string/app_name">
<intent-filter>
Expand Down
61 changes: 61 additions & 0 deletions android/app/src/main/cpp/jnitest.cpp
@@ -0,0 +1,61 @@
#include <string>
#include <sstream>
#include <atomic>
#include <iostream>

#include <jni.h>
#include "Guid.hpp"
#include "Test.hpp"

JavaVM *&javaVM() {
static JavaVM *jvm;
return jvm;
}

extern "C"
{

JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void * /* reserved */) {
javaVM() = jvm;
return JNI_VERSION_1_6;
}

JNIEXPORT jstring JNICALL
Java_ca_graemehill_crossguid_testapp_MainActivity_test(
JNIEnv *env, jobject /*thiz*/)
{
std::stringstream resultStream;
xg::initJni(env);
test(resultStream);
return env->NewStringUTF(resultStream.str().c_str());
}

JNIEXPORT jstring JNICALL
Java_ca_graemehill_crossguid_testapp_MainActivity_newGuid(
JNIEnv *env, jobject /*thiz*/) {
return env->NewStringUTF(xg::newGuid(env).str().c_str());
}

JNIEXPORT jstring JNICALL
Java_ca_graemehill_crossguid_testapp_MainActivity_createGuidFromNativeThread(
JNIEnv *env, jobject /*thiz*/) {

// there is no promise<> in armeabi of ndk
// so ugly atomic_bool wait solution
std::atomic_bool ready { false };
std::string guid;

std::thread([&ready, &guid](){
JNIEnv *threadEnv;
javaVM()->AttachCurrentThread(&threadEnv, NULL);
guid = xg::newGuid(threadEnv);
javaVM()->DetachCurrentThread();

ready = true;
}).detach();

while (!ready);
return env->NewStringUTF(guid.c_str());
}

}
@@ -0,0 +1,64 @@
package ca.graemehill.crossguid.testapp;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

import java.util.concurrent.CountDownLatch;

public class MainActivity extends Activity {

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

final TextView textView = (TextView)findViewById(R.id.mainTextView);
textView.setText(test());

final TextView javaThreadView = (TextView)findViewById(R.id.javaThreadView);
javaThreadView.setText(createGuidFromJavaThread());

final TextView nativeThreadView = (TextView)findViewById(R.id.nativeThreadView);
nativeThreadView.setText(createGuidFromNativeThread());
}

public native String test();

private static class StringCapture {
private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}
}

public String createGuidFromJavaThread() {
final CountDownLatch created = new CountDownLatch(1);
final StringCapture result = new StringCapture();
new Thread(new Runnable() {
@Override
public void run() {
result.setValue(newGuid());
created.countDown();
}
}).start();
try {
created.await();
} catch (InterruptedException e) {
return "Could not get value: " + e.getMessage();
}
return result.getValue();
}

public native String newGuid();

public native String createGuidFromNativeThread();
static {
System.loadLibrary("crossguidtest");
}
}

0 comments on commit 5b45cdd

Please sign in to comment.