I have a service that can either be started with a launcher or runs on boot. The problem is that when I call a C function with JNI, all the Toast notifications are no longer displayed, whereas if I call only the java method, the Toast notifications display just fine. What causes this and how can I fix it?
Note:
If the C function does something simple like calls back to java and terminates, the Toast notifications will display. My function however, has an infinite loop used to handle interrupts.
ServiceLauncher
public class ServiceLauncher extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
Toast.makeText(getBaseContext(), "onCreate", Toast.LENGTH_LONG).show();
super.onCreate(savedInstanceState);
startService(new Intent(this, MyService.class));
finish();
}
}
MyService
public class MyService extends Service {
#Override
public IBinder onBind(Intent intent) {
return null;
}
public void onDestroy() {
Toast.makeText(this, "Terminated", Toast.LENGTH_LONG).show();
}
#Override
public int onStartCommand(Intent intent,int flags, int startid) {
Toast.makeText(this, "Running", Toast.LENGTH_LONG).show();
javaMethod(); /*toast displays if this is called*/
cMethodFromJNI(); /*toast does not display if this is called*/
return START_STICKY;
}
}
C code structure
JNIEXPORT jint JNICALL
Java_className_cMethodFromJNI(JNIEnv *env, jclass type) {
jmethodID mid = (*env)->GetStaticMethodID(env, type, "javaMethod", "(I)V");
// open and read some fd (not shown)
for (;;) {
// wait on change in fd and act on it (not shown)
callJavaAgain(env,type,mid)
}
}
//close fd (not shown)
exit(0);
}
EDIT: I've changed the code like so, following the answer below. Seems like the situation is exactly the same as before, perhaps I am missing something.
Revised C code structure
JNIEXPORT jint JNICALL
Java_className_cMethodFromJNI(JNIEnv * env, jclass type) {
/*cache JVM*/
int status = ( * env) - > GetJavaVM(env, & jvm);
if (status != 0) {
LOGD("failed to retrieve *env");
exit(1);
}
attachedThread();
}
void attachedThread() {
/* get a new environment and attach a new thread */
JNIEnv * newEnv;
JavaVMAttachArgs args;
args.version = JNI_VERSION_1_6; // choose your JNI version
args.name = NULL; // if you want to give the java thread a name
args.group = NULL; // you can assign the java thread to a ThreadGroup
( * jvm) - > AttachCurrentThread(jvm, & newEnv, & args);
jclass cls = ( * newEnv) - > FindClass(newEnv, "fully/qualified/class/name");
jmethodID mid = ( * newEnv) - > GetStaticMethodID(newEnv, cls, "callJavaAgain", "(V)V");
// open and read some fd (not shown)
for (;;) {
// wait on change in fd and act on it (not shown)
intermediaryFunction(newEnv, cls, mid);
}
}
//close fd (not shown)
exit(0);
}
void intermediaryFunction(JNIEnv * newEnv, jclass cls, jmethodID mid) {
//do some stuff (not shown)
( * newEnv) - > CallStaticVoidMethod(newEnv, cls, mid);
}
okay so this topic requires some careful attention. Android docs talk about JavaVM and JNIEnv and how to interface with them. What you will need to do is store a global reference to the JavaVM that's accessible from all threads in the C code. The JVM allows a thread to get access to the JNIEnv which can get you access to a class's methods and what not. The next key problem is that you need to stash a jobject 'pointer' to the android object you call back to from the C code (which you probably already know). Make sure you call JNIEnv->DetachThread on the thread that acquired the JNIEnv reference.
so again:
get JavaVM reference and store it in c code
have java call c code to store the jobject reference for your callback
start a thread to do looping, acquire a reference to a JNIEnv on that thread to call to java
call DetachThread before terminating thread execution
another point of trouble is accessing c++ objects from java code, there's a neat trick where you can have c++ code create the java objects, and assign into a private long _cppPointer member on the created Java class, then add this object to some collection on a java object you have already pointer to (like an activity or something)
good luck.
edit: i finally recalled another great source of information about JNI work
Related
I am building an android app and I need to create UI elements dynamically from JNI part and assign C++ functions as click handler.
I have defined a java function to create a button and return it.
I have a C++ function that calls the Java function and has jobject of button. Now I want to assign another C++ function as click handler to this object.
Java:
public class MainActivity extends AppCompatActivity {
...
public View control(String text) {
Button bt = new Button(this);
bt.setText(text);
((ConstraintLayout)findViewById(R.id.layout_main)).addView(bt);
return bt;
}
}
C++:
extern "C" JNIEXPORT void JNICALL Java_com_shaidin_ebellum_MainActivity_start(JNIEnv *env, jobject me)
{
jmethodID jControl = env->GetMethodID(env->GetObjectClass(me), "control", "(Ljava/lang/String;)Landroid/view/View;");
jstring jText = env->NewStringUTF("test");
jobject jView = env->CallObjectMethod(me, jControl, jText);
// How to add listener here?
env->DeleteLocalRef(jView_);
env->DeleteLocalRef(jText);
}
In this question
Android UI in C++
JNIpp library
https://github.com/DmitrySkiba/itoa-jnipp
has introduced and it does exactly what I want, but I want to implement it myself because I don't want to add that much code to my project. Also that library has a complicated implementation using nested macros that makes it difficult to learn how it works.
The process of making a Java object do a callback to C++ code is pretty much always the same; make the Java object store some kind of reference to the native object (typically a pointer), and have it pass that pointer back to the C++ code which can call the appropriate function.
A potential implementation could look like this:
MyOnClickListener.java
public class MyOnClickListener implements View.OnClickListener {
private final long nativePeer;
public MyOnClickListener(final long nativePeer) {
this.nativePeer = nativePeer;
}
#Override
public void onClick(View v) {
OnClick(nativePeer);
}
#Override
public void finalize() {
Release(nativePeer);
}
private static native void OnClick(final long peer);
private static native void Release(final long peer);
}
C++
extern "C" JNIEXPORT void Java_com_example_myapp_MyOnClickListener_OnClick(
JNIEnv *env,
jclass clazz,
jlong nativePeer) {
auto f = reinterpret_cast<std::function<void(void)>*>(nativePeer);
(*f)();
}
extern "C" JNIEXPORT void Java_com_example_myapp_MyOnClickListener_Release(
JNIEnv *env,
jclass clazz,
jlong nativePeer) {
auto f = reinterpret_cast<std::function<void(void)>*>(nativePeer);
delete f;
}
...
// Setting the OnClickListener
// We allocate this object with `new` since we need it to remain alive
// until the Java code no longer needs it. It is the responsibility of
// the Java code to ensure that the memory gets freed.
auto callback = new std::function<void(void)>([] {
__android_log_print(ANDROID_LOG_WARN, "MyOnClickListener", "Hello from native onClick!");
});
jclass listener_clazz = env->FindClass("com/example/myapp/MyOnClickListener");
jmethodID new_listener = env->GetMethodID(listener_clazz, "<init>", "(J)V");
jobject listener = env->NewObject(listener_clazz, new_listener, callback);
jmethodID set_onclicklistener = env->GetMethodID(button_clazz, "setOnClickListener", "(Landroid/view/View$OnClickListener;)V");
env->CallVoidMethod(button, set_onclicklistener, listener);
Note 1: In your real code you should of course add proper error handling and not blindly assume that every JNI call succeeds.
Note 2: Rather than relying on finalize to be called you might want to explicitly invoke the Release function when you no longer need the native peer object.
Note 3: I used a pointer to a std::function in my example. You may want to use a pointer to some class of your own. That's really up to you to decide.
I've been trying for a while to call a java method from c++, I am able to do it successfully with an Activity class but with a NativeActivity, it crashes when calling CallVoidMethod. GetMethodID is able to find it, it returns an address. env & vm objects are valid & are populated from android_main(). Is it possible that it simply won't work with a native activity class?
Cpp: (edited)
void SendNotification() {
JavaVM* lJavaVM = main_activity->vm;
JNIEnv* lJNIEnv = main_activity->env;
JavaVMAttachArgs lJavaVMAttachArgs;
lJavaVMAttachArgs.version = JNI_VERSION_1_6;
lJavaVMAttachArgs.name = "NativeThread";
lJavaVMAttachArgs.group = NULL;
jint lResult = lJavaVM->AttachCurrentThread(&lJNIEnv, &lJavaVMAttachArgs);
if (lResult == JNI_ERR)
return;
jobject lNativeActivity = main_activity->clazz;
jclass ClassNativeActivity = lJNIEnv->GetObjectClass(main_activity->clazz);
jmethodID _method = lJNIEnv->GetMethodID(ClassNativeActivity, "SendNotification", "()V");
lJNIEnv->CallVoidMethod(lNativeActivity, _method);
lJavaVM->DetachCurrentThread();
}
Java:
package com.thor.kalen;
import android.app.AlertDialog;
import android.app.NativeActivity;
import android.os.Bundle;
public class MainActivity extends NativeActivity
{
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
} // End of public void onCreate(Bundle savedInstanceState)
public void SendNotification() {
new AlertDialog.Builder(this).setTitle("Alert").setMessage("").setNeutralButton("Close", null).show();
}
}
com.thor.kalen.MainActivity.SendNotification() Java method should be called for a jobject of class com.thor.kalen.MainActivity, not a jclass of this object: it's not a static method:
main_activity->env->CallVoidMethod(main_activity.clazz, _method)
Note the comment in native_activity.h:
/**
* The NativeActivity object handle.
*
* IMPORTANT NOTE: This member is mis-named. It should really be named
* 'activity' instead of 'clazz', since it's a reference to the
* NativeActivity instance created by the system for you.
*
* We unfortunately cannot change this without breaking NDK
* source-compatibility.
*/
jobject clazz;
Also, you can only show() an AlertDialog from the Main (UI) thread. Your C++ code suggests that you do you from a background thread.
If this code executes on the main thread, then
main_activity->vm->DetachCurrentThread()
should be removed. AttachThread() can be removed, too, but it's a NOP when called on a thread that is already attached.
I'm currently working on (an existing) Android app using native c++ code. I want send a broadcast to another Android Java app listening to a BroadcastReceiver. The receiving app is yet working, but I can't figure out how to broadcast data from JNI code either directly or by sending the values to java code and them broadcasting it from Java.
I hope my explanaion is clear. Can someone help me on how to accomplish this?
Thanks!
The easiest way is probably to do it in Java and write a JNI callback to call that Java function. If you do need to do it in C, you'll need to use JNI to create an Intent, then to set the action and extras of the intent, then use JNI to call the sendBroadcast method of your activity to actually send it. Its a lot of boilerplate JNI code, so I'd try to avoid it.
The easiest way is to implement all logic in a Java method:
public class MyClass {
static {
System.loadLibrary("mylib_jni");
}
public static final String ACTION_TEST = "com.example.action.TEST";
private final LocalBroadCastManager broadcastManager;
public MyClass(Context context) {
broadcastManager = LocalBroadcastManager.getInstance(context);
}
#SuppressWarnings("unused")
private void onSendText(String text) {
final Intent intent = new Intent(ACTION_TEST).putExtra(Intent.EXTRA_TEXT, text);
broadcastManager.sendBroadcast(intent);
}
private native void sendText();
}
and just call that method from JNI:
JNIEXPORT void JNICALL Java_com_example_MyClass_1sendText(JNIEnv *env, jobject instance) {
jclass clazz = (*env)->GetObjectClass(env, instance);
jmethodID method = (*env)->GetMethodID(env, clazz, "onSendText", "(Ljava/lang/String;)V");
jstring text = (*env)->NewString(env, "test message");
(*env)->CallVoidMethod(env, instance, method, text);
}
I'm experiencing a very strange bug, which I'm thinking might be an environment issue, but I'd like to know if anyone else here is experiencing something similar.
First of all, up until now, everything worked perfectly.
I have a project which works with a jni interface.
Here's the call from my java code:
myJniWrapper.load(CallbackActivity.this); // this code is inside a Runnable
Here's the load method:
public void load(Activity activityCalledFrom) {
if (mLoaded == false) {
loadNative(mNativeHandle, activityCalledFrom); // This is being called correctly.
}
}
private native void loadNative(long nativeHandle, Object activityCalledFrom);
Here's the c++:
JNIEXPORT void JNICALL Java_com_datalogics_dlreader_jni_RMBook_loadNative
(JNIEnv *env, jobject obj, jlong handle, jobject activityCalledFrom)
{
if (handle)
{
jmethodID meth = NULL;
// Convert the long back into a pointer
RMBookNative *theBook = (RMBookNative *) handle;
// Ensure the pointer is valid
if (theBook != NULL)
{
// Get the activity that we were called from
theBook->libraryActivityObj = env->NewGlobalRef(activityCalledFrom);
// Call loadDocument to set the URL for the dpdoc::Document
theBook->loadDocument();
}
}
}
Later on, once the loading is finished, the following code is called in the C++ code:
if (libraryActivityObj != NULL)
{
// Find the loadingFinished method on the library class
meth = jenv->GetMethodID(libraryActivityCls, "loadingFinished", "()V");
// Call the method on the activity
jenv->CallVoidMethod(libraryActivityObj, meth);
}
Then, back in the same activity, I have the following method:
public void loadingFinished()
{
Log.i(getClass().getSimpleName(), "loading finished");
doStuff();
}
The strange thing is, everything is working (at least according to the logs) - and when the c++ tries to call the loadingFinished() method, it instead invokes some random public method in the activity I'm sending - or to be precise, it will call the same random method every time, but if I add another method in the activity, a different method will be called by the tag[:C++] code. It seems to randomly choose a method to call based on the total amount of methods in the activity.
I have tried: cleaning & rebuilding, restarting eclipse, restarting my Mac, restarting the device - nothing helps.
I do not understand what is happening and was hoping if anyone had any insight.
Please let me know if you need me to post more code / general info.
I have a JNI code and I am trying to call a static method in Java.
The OnLoad in JNI looks like below
extern "C" jint JNI_OnLoad(JavaVM *jvm, void *reserved) {
JNIEnv *e;
mJvm = jvm;
if (jvm->GetEnv((void**) &e, JNI_VERSION_1_4) != JNI_OK) {
ALOGE("Error getting Env var");
return JNI_ERR;
}
ALOG_ASSERT(env, "Could not retrieve the env!");
app = e->FindClass("xyz");
updateService = e->GetStaticMethodID(app, "updatestatus", "(I)V");
jniRegisterNativeMethods(e, "xyz",
Methods, NELEM(Methods));
return JNI_VERSION_1_4;
}
I have following global variables
JavaVM *mJvm;
jclass app;
jmethodID updateHDToService;
The callback where I am trying to call the Java method looks like this
JNIEnv *e;
if (!mIsListenerAttached) {
if (mJvm->AttachCurrentThread(&e, NULL) != JNI_OK) {
ALOGE("Error attaching native thread to java");
mIsListenerAttached = false;
return;
}
ALOGI("Aattached native thread to java");
mIsListenerAttached = true;
}
e->CallStaticVoidMethod(app, updateHDToService, radioContext->signalStat);
The static method in Java is called successfully only one time. Next time the same callback is executed it does not call the java method.
What am I doing wronG?
Please have a look at http://developer.android.com/training/articles/perf-jni.html. You don't need the protection variable, Calling AttachCurrentThread on an already-attached thread is a no-op..
But don't forget to call mJvm->detachCurrentThread() before the native thread exits!