Метод Java не вызывается при вызове его из native pthread

Мне нужна простая Java-служба, которая начинается при загрузке системы и использует общую библиотеку с некоторым функционалом, который использует потоки POSIX. Во время реализации интерфейса JNI я столкнулся с некоторой проблемой, которая не позволяет мне вызвать вызов метода Java из собственного кода. GetMethodID () возвращает не NULL, поэтому я полагаю, что это хорошо. Также нет никаких подозрительных ошибок, которые могут помочь. Поэтому я добавил много результатов для регистрации и подготовил простой Java-тест для этого. (Весь код указан ниже, но проект также можно найти в этом репозитории в github ).

Список файлов проекта:

Обслуживание:

  • TestService.java
  • TestController.java
  • TestListener.java
  • TestNative.java << – метод java здесь

Родной код:

  • Layer-jni.c << – родной звонок здесь

Другие:

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

Все файлы перечислены ниже.

В тестовой службе я пробовал следующую логику:

1.Служба запущена:

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 создает класс TestController (где большая часть логики должна быть), которая создает класс TestNative.

Интерфейс 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 просит, чтобы исходная библиотека начала работать с помощью метода startAthread ().
4.Native код хранит JVM, делает глобальную ссылку для вызова объекта и запускает поток.
5.Thread присоединяется к JVM и получает новый указатель JNIEnv *. Затем он ищет идентификатор метода Java, используя глобальную ссылку на объект, которая была получена на шаге 4, а затем пытается вызвать этот метод периодически.

Единственным родным источником является 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. В результате метод java «public void stringJavaMethod (final String regStr)» не вызывается, пока вызывается CallVoidMethod () в собственном коде. Никаких ошибок и никакого вызова метода …

Выход в журнал я получаю при запуске службы тестирования:

 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 

Таким образом, нет никаких сообщений из кода Java о вызове из собственного кода и вызове метода java. И в этом проблема. В результате я планировал увидеть строку «Native2Java call works! \ N» в журнале, который передается как параметр для вызова JNI.

Список 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> 

Я очень признателен, если кто-нибудь может помочь мне с советами или идеями, что я должен проверить / исправить в своей логике.

UPD: если я изменил метод Java на static, GetMethodID () на GetStaticMethodID () и CallVoidMethod () на CallStaticVoidMethod (), он начинает работать:

 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 

Все еще не знаю, почему нестатический член не работает …

UPD2: Исправлен вызов GetMethodID () – заменить jObj на jCls. Если jCls получается через FindClass (), результат остается тем же (без вызова, без ошибок). Если jCls получается через GetObjectClass () вместо FindClass (), то при попытке получить идентификатор метода с помощью этого нового 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 : удалить jObj, теперь используется только jCls. И параметр startAthread исправлен. Но по-прежнему нет ошибки, нет вызова (нестатический метод, статическая версия метода работает).

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

Это использование GetMethodID неверно. Второй параметр должен иметь тип jclass, а не такой, какой вы используете.

Чтобы преобразовать задание в jclass, используйте

jclass GetObjectClass(JNIEnv *env, jobject obj);

Кроме того, вы должны использовать GetStaticMethodID поскольку ваш целевой метод является статическим

Изменить 1 :

public static native void startAthread() подпись встроенного метода public static native void startAthread() . Должен быть

 JNIEXPORT void JNICALL Java_Test_startAthread(JNIEnv *, jclass); 

В настоящее время

 JNIEXPORT void JNICALL Java_Test_startAthread(JNIEnv *, jobject); 

Эта разница объясняет, почему она работает, когда вы меняете соответствующую функцию Java на статическую, поскольку на самом деле у вас на самом деле был объект jclass, а не объект job.

Изменить 2

Теперь, когда объявления функций верны, проблема очевидна: вы пытаетесь вызвать метод-экземпляр без экземпляра класса. Вам нужно либо изменить статическую функцию startAthread на нестатический, либо сохранить объект экземпляра, либо вам придется изменить свой метод Java на статический. На основе вашей фактической реализации выберите тот, который подходит именно вам.