No se invoca el método Java cuando se llama desde pthread nativo

Necesito un servicio de Java simple que comience en el cargador del sistema y utilice la biblioteca compartida con algo funcional que utilice los hilos de POSIX. Durante la implementación de interfaz JNI me enfrenté a un problema que no me permite hacer una llamada al método Java de código nativo. GetMethodID () devuelve no NULL así que supongo que lo está haciendo bien. También no hay ningún error sospechoso que puede ayudar. Así que he añadido una gran cantidad de salida para el registro y se preparó una simple prueba de Java para eso. (Todo el código se muestra a continuación, pero el proyecto se puede encontrar en este repositorio en github también).

Lista de los ficheros del proyecto:

Servicio:

  • TestService.java
  • TestController.java
  • TestListener.java
  • TestNative.java << – El método java está aquí

Código nativo:

  • Layer-jni.c << – La llamada nativa está aquí

Otro:

  • Android.mk
  • Application.mk
  • AndroidManifest.xml

A continuación se enumeran todos los archivos.

He probado la siguiente lógica en el servicio de prueba:

1.Servicio se inicia:

TestService.java :

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"); } } 

2.It crea la clase TestController (donde debe ser la mayor parte de la lógica) que crea la clase TestNative.

Interfaz TestListener.java :

 package com.example.testservice; public interface TestListener { public void stringJavaMethod(String regStr); } 

TestController.java :

 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); } 

}

TestNative.java:

 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(); } 

3.TestNative pregunta a la biblioteca nativa que comience a trabajar a través del método startAthread ().
4. El código nativo almacena JVM, hace una referencia global para el objeto llamante e inicia un subproceso.
5.Thread se une a JVM y obtiene un nuevo puntero JNIEnv *. A continuación, busca el ID de método Java que utiliza el enlace de objeto global que se obtuvo en el paso 4 y luego intenta llamar a este método periódicamente.

La única fuente nativa es layer-jni.c :

 #include "logcat.h" #include "layer-jni.h" #include <jni.h> #include <string.h> #include <pthread.h> #include <unistd.h> #include <time.h> #include <stdio.h> #include <stdlib.h> #include <android/asset_manager.h> #include <android/asset_manager_jni.h> JavaVM *jvm = NULL; /* removed jObj UPD3 */ // jobject jObj; /* added see UPD2 */ jclass jCls; /******************/ static int run = 0; static pthread_t t; /* jobject -> 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)->GetEnv(jvm, (void**)&env, JNI_VERSION_1_6)) { if ( 0 != (*jvm)->AttachCurrentThread(jvm, &env, NULL)) { DBG_INFO("Cannot attach JNIEnv!\n"); } } /* UPD2 - jObj -> jCls */ jmethodID jmid = (*env)->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(&ts, NULL); DBG_INFO("Trying to call method\n"); callVoidMethodString(env, jCls, jmid, "***** Native2Java call works! *****\n"); } (*jvm)->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)->GetJavaVM(env, &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)->NewGlobalRef(env, jCls); (*env)->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)->FindClass(env, "com/example/testservice/TestNative"); *if ((*env)->ExceptionCheck(env) == JNI_TRUE){ * (*env)->ExceptionDescribe(env); * DBG_ERR("Exception while looking for TestNative class.\n"); * return; *} *jCls = (jclass)(*env)->NewGlobalRef(env, clazz); * *if ((*env)->ExceptionCheck(env) == JNI_TRUE){ * (*env)->ExceptionDescribe(env); * DBG_ERR("Exception while trying to globalize TestNative class.\n"); * return; *} *(*env)->DeleteLocalRef(env, clazz); */ /*****************/ if (pthread_create(&t, NULL, thread_func, NULL)) { DBG_ERR("Cannot create thread!\n"); } } static unsigned call_count = 0; /* jobject -> jclass */ void callVoidMethodString(JNIEnv *env, jclass jcl, jmethodID jmid, const char *str) { jstring jstr = (*env)->NewStringUTF(env, str); char calls_str[50] = {0}; sprintf(calls_str, "calls:%u\n", call_count++); (*env)->CallVoidMethod(env, jcl, jmid, jstr); if ((*env)->ExceptionCheck(env)) { DBG_ERR("There is some exceptional situation!\n"); (*env)->ExceptionDescribe(env); (*env)->ExceptionClear(env); } (*env)->DeleteLocalRef(env, jstr); DBG_INFO(calls_str); } 

