Android和C/C++通过Jni实现通信方式二
在前面篇章Android和C/C++通过Jni实现通信方式一介绍了Jni通过全局的jobject和JavaVM来实现跨线程回调Java的方法和完整代码,上述方法在Android的源代码中实际使用并不是非常广泛(重点:这里不是说Jni用的不是很广泛,说的是这种全局的跨线程回调Java的方法),使用比较广泛的是Native层的Looper中addFd方法监听事件,然后回调Java世界。
实际效果演示
老规矩,在正式开始代码编程和介绍之前,先奉上最终效果演示,正所谓眼见为实耳听为虚,不放个大招,各位读者咋有一鼓作气看下去的勇气呢,不然看到最好发现是个半成品或者残次品那就不好了。
(1).Jni端
λ adb logcat -s JniNative,MainStreamThread
--------- beginning of system
--------- beginning of main
I/JniNative( 5118): init called, pid=5118 uid=1000 gid=1000 pthread_id=-1225118740
I/MainStreamThread( 5118): The current thread Name : MainStreamThread
I/MainStreamThread( 5118): enter MainStreamThread::onFirstRef
I/MainStreamThread( 5118): init called, pid=5118 uid=1000 gid=1000 pthread_id=-1201086200
I/MainStreamThread( 5118): write_work: cmd=1
I/JniNative( 5118): ------ CMD_NOTIFY_NATIVE_CALLBACK Hello Java, Im from Native ------
(2.Android端
λ adb logcat -s JniActivity
--------- beginning of system
--------- beginning of main
E/JniActivity( 5118): The current process id : 5118
E/JniActivity( 5118): The current thread id : 1
E/JniActivity( 5118): notifyNativeCall : Hello Java, Im from Native
上面的相关打印日志为我们演示了Android通过Jni实现了在同一个进程里面C/C++跨线程回调的实例。好了演示效果已经OK了,下面我们一步步的来讲解怎么实现这种通信方式。
Android端编程
好了言归正传,在Android端我们必须定义好native方法和相关的回调方法,这里最好借助JAVAH生成相关的头文件,当然最好是掌握native方法相关的签名那样是最好了。放上相关代码:
package com.pax.android2native;
import java.util.ArrayList;
import java.util.List;
import android.os.MessageQueue;
public class JniNative {
// native methods call
private int mNativeFieId = 0;
private native void nativeInit(MessageQueue mqueue);
public void NativeInit(MessageQueue mqueue) {
nativeInit(mqueue);
}
private List<JniNativeListerner> mJniNativeListerners = new ArrayList<JniNativeListerner>();
public interface JniNativeListerner {
public void notifyNativeCall(String msg);
}
public void setNativeListen(JniNativeListerner mJniNativeListerner) {
mJniNativeListerners.add(mJniNativeListerner);
}
public void removeNativeListen(JniNativeListerner mJniNativeListerner) {
mJniNativeListerners.remove(mJniNativeListerner);
}
//Native method callback
public void notifyNativeCall(String msg) {
for (JniNativeListerner lis : mJniNativeListerners) {
lis.notifyNativeCall(msg);
}
}
static {
System.loadLibrary("Native_jni");
}
}
Jni端编程
好了,前面Android端已经定义好了,Jni的native方法了,那么接下来就是进行具体的实现了,在这里可以有两种注册方法 ,一种是静态的一种是动态的,这里我使用动态注册的,并且实现跨线程回调。具体实现如下。
com_pax_android2native_JniNative.cpp
#include "com_pax_android2native_JniNative.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <android/log.h>
#include <jni.h>
#include <assert.h>
#include <poll.h>
#include <utils/threads.h>
#include <utils/RefBase.h>
#include "NativeCode.h"
#include <utils/Looper.h>
#include <android/looper.h>
#include "MainStreamThread.h"
//使用Android域
using namespace android;
#define TAG "JniNative"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
struct fields_t {
jfieldID context;
jmethodID notifyNativeCall;
};
static fields_t fields;
static NativeCode* mCode = NULL;
static const char* const kClassPathName = "com/pax/android2native/JniNative";
static bool initNativeCode(JNIEnv *env, jobject obj, jobject messageQueue);
static sp<MainStreamThread> getMainStreamThread(JNIEnv *env, jobject thiz);
static sp<MainStreamThread> setMainStreamThread(JNIEnv *env, jobject thiz, sp<MainStreamThread> &mt);
JNIEXPORT void JNICALL Java_com_pax_android2native_JniNative_nativeInit
(JNIEnv * env, jobject object , jobject messageQueue){
LOGE(TAG, "init called, pid=%d uid=%d gid=%d pthread_id=%d\n", getpid(), geteuid(), getegid(), pthread_self());
if(mCode != NULL){
delete mCode;
mCode = NULL;
}
if(!initNativeCode(env, object, messageQueue)){
return ;
}
char context[256] = "MainStreamThread";
sp<MainStreamThread> mt = new MainStreamThread(context);
mt->setNativeCode(mCode);
setMainStreamThread(env, object, mt);
//启动线程
mt->run(context, PRIORITY_DEFAULT);
return;
}
static bool read_work(int fd, NativeWork* outWork) {
int res = read(fd, outWork, sizeof(NativeWork));
// no need to worry about EINTR, poll loop will just come back again.
if (res == sizeof(NativeWork)) return true;
if (res < 0)
//ALOGW("Failed reading work fd: %s", strerror(errno));
LOGE(TAG,"Failed reading work fd: %s\n", strerror(errno));
else
//ALOGW("Truncated reading work fd: %d", res);
LOGE(TAG,"Truncated reading work fd: %d\n", res);
return false;
}
/*
* Callback for handling native events on the application's main thread.
*/
static int mainWorkCallback(int fd, int events, void* data){
NativeCode * code = (NativeCode *) data;
if ((events & POLLIN) == 0) {
return 1;
}
NativeWork work;
//读取管道相关的数据
if (!read_work(code->mainWorkRead, &work)) {
return 1;
}
switch(work.cmd){
case CMD_NOTIFY_NATIVE_CALLBACK:{
const char *data = (char *)work.obj;
jstring string = code->env->NewStringUTF(data);
//ALOGW("-------CMD_NOTIFY_CUREENT_WRITER_FILE_PATH----%s----",data);
LOGE(TAG,"------ CMD_NOTIFY_NATIVE_CALLBACK %s ------\n",data);
code->env->CallVoidMethod(code->clazz, fields.notifyNativeCall, string);
code->messageQueue->raiseAndClearException(code->env, "notifyNativeCall");
delete []data;
break;
}
default:
break;
}
return 1;
}
static bool initNativeCode(JNIEnv *env, jobject obj, jobject messageQueue){
if(NULL == mCode){
mCode = new NativeCode();
mCode->messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueue);
if(mCode->messageQueue == NULL){
LOGE(TAG,"Unable to retrieve native MessageQueue\n");
delete mCode;
return false;
}
//创建管道
int msgpipe[2];
if(pipe(msgpipe)){
LOGE(TAG,"Unable to create pipe: %s\n", strerror(errno));
delete mCode;
return false;
}
mCode->mainWorkRead = msgpipe[0];
mCode->mainWorkWrite = msgpipe[1];
int result = fcntl(mCode->mainWorkRead, F_SETFL, O_NONBLOCK);
result = fcntl(mCode->mainWorkWrite, F_SETFL, O_NONBLOCK);
mCode->messageQueue->getLooper()->addFd(
mCode->mainWorkRead, 0, ALOOPER_EVENT_INPUT, mainWorkCallback, mCode);
mCode->env = env;
mCode->clazz = env->NewGlobalRef(obj);
}
return true;
}
static sp<MainStreamThread> setMainStreamThread(JNIEnv *env, jobject thiz, sp<MainStreamThread> &mt){
sp<MainStreamThread> old = (MainStreamThread*)env->GetIntField(thiz, fields.context);
if (mt.get()) {
mt->incStrong(thiz);
}
if (old != 0) {
//ALOGW("decStrong");
LOGE(TAG,"decStrong\n");
old->decStrong(thiz);
}
env->SetIntField(thiz, fields.context, (int)mt.get());
return old;
}
static sp<MainStreamThread> getMainStreamThread(JNIEnv *env, jobject thiz){
MainStreamThread* const p = (MainStreamThread*)env->GetIntField(thiz, fields.context);
return sp<MainStreamThread>(p);
}
// Dalvik VM type signatures
static JNINativeMethod gMethods[] = {
{ "nativeInit", "(Landroid/os/MessageQueue;)V",(void*) Java_com_pax_android2native_JniNative_nativeInit },
};
#define GET_METHOD_ID(var, clazz, methodName, fieldDescriptor) \
var = env->GetMethodID(clazz, methodName, fieldDescriptor); \
LOG_FATAL_IF(!var, "Unable to find method " methodName);
//注册函数
int register_native_interface(JNIEnv *env){
jclass clazz;
clazz = env->FindClass(kClassPathName);
if (clazz == NULL)
return JNI_FALSE;
if (env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0])) < 0){
return JNI_FALSE;
}
GET_METHOD_ID(fields.notifyNativeCall, clazz, "notifyNativeCall", "(Ljava/lang/String;)V");
//将java的mNativeFieId保存在jni层
fields.context = env->GetFieldID(clazz, "mNativeFieId", "I");
LOG_FATAL_IF(!fields.context, "Unable to find method mNativeFieId");
return JNI_TRUE;
}
jint JNI_OnLoad(JavaVM * vm, void * reserved){
JNIEnv * env = NULL;
jint result = -1;
if (vm->GetEnv((void**) &env, JNI_VERSION_1_4) != JNI_OK) {
LOGE(TAG, "ERROR: GetEnv failed\n");
goto bail;
}
assert(env != NULL);
if (register_native_interface(env) < 0) {
LOGE(TAG,"ERROR: native registration failed\n");
goto bail;
}
result = JNI_VERSION_1_4;
bail:
return result;
}
void JNI_OnUnload(JavaVM* vm, void* reserved){
LOGE(TAG, "JNI_OnUnload called\n");
}
MainStreamThread.cpp
#define TAG "MainStreamThread"
#include <utils/Log.h>
#include <stdio.h>
#include <jni.h>
#include <sys/prctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <utils/threads.h>
#include "MainStreamThread.h"
#include <android/log.h>
#include "NativeCode.h"
#define LOGE(TAG,...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
using namespace android;
void MainStreamThread::write_work(int fd, int32_t cmd, int32_t arg1, int32_t arg2, const void *data) {
NativeWork work;
work.cmd = cmd;
work.arg1 = arg1;
work.arg2 = arg2;
work.obj = data;
LOGE(TAG,"write_work: cmd=%d\n", cmd);
restart:
int res = write(fd, &work, sizeof(work));
if (res < 0 && errno == EINTR) {
goto restart;
}
if (res == sizeof(work)) return;
if (res < 0)
LOGE(TAG,"Failed writing to work fd: %s\n", strerror(errno));
else
LOGE(TAG,"Truncated writing to work fd: %d\n", res);
}
void MainStreamThread::notifyNativeCall(const char *data){
LOGE(TAG, "init called, pid=%d uid=%d gid=%d pthread_id=%d\n", getpid(), geteuid(), getegid(), pthread_self());
char *mData;
int len = strlen(data) + 1;
mData = new char[len];
strcpy(mData, data);
write_work(mNativeCode->mainWorkWrite, CMD_NOTIFY_NATIVE_CALLBACK, 0, 0, (void *)mData);
}
MainStreamThread::MainStreamThread(const char * clientName):
Thread(false){
LOGE(TAG,"The current thread Name : %s\n", clientName);
}
MainStreamThread::~ MainStreamThread(){
LOGE(TAG,"enter MainStreamThread::~ MainStreamThread\n");
}
void MainStreamThread::onFirstRef(){
LOGE(TAG,"enter MainStreamThread::onFirstRef\n");
}
status_t MainStreamThread::readToRun(){
LOGE(TAG,"enter MainStreamThread::readToRun\n");
status_t err = NO_ERROR;
notifyNativeCall("MainStreamThread::readToRun");
return err;
}
bool MainStreamThread::threadLoop(){
status_t err = NO_ERROR;
char context[256] = "Hello Java, Im from Native";
notifyNativeCall(context);
//仅运行一次
return false;
}
有了具体的实现代码,还得有相关的编译脚本,编译脚本Android.mk的实现如下,且编译环境必须在Android源码下编译。
include $(CLEAR_VARS)
LOCAL_MODULE_TAGS :=optional
LOCAL_C_INCLUDES := $(KERNEL_HEADERS) \
$(LOCAL_PATH)/include \
libnativehelper/include \
system/core/include \
system/core/include/system \
frameworks/av/include \
frameworks/native/include \
frameworks/native/include/media/openmax \
frameworks/base/include \
frameworks/base/core/jni \
LOCAL_SHARED_LIBRARIES := libcutils liblog libutils libicuuc
LOCAL_LDLIBS := -lm -llog -landroid_runtime -lbinder
LOCAL_MODULE:= libNative_jni
LOCAL_SRC_FILES:= com_pax_android2native_JniNative.cpp MainStreamThread.cpp
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
结语
如上就是使用Jni实现Android和C/C++通信方式二实现的全部过程,这种方式在Android中使用的非常普遍,用得太多了,譬如Input输入子系统啊,NativeActivity等等。熟练掌握这种Jni的跨线程回调的实现技能,那么以后的任何Jni跨线程回调你就再也不会让妈妈担心了,so easy。本篇是Android和C/C++通过Jni实现通信系列的最后一篇了,在Android和C/C++通信实战大荟萃中已经将Android开发中绝大部分常用的通信方式都介绍并提供了一个具体使用案例,如果读者还有其它比较好的也可以告知博主。同时关于本篇使用的Native Looper的介绍可以参见最后的相关链接。
Android消息处理机制(Handler、Looper、MessageQueue与Message)