Android和C/C++通过Jni实现通信方式二

        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)

发布了89 篇原创文章 · 获赞 92 · 访问量 31万+

猜你喜欢

转载自blog.csdn.net/tkwxty/article/details/103177703