从 native pthread 调用时不调用 Java 方法

标签 java android service android-ndk java-native-interface

我需要一个简单的 Java 服务,它在系统启动时启动,并使用共享库和一些使用 POSIX 线程的功能。在实现 JNI 接口(interface)时,我遇到了一些问题,它不允许我从 native 代码调用 Java 方法。 GetMethodID() 不返回 NULL,所以我认为它运行良好。也没有任何可以提供帮助的可疑错误。所以我在日志中添加了很多输出,并为此准备了一个简单的 Java 测试。 (所有代码都在下面列出,但项目也可以在this repository at github中找到)。

项目文件列表:

服务:

  • 测试服务.java
  • 测试 Controller .java
  • TestListener.java
  • TestNative.java <<-- java 方法在这里

native 代码:

  • layer-jni.c <<-- 本地调用在这里

其他:

  • Android.mk
  • 应用.mk
  • AndroidManifest.xml

下面列出了所有文件。

我在测试服务中尝试了以下逻辑:

1.服务启动:

测试服务.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.它创建 TestController 类(大部分逻辑必须在其中),该类创建 TestNative 类。

接口(interface)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* 指针。然后它使用在步骤#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.因此在调用 native 代码中的 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 代码的任何关于来自 native 代码的调用的消息,也没有 Java 方法调用。这就是问题所在。结果我计划在日志中看到一个字符串“ Native2Java call works! \n”作为参数传递给 JNI 调用。

Android.mk list :

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() - 用 jCls 替换 jObj。如果通过 FindClass() 获得 jCls,则结果保持不变(无调用,无错误)。如果 jCls 是通过 GetObjectClass() 而不是 FindClass() 获得的——那么在尝试使用这个新的 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 的参数。但仍然没有错误,没有调用(非静态方法,方法的静态版本正在运行)。

最佳答案

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

这种 GetMethodID 的用法是不正确的。第二个参数应该是 jclass 类型,而不是您正在使用的 jobject。

要将 jobject 转换为 jclass,请使用

jclass GetObjectClass(JNIEnv *env, jobject obj);

此外,您应该使用 GetStaticMethodID,因为您的目标方法是静态的

编辑 1:

检查 public static native void startAthread() 的本地方法签名不正确。应该是

JNIEXPORT void JNICALL Java_Test_startAthread(JNIEnv *, jclass);

目前

JNIEXPORT void JNICALL Java_Test_startAthread(JNIEnv *, jobject);

这种差异解释了为什么当您将相应的 Java 函数更改为静态时它会起作用,因为您实际上一直都有一个 jclass 对象而不是一个 jobject。

编辑2

既然函数声明是正确的,问题就很清楚了:您正试图在没有类实例的情况下调用实例方法。您需要将静态 startAthread 函数更改为非静态函数并保存实例对象,或者您需要将调用的 Java 方法更改为静态函数。根据您的实际实现情况,选择适合您的实现方式。

关于从 native pthread 调用时不调用 Java 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19926657/

相关文章:

c++ - 我的 native Win32 服务是否需要单独的消息文件库?

service - Windows服务 "Starting"

java - 如何检查Map<String, Set<String>>中有多少个HashSet

java - 在 Android 中清除 Glide 图像缓存

java - 如何使用addView添加CustomView?

android - 为什么自定义微调器在选择 Android 中的项目后不隐藏它的下拉菜单?

java - 如何使用 Oracle UCP 在运行时为 Tomcat 数据源添加用户名和密码

java - FacesContext 中的 getExternalContext 中的 NullpointerException

android - 如何知道图像是否仍然存在

powershell - 如何使用 PowerShell 提取所有服务的 "Path to executable"