Android jni call to java

Вызов Java методов из C/C++ кода при помощи JNI на Android.

Вызов Java методов из C/C++ кода при помощи JNI на Android.

В прошлой статье посвященной JNI на Android я описывал как начать работу с Android NDK, в том числе, как вызывать нативные методы из Java, на этот раз я опишу как вызывать Java-методы из нативного кода.

Для лучшего понимания этой статьи, рекомендую предварительно ознакомиться с основами JNI в Android. Сам процесс вызова Java-метода из нативного кода далее мы будем называть обратным вызовом, так как такие вызовы чаще всего используются для оповещения Java-стороны приложения о некоторых событиях, просходящих «в нативе». Код доступен на нашем репозитории на GitHub. Для нетерпеливых есть быстрое решение

Общее описание механизма

И так, перед нами стала задача «дернуть» Java-метод из нативного кода. Каков будет порядок действий?

  1. Опеределить какой метод и у какого класса вы хотите вызвать. Банально, да.
  2. Получить дескриптор нужного класса.
  3. Описать сигнатуру метода на языке JNI-примитивов (не так страшно, как звучит).
  4. Получить идентификатор метода (что-то типа ссылки на метод).
  5. Вызвать метод у нужного объекта (если метод экземплярный) или класса (если метод статический).

Определяемя с методами

Я предлагаю всегда делать методами обратного вызова методы интерфейса, а не конкретного класса. У такого подхода есть преимуществ, начиная с того, что можно будет без труда подменять Java-реалзиацию и определять ряд Java-объектов для обработки которых в нативном коде будет достаточно всего одного решения на нативном уровне.

Читайте также:  Название файла в питон

Пусть у нас есть следующий интерфейс NativeCallListener :

public interface NativeCallListener <
public void onNativeVoidCall();
public void onNativeStringCall(String arg);
>

Данному интерфейсу в нативном коде на C++ будет соответстововать следующий класс JniNativeCallListener :

class JniNativeCallListener <
public:
JniNativeCallListener(JNIEnv* pJniEnv, jobject pWrapperInstance);
/* for onNativeVoidCall()*/
void callJavaVoidMethod();
/* for onNativeStringCall(String) */
void callJavaStringMethod(jobject pString);
~JniNativeCallListener();
private:
JNIEnv* getJniEnv();
/* Method descriptors*/
jmethodID mOnNativeVoidCallmID;
jmethodID mOnNativeStringCallmID;
/* Reference to Java-object*/
jobject mObjectRef;
JavaVM* mJVM;
>;

Заранее хочу обратить внимание, что действия по инциализации объекта будут местами избыточны для случая однопоточного приложения, но обязательны при использовании многопоточности и pthread.

Теперь перейдем к непосредственной инициализации и получению дескрипторов.

Получаем дескриптор класса и методов

К получению дескриптора класса есть два подхода. Первый мне нравится:

jclass clazz = pJniEnv->GetObjectClass(pWrappedInstance);

jclass clazz = pEnv->FindClass(«by/idev/jni/javacall/MainActivity»);

Преимущества первого подхода в отсутсвии хард-кода, второй подход позволяет получить дескриптор (jclass) не имея ссылки на объект. Так что решение о том, какой из них использоват зависит от ситуации.

Полностью инициализация будет выглядеть так:

JniNativeCallListener::JniNativeCallListener(JNIEnv* pJniEnv, jobject pWrappedInstance) <
pJniEnv->GetJavaVM(&mJVM);
mObjectRef = pJniEnv->NewGlobalRef(pWrappedInstance);
jclass clazz = pJniEnv->GetObjectClass(pWrappedInstance);
mOnNativeVoidCallmID = pJniEnv->GetMethodID(clazz, «onNativeVoidCall», «()V»);
mOnNativeStringCallmID = pJniEnv->GetMethodID(clazz, «onNativeStringCall», «(Ljava/lang/String;)V»);
log_debug(«JniNativeCallListener created.»);
>