6. Como resultado, el método java "public void stringJavaMethod (final String regStr)" no se invoca mientras se invoca CallVoidMethod () en código nativo. No hay errores y no hay método de llamada …

Salida de registro que obtengo al iniciar el servicio de prueba:

 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 

Por lo tanto, no hay ningún mensaje de código Java sobre la llamada desde código nativo y ninguna invocación de método java. Y ese es el problema. Como resultado planeé ver una cadena "Native2Java call works! \ N" en el registro que se pasa como parámetro a la llamada JNI.

Listado de Android.mk:

 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) 

Application.mk:

 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 

AndroidManifest.xml:

 <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.testservice" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="14" android:targetSdkVersion="17" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"></uses-permission> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/> <uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.RECORD_AUDIO"/> <uses-permission android:name="android.permission.USE_SIP"/> <application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name"> <service android:name=".TestService" android:exported="true"> <intent-filter> <action android:name="com.example.testservice.TestService"> </action> </intent-filter> </service> <receiver android:name=".autostart"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"> </action> </intent-filter> </receiver> </application> </manifest> 

Aprecio altamente si alguien puede ayudarme con un consejo o ideas qué tengo que comprobar / fijar en mi lógica.

UPD: Si cambio el método de Java a la estática, GetMethodID () a GetStaticMethodID () y CallVoidMethod () a CallStaticVoidMethod () comienza a trabajar:

 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 

Todavía no sé la razón por la cual el miembro no estático no está trabajando …

UPD2: Llamada fija GetMethodID () – reemplace jObj con jCls. Si jCls se obtiene a través de FindClass (), el resultado sigue siendo el mismo (sin llamada, sin error). Si jCls se obtiene a través de GetObjectClass () en lugar de FindClass () – entonces se produjo una excepción al intentar obtener el ID del método utilizando este nuevo jCls:

 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) 

UPD3 : Eliminar jObj, sólo jCls se utiliza ahora. Y el parámetro de startAthread se ha corregido. Pero aún no hay error, no hay llamada (método no estático, la versión estática del método está funcionando).

One Solution collect form web for “No se invoca el método Java cuando se llama desde pthread nativo”

jmethodID jmid = (*env)->GetMethodID(env, jObj, "stringJavaMethod", "(Ljava/lang/String;)V");

Este uso de GetMethodID es incorrecto. El segundo parámetro debe ser del tipo jclass, no jobject como lo está utilizando.

Para convertir el jobject en jclass, utilice

jclass GetObjectClass(JNIEnv *env, jobject obj);

Además, debe utilizar GetStaticMethodID como su método de destino es estático

Editar 1 :

Comprobar la firma del método nativo de public static native void startAthread() es incorrecto. Debería ser

 JNIEXPORT void JNICALL Java_Test_startAthread(JNIEnv *, jclass); 

Actualmente

 JNIEXPORT void JNICALL Java_Test_startAthread(JNIEnv *, jobject); 

Esta diferencia explica por qué funciona cuando cambia la función Java correspondiente a estática, ya que en realidad tenía un objeto jclass todo el tiempo y no un jobject.

Editar 2

Ahora que las declaraciones de funciones son correctas, el problema es claro: está intentando llamar a un método de instancia sin una instancia de la clase. Deberá cambiar su función estática startAthread a no estática y guardar el objeto de instancia, o tendrá que cambiar su método Java llamado estático. Basado en su implementación real, elija el que sea adecuado para usted.

  • Detener el servicio en android
  • Servicio remoto, pérdidas de actividad al girar
  • ¿Cómo obtengo el nombre de una clase de servicio en Android?
  • Cómo activar el inicio automático de mi aplicación en xiaomi mediante programación
  • Solicitar contraseña antes de desinstalar la aplicación
  • Cómo programar una ejecución de código en android o: ¿qué son exactamente los hilos de daemon en android?
  • Android Actualizaciones periódicas de la ubicación GPS con AlarmManager dentro de un servicio
  • El servicio android se reinicia en la aplicación matado
  • Proporcionar un servicio de fondo para otras aplicaciones
  • Enfoque eficiente para comprobar continuamente si la conexión a Internet está disponible en Android
  • Actividad abstracta de Android en el manifiesto
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.