システムの起動時に開始され、POSIX スレッドを使用するいくつかの機能を備えた共有ライブラリを使用する単純な Java サービスが必要です。JNI インターフェイスを実装しているときに、ネイティブ コードから Java メソッドを呼び出せないという問題に直面しました。GetMethodID() は NULL を返さないので、うまくいっていると思います。また、役立つ疑わしいエラーはありません。そのため、ログに多くの出力を追加し、そのための簡単な Java テストを用意しました。(すべてのコードは以下にリストされていますが、プロジェクトはgithub のこのリポジトリにもあります)。
プロジェクトのファイルのリスト:
サービス:
- TestService.java
- TestController.java
- TestListener.java
- TestNative.java <<-- Java メソッドはこちら
ネイティブ コード:
- layer-jni.c <<-- ネイティブ呼び出しはこちら
他の:
- Android.mk
- アプリケーション.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. TestNative クラスを作成する TestController クラス (ほとんどのロジックが必要な場所) を作成します。
インターフェイス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.ネイティブ コードは JVM を格納し、オブジェクトを呼び出すためのグローバル参照を作成し、スレッドを開始します。
5.スレッドが JVM にアタッチし、新しい JNIEnv* ポインターを取得します。次に、手順 4 で取得したグローバル オブジェクト リンクを使用して Java メソッド ID を検索し、定期的にこのメソッドを呼び出そうとします。
唯一のネイティブ ソースは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.その結果、ネイティブ コードの CallVoidMethod() が呼び出されている間、Java メソッド「public void stringJavaMethod(final String regStr)」が呼び出されません。エラーもメソッド呼び出しもありません...
テスト サービスの開始中に取得したログ出力:
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 呼び出しが機能します。\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)
アプリケーション.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 メソッドを静的に変更すると、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() を介して取得された場合、結果は同じままです (呼び出しなし、エラーなし)。FindClass() ではなく GetObjectClass() を介して jCls を取得した場合、この新しい jCls を使用してメソッド ID を取得しようとしたときに例外が発生しました。
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 のパラメータが修正されました。しかし、まだエラーも呼び出しもありません (非静的メソッド、メソッドの静的バージョンが機能しています)。