В данном случая, лаконичности ради, я провожу инициализацию в конструкторе, что является плохим тоном. Гораздо лучшим вариантом был бы отдельный метод инициализации, потому что мы помним, что логика и понетциальные ошибки в конструкторе — это плохо, а при вызовах GetMethodID и GetObjectClass и им подобных могут возникать ошибки (они могут вернуть NULL , хотя со мной такого не случалось).

Логирование определено среди утилит в файле Util.h

#include
#include
#include
#define LOG_TAG «NativeLog»
#ifndef UTIL_H_
#define UTIL_H_

void log_debug(const char* pMessage) <
__android_log_write(ANDROID_LOG_DEBUG, LOG_TAG, pMessage);
>
void log_error(const char* pMessage) <
__android_log_write(ANDROID_LOG_ERROR, LOG_TAG, pMessage);
>
/*
* Some utility methods
*/
void makeGlobalRef(JNIEnv* pEnv, jobject* pRef) <
if (*pRef) <
jobject globalRef = pEnv->NewGlobalRef(*pRef);
pEnv->DeleteLocalRef(*pRef);
//TODO NULL-check for globalRef
*pRef = globalRef;
>
>
void deleteGlobalRef(JNIEnv* pEnv, jobject* pRef) <
if (*pRef != NULL) <
pEnv->DeleteGlobalRef(*pRef);
*pRef = NULL;
>
>

#endif /* UTIL_H_ */

Вы можете спросить, а зачем нужны эти строки:

pJniEnv->GetJavaVM(&mJVM);
mObjectRef = pJniEnv->NewGlobalRef(pWrappedInstance);

А я напомню, что ссылки на объекты, переданные в JNI-метод, действительны только в пределах времени выполнения этого метода. То есть при попытки обратится к mObjectRef или pJniEnv после выполнения метода мы потерпим фиаско. Поэтому мы создаем глобальную ссылку на mObjectRef , чтобы потому вызвать у него нужный метод, и получаем ссылку на Java-машину mJVM , что при помощи нее в будещем получать JNIEnv . Вот так все запутанно, но походу дела все станет понятнее Чтобы получать JNIEnv используется метод getJniEnv():

JNIEnv* JniNativeCallListener::getJniEnv() <
JavaVMAttachArgs attachArgs;
attachArgs.version = JNI_VERSION_1_6;
attachArgs.name = «>>>NativeThread__Any»;
attachArgs.group = NULL;
JNIEnv* env;
if (mJVM->AttachCurrentThread(&env, &attachArgs) != JNI_OK) <
env = NULL;
>
return env;
>

Определение сигнатуры и получение идентификатора метода

Этим целом служат строки которые вы уже видели:

mOnNativeVoidCallmID = pJniEnv->GetMethodID(clazz, «onNativeVoidCall», «()V»);
mOnNativeStringCallmID = pJniEnv->GetMethodID(clazz, «onNativeStringCall», «(Ljava/lang/String;)V»);

В качестве параметров GetMethodID служат дескриптор класса, имя метода и дикое, на первый взгляд, описание списка параметров и возвращаемого типа «()V» и «(Ljava/lang/String;)V». В скобках указаны входные параметры, после них — возвращаемый тип. Таким образом первый метод не имеет входных параметров и ничего не возвращает, а второй принимает строку и тоже ничего не возвращает. Ниже приведена таблица типов параметров и пару примеров описания методов.

Поддерживаемые JNI типы данных и их коды

