Note that there are some explanatory texts on larger screens.

plurals
  1. POJava method is not invoked when calling it from native pthread
    primarykey
    data
    text
    <p>I need a simple Java service which starts at system boot and uses shared library with some functional which uses POSIX threads. While implementing JNI interface I faced with a some problem which doesn't allow me to make a Java method call from native code. GetMethodID() returns not NULL so I suppose it is doing well. Also there is no any suspicious errors which can help. So I added a lot of output to log and prepared a simple java test for that. (all the code is listed below, but project can be found in <a href="https://github.com/YaMike/JavaServiceTest" rel="nofollow">this repository at github</a> as well).<br> <br>List of the files of the project:</p> <p>Service:</p> <ul> <li>TestService.java </li> <li>TestController.java</li> <li>TestListener.java</li> <li><strong>TestNative.java &lt;&lt;-- java method is here</strong></li> </ul> <p>Native code:</p> <ul> <li><strong>layer-jni.c &lt;&lt;-- native call is here</strong></li> </ul> <p>Other:</p> <ul> <li>Android.mk</li> <li>Application.mk</li> <li>AndroidManifest.xml</li> </ul> <p>All files are listed below.</p> <p>I tried the following logic in test service:</p> <p>1.Service is started:</p> <p><strong>TestService.java</strong>:</p> <pre class="lang-java prettyprint-override"><code>package com.example.testservice; import java.lang.String; import android.app.Notification; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.util.Log; import android.widget.Toast; public class TestService extends Service { private Looper mServiceLooper; private ServiceHandler mServiceHandler; private TestController testCtrl = null; private static final String TAG = "TestService"; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { Log.e(TAG, msg.toString()); Log.e(TAG, "IT WORKS"); } } @Override public IBinder onBind(Intent intent) { return null; } public void onDestroy() { Toast.makeText(this, "Test service stopped", Toast.LENGTH_LONG).show(); Log.d(TAG, "Test has been stopped"); } @Override public void onCreate() { super.onCreate(); Log.i(TAG, "onCreate"); HandlerThread thread = new HandlerThread("ServiceStartArguments", android.os.Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mServiceLooper = thread.getLooper(); mServiceHandler = new ServiceHandler(mServiceLooper); } @Override public void onStart(Intent intent, int startid) { if (testCtrl != null) { Log.d(TAG, "Service already running."); return; } Log.d(TAG, "Starting test controller"); testCtrl = new TestController(); Log.d(TAG, "Test controller has started"); } } </code></pre> <p>2.It creates TestController class (where most of the logic must be) which creates TestNative class.</p> <p>interface <strong>TestListener.java</strong>:</p> <pre class="lang-java prettyprint-override"><code>package com.example.testservice; public interface TestListener { public void stringJavaMethod(String regStr); } </code></pre> <p><strong>TestController.java</strong>:</p> <pre class="lang-java prettyprint-override"><code>package com.example.testservice; import android.util.Log; public class TestController implements TestListener { private static final String TAG = "TestController"; private TestNative mTestNative = null; TestController() { Log.d(TAG, "Starting test native"); mTestNative = new TestNative(this); } @Override public void stringJavaMethod(String regStr) { Log.d(TAG, "Callback called!!!!\n"); Log.e(TAG, regStr); } </code></pre> <p>}</p> <p><strong>TestNative.java:</strong></p> <pre class="lang-java prettyprint-override"><code>package com.example.testservice; import android.os.Handler; import android.util.Log; public class TestNative implements TestListener { static { System.loadLibrary("log"); System.loadLibrary("layer-jni"); } private static final String TAG = "TestNative"; private Handler mHandler; private TestListener mDelegate; TestNative(TestListener t) { mDelegate = t; mHandler = new Handler(); startAthread(); } @Override public void stringJavaMethod(final String regStr) { Log.d(TAG, "IT WORKS?" + regStr); mHandler.post(new Runnable() { public void run() { Log.e(TAG, "CALLED!\n"); mDelegate.stringJavaMethod(regStr); } }); } /* native interface */ public static native void startAthread(); } </code></pre> <p>3.TestNative class asks native library to start working via startAthread() method.<br> 4.Native code stores JVM, makes a global reference for calling object and starts a thread.<br> 5.Thread attaches himself to JVM and gets a new JNIEnv* pointer. Then it looks for Java method ID using global object link which was obtained at step#4 and then it tries to call this method periodically.<br></p> <p>The only native source is <strong>layer-jni.c</strong>: </p> <pre><code>#include "logcat.h" #include "layer-jni.h" #include &lt;jni.h&gt; #include &lt;string.h&gt; #include &lt;pthread.h&gt; #include &lt;unistd.h&gt; #include &lt;time.h&gt; #include &lt;stdio.h&gt; #include &lt;stdlib.h&gt; #include &lt;android/asset_manager.h&gt; #include &lt;android/asset_manager_jni.h&gt; JavaVM *jvm = NULL; /* removed jObj UPD3 */ // jobject jObj; /* added see UPD2 */ jclass jCls; /******************/ static int run = 0; static pthread_t t; /* jobject -&gt; jclass */ void callVoidMethodString(JNIEnv *env, jclass jcl, jmethodID jmid, const char *str); JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *ajvm, void *dummy) { return JNI_VERSION_1_6; } void *thread_func(void *dummy) { run = 1; JNIEnv *env = NULL; if (JNI_EDETACHED == (*jvm)-&gt;GetEnv(jvm, (void**)&amp;env, JNI_VERSION_1_6)) { if ( 0 != (*jvm)-&gt;AttachCurrentThread(jvm, &amp;env, NULL)) { DBG_INFO("Cannot attach JNIEnv!\n"); } } /* UPD2 - jObj -&gt; jCls */ jmethodID jmid = (*env)-&gt;GetMethodID(env, jCls, "stringJavaMethod", "(Ljava/lang/String;)V"); if (!jmid) { DBG_ERR("Cannot find java method...Terminating\n"); return NULL; } while(run) { struct timespec ts = {.tv_sec = 1, .tv_nsec = 0 }; nanosleep(&amp;ts, NULL); DBG_INFO("Trying to call method\n"); callVoidMethodString(env, jCls, jmid, "***** Native2Java call works! *****\n"); } (*jvm)-&gt;DetachCurrentThread(jvm); return NULL; } JNIEXPORT void JNICALL Java_com_example_testservice_TestNative_startAthread( JNIEnv* env, jobject thiz) { DBG_INFO("enter startAthread()\n"); if (JNI_OK != (*env)-&gt;GetJavaVM(env, &amp;jvm)) { DBG_ERR("Cannot access Java VM! Terminating call.\n"); return; } DBG_INFO("Caching class tc...\n"); /* Updated: jObj replaced with jCls */ jCls = thiz; jobject globalRef = (*env)-&gt;NewGlobalRef(env, jCls); (*env)-&gt;DeleteLocalRef(env, jCls); jCls = globalRef; if (NULL == jCls) { DBG_ERR("Cannot cache class TronNative!\n"); return; } /* UPD3: removed block below */ /* Added see UPD2 */ /*DBG_INFO("Caching class TestNative...\n"); *jclass clazz = (*env)-&gt;FindClass(env, "com/example/testservice/TestNative"); *if ((*env)-&gt;ExceptionCheck(env) == JNI_TRUE){ * (*env)-&gt;ExceptionDescribe(env); * DBG_ERR("Exception while looking for TestNative class.\n"); * return; *} *jCls = (jclass)(*env)-&gt;NewGlobalRef(env, clazz); * *if ((*env)-&gt;ExceptionCheck(env) == JNI_TRUE){ * (*env)-&gt;ExceptionDescribe(env); * DBG_ERR("Exception while trying to globalize TestNative class.\n"); * return; *} *(*env)-&gt;DeleteLocalRef(env, clazz); */ /*****************/ if (pthread_create(&amp;t, NULL, thread_func, NULL)) { DBG_ERR("Cannot create thread!\n"); } } static unsigned call_count = 0; /* jobject -&gt; jclass */ void callVoidMethodString(JNIEnv *env, jclass jcl, jmethodID jmid, const char *str) { jstring jstr = (*env)-&gt;NewStringUTF(env, str); char calls_str[50] = {0}; sprintf(calls_str, "calls:%u\n", call_count++); (*env)-&gt;CallVoidMethod(env, jcl, jmid, jstr); if ((*env)-&gt;ExceptionCheck(env)) { DBG_ERR("There is some exceptional situation!\n"); (*env)-&gt;ExceptionDescribe(env); (*env)-&gt;ExceptionClear(env); } (*env)-&gt;DeleteLocalRef(env, jstr); DBG_INFO(calls_str); } </code></pre> <p>6.As a result java method "public void stringJavaMethod(final String regStr)" is not invoked while CallVoidMethod() in native code is called. No any errors and no method call...</p> <p>Log output I get while starting test service:</p> <pre><code>11-12 14:05:05.396: I/GAV2(18672): Thread[GAThread,5,main]: No campaign data found. 11-12 14:05:05.586: I/Autostart(16419): Starting service... 11-12 14:05:05.586: D/dalvikvm(18934): Late-enabling CheckJNI 11-12 14:05:05.586: I/ActivityManager(441): Start proc com.example.testservice for service com.example.testservice/.TestService: pid=18934 uid=10097 gids={50097, 3003, 1028} 11-12 14:05:05.606: D/dalvikvm(18934): Debugger has detached; object registry had 1 entries 11-12 14:05:05.696: D/dalvikvm(441): GC_EXPLICIT freed 1485K, 39% free 16961K/27776K, paused 3ms+7ms, total 89ms 11-12 14:05:05.736: I/TestService(18934): onCreate 11-12 14:05:05.736: D/TestService(18934): Starting test controller 11-12 14:05:05.736: D/TestController(18934): Starting test native 11-12 14:05:05.736: D/dalvikvm(18934): No JNI_OnLoad found in /system/lib/liblog.so 0x420e0a08, skipping init 11-12 14:05:05.736: D/dalvikvm(18934): Trying to load lib /data/app-lib/com.example.testservice-1/liblayer-jni.so 0x420e0a08 11-12 14:05:05.736: D/dalvikvm(18934): Added shared lib /data/app-lib/com.example.testservice-1/liblayer-jni.so 0x420e0a08 11-12 14:05:05.736: I/JNITestService(18934): enter startAthread() 11-12 14:05:05.736: I/JNITestService(18934): Caching class tc... 11-12 14:05:05.736: D/TestService(18934): Test controller has started 11-12 14:05:06.736: I/JNITestService(18934): Trying to call method 11-12 14:05:06.736: I/JNITestService(18934): calls:0 11-12 14:05:07.736: I/JNITestService(18934): Trying to call method 11-12 14:05:07.736: I/JNITestService(18934): calls:1 ...etc </code></pre> <p>Thus there are no any messages from Java code about call from native code and no java method invocation. And that's the problem. As a result I planned to see a string "<strong><em></strong> Native2Java call works! <strong></em></strong>\n" in the log which is passed as parameter to JNI call.</p> <p>Listing of Android.mk:</p> <pre><code>LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) ANDROID_NDK := /home/michael/Downloads/android-ndk-r8e LOCAL_MODULE := layer-jni LOCAL_SRC_FILES := layer-jni.c LOCAL_LDLIBS := -llog TARGET_ARCH_ABI := armeabi-v7a include $(BUILD_SHARED_LIBRARY) </code></pre> <p>Application.mk:</p> <pre><code>APP_ABI := armeabi-v7a APP_PLATFORM := android-9 #APP_CPPFLAGS := -D__GXX_EXPERIMENTAL_CXX0X__ -std=gnu++11 STLPORT_FORCE_REBUILD := true APP_STL := stlport_shared </code></pre> <p>AndroidManifest.xml:</p> <pre class="lang-xml prettyprint-override"><code>&lt;manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testservice" android:versionCode="1" android:versionName="1.0" &gt; &lt;uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" /&gt; &lt;uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"&gt;&lt;/uses-permission&gt; &lt;uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/&gt; &lt;uses-permission android:name="android.permission.INTERNET"/&gt; &lt;uses-permission android:name="android.permission.RECORD_AUDIO"/&gt; &lt;uses-permission android:name="android.permission.USE_SIP"/&gt; &lt;application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name"&gt; &lt;service android:name=".TestService" android:exported="true"&gt; &lt;intent-filter&gt; &lt;action android:name="com.example.testservice.TestService"&gt; &lt;/action&gt; &lt;/intent-filter&gt; &lt;/service&gt; &lt;receiver android:name=".autostart"&gt; &lt;intent-filter&gt; &lt;action android:name="android.intent.action.BOOT_COMPLETED"&gt; &lt;/action&gt; &lt;/intent-filter&gt; &lt;/receiver&gt; &lt;/application&gt; &lt;/manifest&gt; </code></pre> <p>I highly appreciate if somebody can help me with an advice or ideas what I have to check/to fix in my logic.</p> <p><strong>UPD:</strong> If I change Java method to static, GetMethodID() to GetStaticMethodID() and CallVoidMethod() to CallStaticVoidMethod() it starts working:</p> <pre><code>11-12 17:44:27.406: D/TestNative(21444): IT WORKS?***** Native2Java call works! ***** 11-12 17:44:27.406: I/JNITestService(21444): calls:38 11-12 17:44:28.406: I/JNITestService(21444): Trying to call method 11-12 17:44:28.406: D/TestNative(21444): IT WORKS?***** Native2Java call works! ***** 11-12 17:44:28.406: I/JNITestService(21444): calls:39 11-12 17:44:29.426: I/JNITestService(21444): Trying to call method </code></pre> <p>Still don't know the reason why non-static member is not working...</p> <p><strong>UPD2:</strong> Fixed call GetMethodID() - replace jObj with jCls. If jCls is obtained via FindClass() then result remains the same (no call, no error). If jCls is obtained via GetObjectClass() instead of FindClass() - then exception occured while trying to get method id using this new jCls: </p> <pre><code>11-12 18:17:59.926: D/TestService(22540): Test controller has started 11-12 18:17:59.926: E/JNITestService(22540): Cannot find java method...Terminating 11-12 18:17:59.926: W/dalvikvm(22540): threadid=12: thread exiting with uncaught exception (group=0x4198b700) 11-12 18:17:59.926: E/AndroidRuntime(22540): FATAL EXCEPTION: Thread-6532 11-12 18:17:59.926: E/AndroidRuntime(22540): java.lang.NoSuchMethodError: no method with name='stringJavaMethod' signature='(Ljava/lang/String;)V' in class Ljava/lang/Class; 11-12 18:17:59.926: E/AndroidRuntime(22540): at dalvik.system.NativeStart.run(Native Method) </code></pre> <p><strong>UPD3</strong>: Remove jObj, only jCls is used now. And parameter of startAthread has been fixed. But still no error, no call (non-static method, static version of method is working).</p>
    singulars
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload