Skip to content

Commit

Permalink
Added Native to Java in Sec 1
Browse files Browse the repository at this point in the history
  • Loading branch information
sjfricke committed Aug 7, 2017
1 parent 067ae8d commit 277aac2
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 180 deletions.
14 changes: 8 additions & 6 deletions Section_01_NDK/README.md
Expand Up @@ -35,13 +35,15 @@
* Header file to link native and Java together.
* [Chapter 7 - **The Native Code**](./Tutorials/Chapter_07.md)
* Writing native C code.
* [Chapter 8 - **The Makefiles**](./Tutorials/Chapter_08.md)
* [Chapter 8 - **Adding a GUI**](./Tutorials/Chapter_08.md)
* How to add a button and other GUI elements.
* [Chapter 9 - **Native to Java**](./Tutorials/Chapter_09.md)
* How to send data from Native layer to Java.
* [Chapter 10 - **The Makefiles**](./Tutorials/Chapter_10.md)
* Using ndk-build to build the project.
* [Chapter 9 - **Adding a library**](./Tutorials/Chapter_09.md)
* [Chapter 11 - **Adding a library**](./Tutorials/Chapter_11.md)
* How to add libraries to your project.
* [Chapter 10 - **Debugging**](./Tutorials/Chapter_10.md)
* How to use adb and logcat to debug.
* [Chapter 11 - **Adding a GUI**](./Tutorials/Chapter_11.md)
* How to add a button and other GUI elements.
* [Chapter 12 - **Debugging**](./Tutorials/Chapter_12.md)
* How to use adb and logcat to debug.

[Section 2 - Graphics](../Section_02_Graphics) ==>
2 changes: 1 addition & 1 deletion Section_01_NDK/Tutorials/Chapter_02.md
Expand Up @@ -27,7 +27,7 @@ You first must understand the trade-offs you are giving when using NDK before yo
* Using Java Native Interface (JNI) we will send all UI events down to where all our native C/C++ code is written.

## Building the Project (CMake vs ndk-build)
* There is more about this in the [makefile](./Chapter_08.md) chapter.
* There is more about this in the [makefile](./Chapter_10.md) chapter.
* You can use either `CMake` or `ndk-build` for building your project together.
* Build tools are programs that automate the creation of executable applications from source code.
* Building incorporates compiling, linking and packaging the code into a usable or executable form.
Expand Down
15 changes: 0 additions & 15 deletions Section_01_NDK/Tutorials/Chapter_06.md
Expand Up @@ -40,21 +40,6 @@ Because C++ and Java have different data types, we need to use JNI data types to

* **Note:** there is a JNI array type for each of the different primitive types as well.

## Setting up the JVM
* We optionally can to store a reference to the Java Virtual Machine so that we can call into the Java layer to trigger rendering or call back up from the native code to the Java layer.

```
jint JNI_OnLoad(JavaVM* vm, void*) {}
app.SetJavaVM(vm);
return JNI_VERSION_1_6;
}
// This part can be anywhere else in your code ...
JavaVM* java_vm_;
void SetJavaVM(JavaVM* java_vm) { java_vm_ = java_vm; }
```
* With this we will now have reference to the JVM held in our C++ class of choice.

## Bridging the function calls
* For this example image we called `public static native void onGlSurfaceChanged(int width, int height);` from the Java thread and will need to create a JNI function call to handle it.
* `JNIEXPORT void JNICALL` will
Expand Down
141 changes: 53 additions & 88 deletions Section_01_NDK/Tutorials/Chapter_08.md
@@ -1,92 +1,57 @@
<== [Chapter 7](./Chapter_07.md) -- [Chapter 9](./Chapter_09.md) ==>

# Chapter 8 - The Makefiles

## ndk-build
* So it is possible to use CMake or ndk-build for building your project
* We will be using ndk-build
* CMake is definitely the "newer" choice to use as it is compatible with various other platforms
* Since Tango is a Android platform specific project, I feel justified to use ndk-build
* **Honesty:** I don't know how to build NDK in CMake and anyone who does **please** add it!

![Makefiles](../Images/Makefiles.png)

Now that we have our code, we need to make sure that everything gets built correctly so we can package the APK to the device. These two files will be used when `ndk-build` is ran

## Applicaiton.mk
* This file is where all the more hardware specific options are set
* `APP_ABI := armeabi-v7a arm64-v8a x86`
* List all the different ISA to build for. You will see a build for each on listed
* armeabi-v7a is used by the dev kit
* arm64-v8a is used by the Lenovo Phab 2 Pro
* The Android OS will only install the correct version of the compiled build so unless needed, its suggested to keep all main types listed
* `APP_STL := gnustl_static`
* The *system* runtime is the default if there is no APP_STL definition.
* You can only select a single C++ runtime that all your code will depend on.
* It is not possible to mix shared libraries compiled against different C++ runtimes.
* [A list of Android C++ Library Support](https://developer.android.com/ndk/guides/cpp-support.html)
* `APP_PLATFORM := android-19`
* Tells the ndk-build the minimum version of Android SDK API to build for.

## Android.mk
* This file is used to tell which libraries, flags, directories, etc. to use
* The main part to take away from it now is that file is what controls the compiling of your native code
* **Really good** in depth source of details on Anroid.mk files can be [found here](http://android.mk/)
* **ALSO** here is the official guide sheet [from Google](https://developer.android.com/ndk/guides/android_mk.html)

#### In-depth
* This is where you list all the files you want to compile
* If you add libraries, like tango_gl, need to include its source and headers
* Let's take an example and break it down

```
LOCAL_PATH := $(call my-dir)
PROJECT_ROOT_FROM_JNI := ../../../../..
PROJECT_ROOT := $(LOCAL_PATH)/$(PROJECT_ROOT_FROM_JNI)
include $(CLEAR_VARS)
LOCAL_MODULE := libcpp_plane_fitting_example
LOCAL_SHARED_LIBRARIES := tango_client_api tango_support_api
LOCAL_STATIC_LIBRARIES := png
LOCAL_CFLAGS := -std=c++11
LOCAL_C_INCLUDES := $(PROJECT_ROOT)/tango_gl/include \
$(PROJECT_ROOT)/third_party/glm \
$(PROJECT_ROOT)/third_party/libpng/include/
LOCAL_SRC_FILES := jni_interface.cc \
plane_fitting.cc \
plane_fitting_application.cc \
point_cloud_renderer.cc \
$(PROJECT_ROOT_FROM_JNI)/tango_gl/bounding_box.cc \
$(PROJECT_ROOT_FROM_JNI)/tango_gl/camera.cc \
...
...
$(PROJECT_ROOT_FROM_JNI)/tango_gl/obj_loader.cc
LOCAL_LDLIBS := -lGLESv2 -llog -L$(SYSROOT)/usr/lib -lz -landroid
include $(BUILD_SHARED_LIBRARY)
$(call import-add-path,$(PROJECT_ROOT))
$(call import-add-path,$(PROJECT_ROOT)/third_party)
$(call import-module,libpng)
$(call import-module,tango_client_api)
$(call import-module,tango_support_api)
```

* First not that the `\` is used to continue the line and make the file more readable than having one super long line.
* The `$(call _______)` is a way to call special instructions followed by a parameter if valid
* So first let's start with with `LOCAL_PATH := $(call my-dir)` This sets the variable `LOCAL_PATH` to the directory where the `Android.mk` file is held.
* `PROJECT_ROOT_FROM_JNI := ../../../../..` is just a way to go up to the top directory five folders up and set it to * `PROJECT_ROOT_FROM_JNI` Then we combine the two variables and get the root of the project with `PROJECT_ROOT := $(LOCAL_PATH)/$(PROJECT_ROOT_FROM_JNI)`
* The `include $(CLEAR_VARS)` variable is provided by the build system and points to a special GNU Makefile that will clear many LOCAL_XXX variables for you with the exception of LOCAL_PATH.
* The `LOCAL_MODULE` variable must be defined to identify each module you describe in your `Android.mk`. The name must be *unique* and not contain any spaces.
* `LOCAL_SHARED_LIBRARIES := tango_client_api tango_support_api` - The list of shared libraries *modules* this module depends on at runtime.
* `LOCAL_STATIC_LIBRARIES := png` - The list of static libraries modules (built with `BUILD_STATIC_LIBRARY`) that should be linked to this module. This only makes sense in shared library modules.
* `LOCAL_CFLAGS := -std=c++11` - where we set flags and in this case declare C++ 11
* `LOCAL_C_INCLUDES` - Additional directories to instruct the C/C++ compilers to look for header files in.
* The build system looks at `LOCAL_SRC_FILES` to know what source files to compile, make sure you have them all that you need!
* `LOCAL_LDLIBS` The list of additional linker flags to be used when building your module. **NOTE** you will need -landroid to use the asset_manager library which is part of the `<android/asset_manager.h>` file
* The `BUILD_SHARED_LIBRARY` is a variable provided by the build system that will start building your shared libraries... I know shocker right?
* `$(call import-add-path,$(PROJECT_ROOT))` will go and take your `PROJECT_ROOT` folder and add it to your path list for the next part
* `$(call import-module,<name>)` A function that allows you to find and include the Android.mk of another module by name. A typical example is. This will look for the module tagged `<name>` in the list of directories referenced by your `NDK_MODULE_PATH` environment variable, and include its `Android.mk` automatically for you.
# Chapter 8 - Adding a GUI

Here are the steps to take to add a GUI such as a button to your NDK application:

* in the `res/layout/*.xml` file create a button using the Android Studio drag and drop tool
* In the `properties` section in the right give your item a good ID name.
* You can also change other useful information such as the text displayed.
* You can set your onClick in the Main Activity programmatically or just set the onClick in the properties of the GUI.
* **NOTE:** you need to have a function first in your main activity.
* Create the function in your main activity
* example - `public void snapShot(View view) { }`
* **NOTE:** I have found from others that a better way of getting the click is using a `findViewById(R.id.snapshot).setOnClickListener(this);` call **after** `setContentView()`
* This way lets you have one function to switch case the buttons.
* ```
@Override
public void onClick(View v) {
// Handle button clicks.
switch (v.getId()) {
...
...
}
}
```
* Now add a call to you JNI Native class.
* ```
public void snapShot(View view) {
TangoJNINative.snapShot(1);
}
```
* In you JNI Native file add the new function:
* `public static native void snapShot();`
* In the JNI_Header add if you are not auto generating it:
* ```
Java_com_demo_tutorial_tango_tango_1ndk_1tutorial_TangoJNINative_snapShot(JNIEnv*, jobject, int type)) {
app.snapShot(type);
}
```
* Add the function to you native code.
* Don't forget to declare it in your header file if needed.
* ```
void myApp::snapShot(int type) {
// cool stuff
}
```

## Conclusion

To send data from a GUI element to your native code you need to:

1. Create the GUI event in Java UI thread.
2. Call native Java function.
3. Have JNI function to handle it.
4. Pass value to native code.

<== [Chapter 7](./Chapter_07.md) -- [Chapter 9](./Chapter_09.md) ==>
106 changes: 102 additions & 4 deletions Section_01_NDK/Tutorials/Chapter_09.md
@@ -1,11 +1,109 @@
<== [Chapter 8](./Chapter_08.md) -- [Chapter 10](./Chapter_10.md) ==>

# Chapter 9 - Adding a library
# Chapter 9 - Native to Java

# TODO - Chapter not finished!
There are two ways you will return data back to the Java layer, either synchronously or asynchronously.

We are going to go over how to add a file to Android.mk which is your main source for adding a library
## Synchronously

If you get a **Undefined reference to ________** warning there is a good chance you are missing a file that is needed for the library.
So if you want to return a value back to the Java layer (maybe you did some computation in C and want to return the results) you can return the value in the JNI function call.

* ```
JNIEXPORT jdouble JNICALL
Java_com_demo_tutorial_tango_tango_1ndk_1tutorial_TangoJNINative_doCompute(JNIEnv*, jobject)) {
jdouble result = ComputeUsingNativeCode();
return result;
}
```

* This works when you have something that runs in a blocking matter, but when if you want the native layer to return the result asynchronously? Well this requires a little bit more work with JNI and explained in next chapter!

## Asynchronously

So in my opinion, sending asynchronous data back from the native layer might be the most frustrating part of JNI to deal with as its easy to forget one things and then all hell breaks loose and its hard to debug because you are transitioning from a dynamically built library to the JVM and ya... just follow these instructions and be prepared for battle.

* **IMPORTANT:** For this tutorial case example, lets pretend there is a Java function `public static native void getPointCloudAverage();` and we don't want to block the main thread since this might take a while so we have another Java function `public void average(int pointCloudAvg);` which we want to call so it can display the value to the screen.

### Setting up the JVM

To be able to call back to the JVM we need to save it's reference in our native code.

* ```
jint JNI_OnLoad(JavaVM* vm, void*) {}
app.SetJavaVM(vm);
return JNI_VERSION_1_6;
}
// This part can be anywhere else in your code...
jobject calling_activity_obj_;
jmethodID on_demand_method_;
JavaVM* java_vm_;
void SetJavaVM(JavaVM* java_vm) { java_vm_ = java_vm; }
```
* With this we will now have reference to the JVM held in our C++ class of choice.

### OnCreate

* We should have a Java function `public static native void onCreate(Activity callerActivity);`
* In our JNI we should reference the class with our JavaVM* object and pass in:
* ```
void myApp::OnCreate(JNIEnv* env, jobject caller_activity)
{
// Need to create an instance of the Java activity
calling_activity_obj_ = env->NewGlobalRef(caller_activity);
// Need to enter package and class to find Java class
//
jclass handlerClass = env->GetObjectClass(caller_activity);
// Here you need to name the function and the JNI argument/parameter type
on_demand_method_ = env->GetMethodID(handlerClass, "average", "(I)V");
}
```
* **Note:** If the class that called the `public static native void onCreate(Activity callerActivity);` is also the same class with `public void average(int pointCloudAvg);` then we can just call:
* `jclass handlerClass = env->GetObjectClass(caller_activity);`
* The `jobject` is the *this* of the Java class.
* **If** `public void average(int pointCloudAvg);` is in a **different** class then we need to seek it out with:
* `jclass handlerClass = env->FindClass("com/demo/tutorial/tango/tango_ndk_tutorial/Your_Activity_Name_Here");`
* We need to give the path the Java Activity that holds the function we are calling back
* For `GetMethodID(handlerClass, "average", "(I)V");`:
* Pass the jclass variable as the first argument.
* Pass the name of the Java function as the second argument.
* Pass the JNI notation of the function type.
* `(I)V` represents that it will be a function that takes an **I**nteger and returns **V**oid

### Calling the function

Whenever we are ready we can now call the average function.

* ```
// ...
if (calling_activity_obj_ == nullptr || on_demand_method_ == nullptr) {
LOGE("Can not reference Activity to request render");
return;
}
JNIEnv *env;
java_vm_->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
// Here, we notify the Java activity that we'd like it to trigger a callback.
// pointCloudAverageValue is our integer we are passing back
env->CallVoidMethod(calling_activity_obj_, on_demand_method_, pointCloudAverageValue);
```

### Clean up

We need to make sure we clean up all our references.
* ```
void myApp::OnDestroy() {
JNIEnv* env;
java_vm_->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
env->DeleteGlobalRef(calling_activity_obj_);
calling_activity_obj_ = nullptr;
on_demand_method_ = nullptr;
}
```

<== [Chapter 8](./Chapter_08.md) -- [Chapter 10](./Chapter_10.md) ==>

0 comments on commit 277aac2

Please sign in to comment.