Java JNI JNI array Код Код массива
boolean jboolean jbooleanArray Z [Z
byte jbyte jbyteArray B [B
char jchar jcharArray C [C
double jdouble jdoubleArray D [D
float jfloat jfloatArray F [F
int jint jintArray I [I
long jlong jlongArray J [J
short jshort jshortArray S [S
Object jobject jobjectArray L [L
Class jclass нет L [L
String jstring нет L [L
void void нет V нет

//Получение конструтора. Да, конструктор — тоже метод.
jmethod constructor = pEnv->GetMethodID(clazz, «», «(Ljava/lang/String;)V»);
//Аргемент метода — произвольный класс SomeClass
jmethodID customObjectMethod= pEnv->GetMethodID(clazz,»doWork», «(Lby/idev/native/SomeClass;)V»);
//Получение знаменитого метода equals(object)
jmethodID equals = pEnv->GetMethodID(clazz,»equals», «(Ljava/lang/Object;)Z»);

Вызов метода обратного вызова

Дескрипторы получены, теперь можно написать код который используя их и JNIEnv произведет нужные действия:

void JniNativeCallListener::callJavaVoidMethod() <
JNIEnv* jniEnv = getJniEnv();
jniEnv->CallVoidMethod(mObjectRef, mOnNativeVoidCallmID);
>

void JniNativeCallListener::callJavaStringMethod(jobject pString) <
JNIEnv* jniEnv = getJniEnv();
jniEnv->CallVoidMethod(mObjectRef, mOnNativeStringCallmID, pString);
>

Вот и все! Теперь можно написать клиентский код, который будет использовать уже написанное решение, выглядеть он может так:

#include «JniNativeCall.hpp»
#include «JniNativeCall.hpp»
#include «by_idev_jni_javacall_MainActivity.h»
#ifndef JNIGLUE_CPP_
#define JNIGLUE_CPP_
JNIEXPORT void JNICALL Java_by_idev_jni_javacall_MainActivity_makeNativeCall (JNIEnv *pEnv, jobject pThis, jobject pNativeCallListener) <
JniNativeCallListener listener(pEnv, pNativeCallListener);
jstring jStr = pEnv->NewStringUTF(«Hello from native!»);
//Call Java callbacks
listener.callJavaStringMethod(jStr);
listener.callJavaVoidMethod();
>

#endif /* JNIGLUE_CPP_ */

На Java-стороне код будет таким:

public class MainActivity extends Activity
implements NativeCallListener, OnClickListener < private int mCount = 0;
private Button mButton;
private TextView mText;

@Override
public void onCreate(Bundle savedInstanceState) <
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(this);

mText = (TextView) findViewById(R.id.text);
>

@Override
public void onNativeVoidCall() <
mCount++;
mText.setText(String.valueOf(mCount));
>
@Override
public void onNativeStringCall(String arg) <
makeNoise(«Native call with string: » + arg);
>

@Override
public void onClick(View v) <
switch (v.getId()) <
case R.id.button:
makeNativeCall(this);
break;
>
>

private void makeNoise(String text) <
Toast.makeText(this, text, Toast.LENGTH_LONG).show();
>

native private void makeNativeCall(NativeCallListener nativeCallListener);

static <
System.loadLibrary(«jnicall»);
>
>

Все вышесказанное, но очень быстро

Весь код можно сократить до жалких трех строк логики:

JNIEXPORT void JNICALL Java_by_idev_jni_javacall_MainActivity_makeNativeCall (JNIEnv *pEnv, jobject pThis, jobject pNativeCallListener) <
//получение дескриптора класса
jclass clazz = pEnv->GetObjectClass(pNativeCallListener);
//получение идентификатора метода
jmethodID voidVoidMethod = pEnv->GetMethodID(clazz,»onNativeVoidCall», «()V»);
//вызов метода
pEnv->CallVoidMethod(pNativeCallListener, voidVoidMethod);
>

Если заморачиваться не нужно — то такой подход сработает, для больших систем я бы все-таки рекомендовал реализовать все на должном уровне Так же не очень разумно делать «циклические вызовы», когда Java-код вызывает С++ код, который вызывает Java-код, который снова вызывает C++ код. Четвертый шаг тут явно избыточный.

Заключение

JNI — замечательный, но требующий бережного отношения к себе механизм, дающий понимание работы многих аспектов Java и программирования в целом. В следующий раз мы с вами займемся доступом к полям объекта. Код для данной статьи тут.

Happy Coding!

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Источник

Оцените статью