-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
sjfricke
committed
Aug 7, 2017
1 parent
067ae8d
commit 277aac2
Showing
9 changed files
with
288 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) ==> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) ==> |
Oops, something went wrong.