in my work I am trying to do some comparisons and as such I need to be able to write to database from NDK.
I tried to inspire from this source, but I keep getting (from emulator and physical device) following error on insert:
file is encrypted or is not a database
My relevant piece of code:
extern "C"
{
JNIEXPORT jstring JNICALL Java_package_Benchmark_getMessage(JNIEnv *env, jobject thisObj, jstring filePath, jstring databasePath)
{
sqlite3 *db;
const char *database = env->GetStringUTFChars(databasePath, 0);
if (sqlite3_open_v2(database, &db, SQLITE_OPEN_READWRITE, NULL) == SQLITE_OK) // returns zero but probably sets db as read-only
{
if (SQLITE_OK != sqlite3_exec(db, "INSERT INTO lessons (wordCount, wordPassedCount, lessonName, selected) VALUES (0, 0, '50k', 0)", NULL, NULL, NULL))
LOGD("%s", sqlite3_errmsg(db)); // here i get the error
int lesson_id = sqlite3_last_insert_rowid(db);
return env->NewStringUTF("Database opened!!");
}
env->ReleaseStringUTFChars(databasePath, database);
}
}
So my question is:
Can I (and how) insert to application's database (created by Java api) from NDK?
As workaround I guess would be possible to create temporary database (not encrypted), but I rather use the one I created beforehand by Java's SQLiteOpenHelper.
I found a problem.
First let me say sorry because the problem is not visible from original code in my question.
I passed to C++ function path to database file returned by
getDatabasePath("database_name").getAbsolutePath()
but it seems that it returns path with symbolic links and that SQLite somehow cannot handle.
Solution:
try {
getDatabasePath("database_name").getCanonicalPath();
}
catch (IOException e) { e.printStackTrace(); }
According to manual canonical does resolve symbolic links.
Related
1. Summarize the problem:
I would like to invoke a C# method by invoking a Java method to check license file. This license check is performed by using a C# dll. I'm using JNI and a C++ wrapper. I will provide necessary source code below.
The C# dll has a method public static string GetLicenseStatus() implemented which I wrote a wrapper for and now I'm trying to invoke this method from Java application.
I'm using jdk-17.0.2.8-hotspot from Eclipse Adoptium (64-bit) and IntelliJ IDEA as Java IDE and Visual Studio 2022 for C# project.
After Java method invocation I expect that it returns a String (number from 0-4, not valid, valid, expired, ...) but it results in a StackOverflowException when C# code is being executed/accessed.
2. Describe what you've tried
I also tried to return just a value in the C++ method without calling any C# code; this worked fine. So JNI <--> C++ wrapper is working fine.
Also I tried to run C# source code within a C# main class, that was also working fine. So there's no faulty C# code.
Good to know is maybe also that I tried to create an own C# dll to confirm that the issue is not related to the license dll (that's why I writing before about a "C# project in Visual Studio"). This dll is very basic and is just checking for dummy username & password. Even When I tried to just return true in the function, when invoking it from Java it resulted again in a StackOverflowException in Java IDE. Its running into this error when attempting to instantiate an object with gcnew. My own created C# class and also the C# license dll were added as reference in the C++ project.
Maybe also worth to mention:
The C# dll is relying on another dll to process license checking I assume.
I observed that Visual Studio for some reason doesn't recognise imported header files. I have to add them manually in visual Studio and copy paste code into the manual created file.
3. Show some code
"Authenticator.java":
package org.example;
public class Authenticator {
static {
System.loadLibrary("CppAuthenticator");
}
public native boolean authenticate(String username, String password);
public native String getLicenseStatus();
public static void main(String[] args) {
System.out.println("Program start");
Authenticator authenticator = new Authenticator();
System.out.println("Authenticator created");
/**boolean valid = authenticator.authenticate(args[0], args[1]);
System.out.println("Is valid?: "+valid);
if(!valid) {
System.err.println("Not valid!");
System.exit(1);
}
else {
System.out.println("Valid");
}**/
System.out.println("License Check...");
System.out.println("Status: "+authenticator.getLicenseStatus());
}
}
"CppAuthenticator.cpp"
#include "pch.h"
#include <msclr\marshal.h>
#include "CppAuthenticator.h"
#include "org_example_Authenticator.h"
// this is the main DLL file.
#include <string>
using System::Text::Encoding;
String^ toString(const char* chars) {
int len = (int)strlen(chars);
array<unsigned char>^ a = gcnew array<unsigned char> (len);
int i = 0;
while (i < len) {
a[i] = chars[i];
}
return Encoding::UTF8->GetString(a);
}
bool authenticate(const char* username, const char* password) {
SharpAuthenticator::Authenticator^ a = gcnew SharpAuthenticator::Authenticator(); // Fails here
return a->Authenticate(toString(username), toString(password));
}
JNIEXPORT jboolean JNICALL Java_org_example_Authenticator_authenticate
(JNIEnv* env, jobject c, jstring username, jstring password) {
jboolean isCopyUsername;
const char *c_username = env->GetStringUTFChars(username, &isCopyUsername);
jboolean isCopyPassword;
const char* c_password = env->GetStringUTFChars(password, &isCopyPassword);
jboolean result = authenticate(c_username, c_password);
env->ReleaseStringUTFChars(username, c_username);
env->ReleaseStringUTFChars(password, c_password);
return result;
}
String^ getLicenseStatus() {
return LicenseCheck::ValidateLicense::GetLicenseStatus(); // Fails here
}
JNIEXPORT jstring JNICALL Java_org_example_Authenticator_getLicenseStatus
(JNIEnv* env, jobject c) {
String^ cliString = getLicenseStatus();
msclr::interop::marshal_context context;
const char* utf8String = context.marshal_as<const char*>(cliString);
jstring result = env->NewStringUTF(utf8String);
return result;
}
"SharpAuthenticator.cs":
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SharpAuthenticator
{
public class Authenticator
{
public bool Authenticate(String username, String password)
{
return username == "user" && password == "pass";
}
public bool Authenticate1()
{
return false;
}
}
}
Here is the project structure I have in Visual Studio ("org_example_Authenticator.h" code was created with "javac -h ..."-command located in bin folder of JDK mentioned above.)
Here are the C++ project properties in Visual Studio:
Here are C# project properties for my own created dummy dll mentioned above:
It was a stupid mistake... It just cost me 1.5 days figuring out that I forgot to increment i in the while loop in toString method of "CppAuthenticator.cpp". Why these things always happen to me...? :D
Here the correct working method:
String^ toString(const char* chars) {
int len = (int)strlen(chars);
array<unsigned char>^ a = gcnew array<unsigned char> (len);
int i = 0;
while (i < len) {
a[i] = chars[i];
i++;
}
return Encoding::UTF8->GetString(a);
}
I am reading BufferedInputStream's source code in Java.I noticed there's a private Field:BUF_OFFSET.enter image description here
private static final long BUF_OFFSET
= U.objectFieldOffset(BufferedInputStream.class, "buf");
I tried to check it out,then I found the method:objectFieldOffset1.It is a native method.So I download the JDK source code(jdk-17 +35) from github.https://github.com/openjdk/jdk/tree/jdk-17%2B35
Finally I found the jlong find_field_offset method.
I can basically understand the code except the JavaFieldStreamenter image description here
static jlong find_field_offset(jclass clazz, jstring name, TRAPS) {
assert(clazz != NULL, "clazz must not be NULL");
assert(name != NULL, "name must not be NULL");
ResourceMark rm(THREAD);
char *utf_name = java_lang_String::as_utf8_string(JNIHandles::resolve_non_null(name));
InstanceKlass* k = InstanceKlass::cast(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
jint offset = -1;
for (JavaFieldStream fs(k); !fs.done(); fs.next()) {
Symbol *name = fs.name();
if (name->equals(utf_name)) {
offset = fs.offset();
break;
}
}
if (offset < 0) {
THROW_0(vmSymbols::java_lang_InternalError());
}
return field_offset_from_byte_offset(offset);
}
I tried to search in google,but nothing found.I tried to find it in Java offical document,nothing either.
Looks like JavaFieldStream is a iterator?
Looks like JavaFieldStream is a iterator?
Correct.
It is declared in "jdk17u/src/hotspot/share/oops/fieldStreams.hpp"
The comments in the file say (for FieldStreamBase):
// The is the base class for iteration over the fields array
// describing the declared fields in the class. Several subclasses
// are provided depending on the kind of iteration required. The
// JavaFieldStream is for iterating over regular Java fields and it
// generally the preferred iterator. InternalFieldStream only
// iterates over fields that have been injected by the JVM.
// AllFieldStream exposes all fields and should only be used in rare
// cases.
Hint: find jdk17u -type f | xargs grep JavaFieldStream | less
I am attempting to call JNIEnv::NewObject() in some JNI code when a C function returns a non-zero error code.
The order of events looks like:
Call C function.
If return code is non-zero, call a helper function which throws a custom excpetion.
The class I am trying to construct so that I can throw it is:
public final class HseException extends Exception {
private static final long serialVersionUID = 8995408998818557762L;
private final int errno;
private final Context ctx;
/* Only called from C */
HseException(final String message, final int errno, final Context ctx) {
super(message);
this.errno = errno;
this.ctx = ctx;
}
public Context getContext() {
return this.ctx;
}
public int getErrno() {
return this.errno;
}
public static enum Context {
NONE
}
}
In my code I am caching the jclass and jmethodID for the class and the constructor in a global struct, but the code looks like:
globals.com.micron.hse.HseException.class =
(*env)->FindClass(env, "com/micron/hse/HseException");
globals.com.micron.hse.HseException.init = (*env)->GetMethodID(
env,
globals.com.micron.hse.HseException.class,
"<init>",
"(Ljava/lang/String;ILcom/micron/hse/HseException$Context;)V");
globals.com.micron.hse.HseException.Context.class =
(*env)->FindClass(env, "com/micron/hse/HseException$Context");
globals.com.micron.hse.HseException.Context.NONE = (*env)->GetStaticFieldID(
env,
globals.com.micron.hse.HseException.Context.class,
"NONE",
"Lcom/micron/hse/HseException$Context;");
Note that the above code is located in the JNI_OnLoad() function of my library. This function completes without error, so this tells me that at least my classes and methods are being loaded correctly.
Lastly here is my helper function where I throw my custom exception type:
/* hse_err_t is a scalar type.
* hse_strerror() creates a string out of that scalar.
* hse_err_to_ctx() gets the enum context value embedded within the scalar.
* hse_err_to_errno() gets the errno value embedded within the scalar.
*/
jint
throw_new_hse_exception(JNIEnv *env, hse_err_t err)
{
assert(env);
assert(err);
const size_t needed_sz = hse_strerror(err, NULL, 0);
char *buf = malloc(needed_sz + 1);
if (!buf)
return (*env)->ThrowNew(
env,
globals.java.lang.OutOfMemoryError.class,
"Failed to allocate memory for error buffer");
hse_strerror(err, buf, needed_sz + 1);
const jstring message = (*env)->NewStringUTF(env, buf);
free(buf);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
const int rc = hse_err_to_errno(err);
const enum hse_err_ctx ctx = hse_err_to_ctx(err);
jfieldID err_ctx_field = NULL;
switch (ctx) {
case HSE_ERR_CTX_NONE:
err_ctx_field = globals.com.micron.hse.HseException.Context.NONE;
break;
}
assert(err_ctx_field);
const jobject err_ctx_obj = (*env)->GetStaticObjectField(
env, globals.com.micron.hse.HseException.Context.class, err_ctx_field);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
const jobject hse_exception_obj = (*env)->NewObject(
env,
globals.com.micron.hse.HseException.class,
globals.com.micron.hse.HseException.init,
message,
rc,
err_ctx_obj);
if ((*env)->ExceptionCheck(env))
return JNI_ERR;
return (*env)->Throw(env, (jthrowable)hse_exception_obj);
}
I know for a fact that the (*env)->NewObject() call is what is raising the exception because an exception check before and after will tell me so. The (*env)->NewStringUTF() call is successful and contains the string it should contain. The context field is also retrieved successfully.
What I am not understanding is why I am getting an InstantiationException. The Throws section of the JNIEnv::NewObject() is marked as the following:
THROWS:
InstantiationException: if the class is an interface or an abstract class.
OutOfMemoryError: if the system runs out of memory.
Any exceptions thrown by the constructor.
My class is not an interface nor is it an abstract class, so where could this exception be generated from? The weird thing is that I swear this worked before, but since I am writing these Java bindings from scratch, I have just been overwriting commits and force pushing to my branch.
Any help is appreciated. Unfortunately getMessage() on the exception returns null which just isn't helpful at all. There is no message from the JVM telling me potentially what I have done wrong either.
One detail that could be helpful is that when I try to call JNIEnv::ThrowNew() (after putting a (Ljava/lang/String;)V constructor in the same HseException class, jni_ThrowNew() segfaults, and I cannot understand why. The class is valid when I stash the jclass, and I know for a fact that the memory it is stashed in isn't overwritten in any way, since I have checked the pointer.
The repo where all this code lives is: https://github.com/hse-project/hse-java. Unfinished product, but at least it is buildable and tests can be ran. In the event that someone decides to clone the repo and build it, I will repeat the directions here:
meson build
ninja -C build
meson test -C build -t 0 KvsTest # I am using this test to exercise the code path
My goal tomorrow will be to try to reproduce the issue in a smaller manner. I may also try to peer into the OpenJDK code assuming that is where the JNI interfaces live. Figure if I look hard enough, I might find the line of code which generates the exception.
Edit: I did a test where in my current code, I added a main function and a native function whose only purpose is to throw an exception from C. The code looks something like:
private static native void throwException();
public static void main(String[] args) {
System.load("/path/to/.so");
throwException();
}
The implementation of the native function is:
void Java_com_micron_hse_Hse_throwException
(JNIEnv *env, jclass hse_cls)
{
(void)hse_cls;
/* Generate error */
hse_err_t err = hse_kvdb_txn_begin(NULL, NULL);
throw_new_hse_exception(env, err);
}
This printed the following after executing java -jar path/to/jar:
Exception in thread "main" com.micron.hse.HseException: lib/binding/kvdb_interface.c:1046: Invalid argument (22)
at com.micron.hse.Hse.throwException(Native Method)
at com.micron.hse.Hse.main(Hse.java:28)
That is exactly what I expect to be printed, so now I would say I am even more lost than when I started. For some reason in the context of my tests, the InstantiationException is raised. Not sure if an application using the JAR would hit the same issue or if it is just a test context thing.
Edit 2:
Changed the main method from the previous edit to the following which is pretty much exactly what my test does:
public static void main(String[] args) throws HseException {
try {
loadLibrary(Paths.get("/home/tpartin/Projects/hse-java/build/src/main/c/libhsejni-2.so"));
init();
final Kvdb kvdb = Kvdb.open(Paths.get("/media/hse-tests"));
final Kvs kvs = kvdb.kvsOpen("kvs");
kvs.delete((byte[])null);
kvs.close();
kvdb.close();
} finally {
// fini();
}
}
And was able throw the exception from C appropriately. This must mean that something is wrong with my test environment somehow.
Edit 3: Another clue. On one test, this issue generates the InstantiationException. On another test, this issue segfaults in jni_NewObject.
My issue was that I was holding onto jclass et al. references for too long.
Prior question: Why I should not reuse a jclass and/or jmethodID in JNI?
Java docs: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/design.html#global_and_local_references
All Java objects returned by JNI functions are local references.
Thanks to Andrew Henle for pointing this out in the comments of the question. I have highlighted his comment in this answer, and will mark it is as the answer.
I'm doing a Java program that calls some C functions via the JNI. I have a situation where I gather a bunch of computer energy readings in C and want to write them to a file. Ideally, I want to use fprintf(), instead of formatting all the data as a string, passing it up to Java through the JNI interface, and then writing it to file the Java way, but that seems a lot less efficient.
The only problem is that when I do fprintf() in C, the output file I get has a garbage name. Definitely not the file name I provided it.
alejandro#alejandro-ThinkPad-E15:~/throwawayfiles$ ls
''$'\360\210\025\032\a'
The contents of the file are what I expect it to be, though. I've also called this function in C and it worked just fine, the only problem is when I facilitate calling it from a Java program. Is there a way that I can make sure that the output file has the name I actually want? Or is this just one of the problems of JNI stuff that I have to deal with. Like I said, plan B is to put all the data in a String, send it to java, and filewrite from there, but that's slow, and also code I'd rather not write :)
Thank you!
The code used to write to file. I'm handling a data structure AsyncEnergyMonitor that gathers energy readings. All of the reading and data storage is done in C, but it's all faciliated in the larger context of a Java program.
private native static void writeToFileFromC(String filePath);
public void writeToFile(String filePath)
{
writeToFileFromC(filePath);
}
JNIEXPORT void JNICALL
Java_jrapl_AsyncEnergyMonitorCSide_writeToFileFromC(JNIEnv* env,
jclass jcls, const char* filepath)
{
writeToFile(monitor, filepath);
}
Here is where the file is initially opened and I write in the header line.
void writeToFile(AsyncEnergyMonitor *monitor, const char* filepath){
FILE * outfile = (filepath) ? fopen(filepath,"w") : stdout;
fprintf(outfile,"samplingRate: %d milliseconds\n",monitor->samplingRate);
fprintf(outfile,"socket,dram,gpu,core,pkg,timestamp(usec since epoch)\n");
if (USING_DYNAMIC_ARRAY)
writeToFile_DynamicArray(outfile, monitor->samples_dynarr);
if (USING_LINKED_LIST)
writeToFile_LinkedList(outfile, monitor->samples_linklist);
if (filepath) fclose(outfile);
}
And here are the two functions I use to write the rest of the data, depending on whether the data is stored in a linked list or a dynamic array.
void
writeToFile_DynamicArray(FILE* outfile, DynamicArray* a) {
for (int i = 0; i < a->nItems; i++) {
EnergyStats current = a->items[i];
char csv_string[512];
energy_stats_csv_string(current, csv_string);
fprintf(outfile,"%s\n",csv_string);
}
}
void
writeToFile_LinkedList(FILE* outfile, LinkedList* l) {
LinkNode* current = l->head;
while(current != NULL) {
int upperbound = (current == l->tail) ?
(l->nItemsAtTail) : (NODE_CAPACITY);
for (int i = 0; i < upperbound; i++) {
char ener_string[512];
energy_stats_csv_string(current->items[i], ener_string);
fprintf(outfile,"%s\n",ener_string);
}
current = current->next;
}
}
I forgot to explicitly convert the filepath name from a Java string to a C string. Had nothing to do with file writing from C. Just made a garbage string name because I didn't convert
JNIEXPORT void JNICALL
Java_jrapl_AsyncEnergyMonitorCSide_writeToFileFromC(JNIEnv* env,
jclass jcls, const char* filepath)
{
writeToFile(monitor, filepath);
}
Fixed it to
JNIEXPORT void JNICALL
Java_jrapl_AsyncEnergyMonitorCSide_writeToFileFromC(JNIEnv* env, jclass jcls,
jstring jstringFilepath)
{
const char* filepath = (*env)->GetStringUTFChars(env, jstringFilepath, NULL);
writeToFile(monitor, filepath);
(*env)->ReleaseStringUTFChars(env, jstringFilepath, filepath);
}
All good now.
I have a problem in my Java native audio library but first, here is my current approach:
With a native method I'm opening a 'global' stream, which receives data over a callback function.
The callback function runs until there is no data.
If there is no data, the stream only stops, but does not get closed.
Now I wanted to feed the stream with data again [trying to start stream again(this operation is allowed)], but the stream has already been deleted.
So now I tried to figure out how to prevent deletion of the stream from C++ or Java.
One solution was to create a thread in the stream, which prevents the deletion.
But I don't like this solution...
So I searched how to keep such objects alive and found out, that there are so called "global references" which can be made with the JNI. But I did not understand whether they are only for java objects or for both.
Also I tried out whether another pointer type of C++ could help.
I appreciate any help or ideas, it does not have to be JNI only. C++ standard library methods/functions/classes etc. are also good :) !
System information:
Compiler: MinGW64 over MSYS2
JDK8u91
Of course 64bit operation system (Does not have to be named xD)
With global stream is meant, that the stream is accessible to all JNI methods.
EDIT:
Okay, 'to let the cat out of the back' I'm using RtAudio.
Realtime C++ Audio Library
Example:
//THIS IS C++ CODE
RtAudio audio(RtAudio::WASAPI);
int callback(//Buffer stuff etc.){
//do something
if(data.isEmpty())return 1;//invokes audio.closeStream() but this does NOT closes the stream!
else return 0; //Go on with the stream rather wait for the next call
}
JNIEXPORT void JNICALL openStream(jintArray data){
//This is a outputstream
audio.openStream(&outputParams,....., &callback,....);
audio.startStream();
}
JNIEXPORT void JNICALL fillData(jintArray data){
//filldata again!
stream.start(); //Starts the stream but does nothing, because the stream is deleted because of Java
}
If I would change the openStream method to this, the stream won't be deleted but I look for a better solution...
JNIEXPORT void JNICALL openStream(jintArray data){
//This is a outputstream
audio.openStream(&outputParams,....., &callback,....);
audio.startStream();
**while(true); //ADD THIS AND THE STREAM WON'T BE DELETED!**
}
Another solution is to add into the RtAudio API a "keepInstanceAliveThread" which is called after the stopStream() method and deleted after calling startStream() or closeStream(). I would rather prefer another solution but at all, there isn't any yet.
Pre-outcomes:
Thanks to #marcinj:
global object are known to cause many problems, its hard to control their construction/destruction.
EDIT:
I found out in the internet (also on stackoverflow), that the destructor is called after the return of a JNI method.
Use a long in the Java object to hold a pointer to the C++ object.
A Java long is 64 bits, and every platform Java runs on has either 32- or 64-bit pointers. And every platform Java is supplied for will support this, despite it not being strictly-conforming C or C++ code.
Java:
// class member
private long audio
// native functions
private native long openStream( int[] data );
private native void deleteStream( long audio );
private native void nativeFillData( long audio, int[] data );
public MyClass()
{
audio = openStream( data );
}
public void fillData( int[] data )
{
nativeFillData( this.audio, data );
}
// delete the C++ object - you may want to
// control this directly and not rely on
// finalize() getting called
protected void finalize()
{
deleteStream( audio );
super.finalize();
}
C++:
JNIEXPORT jlong JNICALL openStream(jintArray data)
{
RtAudio *audio = new RtAudio(RtAudio::WASAPI);
audio->openStream(&outputParams,....., &callback,....);
audio->startStream();
// C-style cast - JNI interface is C, not C++
return( ( jlong ) audio );
}
JNIEXPORT void JNICALL deleteStream(jlong jaudio)
{
RtAudio *audio = static_cast <RtAudio *>( jaudio );
delete audio;
}
JNIEXPORT void JNICALL nativeFillData(jlong jaudio, jintArray data)
{
RtAudio *audio = static_cast <RtAudio *>( jaudio );
audio->start();
...
}
1) JAVA THREAD WAY
We can create a new thread to keep running in a JNI function locked with a monitor or conditional while loop.
Then a separate call would stop the execution of the thread in your function by releasing the monitor or changing the condition in the while loop.
2) JAVA OBJECT REFERENCE WAY
Another option is to create a Global Reference of your object
Android JNI and NewGlobalRef.
Here a separate call on USB disconnect would do DeleteGlobalRef.
We can also move the life cycle of your C++ object into java by passing it back to the java layer
keep some sort of c++ object alive over multiple jni calls.
Here a separate call on USB disconnect would remove any reference to C++ object in your java code.
Implementation
Native File (mynative.cpp)
extern "C" JNIEXPORT jobject JNICALL
Java_com_android_nativecpp_MainActivity_createJniNativeReference(JNIEnv* env, jobject obj, jint size) {
void* buf = malloc(size);
jobject sharedbytebuffer = env->NewDirectByteBuffer(buf, size);
return env->NewGlobalRef(sharedbytebuffer);
}
extern "C" JNIEXPORT void JNICALL
Java_com_android_nativecpp_MainActivity_deleteJniNativeReference(JNIEnv* env, jobject obj, jobject sharedbytebuffer) {
env->DeleteGlobalRef(sharedbytebuffer);
void* buf = env->GetDirectBufferAddress(sharedbytebuffer);
free(buf);
return;
}
Java file (MainActivity.java)
import java.nio.ByteBuffer;
private ByteBuffer mJniReference;
public native ByteBuffer createJniNativeReference(int size);
public native void deleteJniNativeReference(ByteBuffer mJniReference);
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mJniReference = createJniNativeReference(1000);
}
protected void onDestroy() {
deleteJniNativeReference(mJniReference);
super.onDestroy();
}
EXPLAINATION
The reason for either of 1) or 2) is otherwise, the creating frame is then exited from the stack and the JNI local references are deleted.
This will end all the threads in C++ when all std::thread references (which are not detached, ideally) are deleted.
In the non-detached case std::thread destructors are called on the main exit, or when a thread object goes out of scope and then terminate() is called.
In the detached case the detached threads exit on app close which kills the host process. They can also be garbage collected.