码迷,mamicode.com
首页 > 移动开发 > 详细

Android JNI入门教程

时间:2015-08-07 14:41:33      阅读:121      评论:0      收藏:0      [点我收藏+]

标签:

Android JNI入门教程

最近公司里有项目要用到jni,就研究并整理了下,现在分享给大家。

下载最新的ndk,新建环境变量NDK_ROOT,值是ndk的根目录,并把它加到PATH。这样做是因为我们要用到ndk的ndk-build.cmd,而且像eclipse要根据NDK_ROOT识别ndk。

现在新建一个最简单的安卓工程NewJNI,修改类MainActivity:

public class MainActivity extends Activity {

    public native void Hello(String name);
    
    public void Hi(HelloResponse helloResponse)
    {
        System.out.println("Hi " + helloResponse.name);
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        System.loadLibrary("NewJNI");
        Hello("mynamepfd");
    }
}

Java调C/C++

调用Hello方法,将会打印Hello mynamepfd,但是由于加上了native 关键字,表示这个方法是由C/C++实现的,直接运行当然会抛异常。

Hello方法的实现,在动态库NewJNI中,那么,NewJNI这个动态库,该如何生成呢?

在控制台中,cd到工程根目录\src下,

javah -d ../jni -jni com.example.newjni.MainActivity

这样就把类MainActivity在jni下要用到的头文件生成到了jni目录下。

观察头文件,可以发现,Java调C/C++,参数并不是以基本数据类型传递到C/C++中,而是以在jni.h中定义的以j开头的对象来传递的。还可以观察到,在name之前,还有JNIEnv *jenv、jobject obj这两个参数,其中obj,就是调用者的实例,相当于new出来的类Main。

jstring 比较特殊,它不能直接使用,要通过jenv提供的一组函数来操作:

jstring (*NewString)(JNIEnv*, const jchar*, jsize);
jsize (*GetStringLength)(JNIEnv*, jstring);
const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*);
jstring (*NewStringUTF)(JNIEnv*, const char*);
jsize (*GetStringUTFLength)(JNIEnv*, jstring);
const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*);
void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*);

NewString,根据传入的const char*,构造unicode编码的jstring,

NewStringUTF,根据传入的const char*,构造utf8编码的jstring,

GetStringChars,根据传入的jstring,取出utf8编码的const char*,

GetStringUTFChars,根据传入的jstring,取出unicode编码的const char*,

这两个函数,在调用后,要分别通过ReleaseStringChars、ReleaseStringUTFChars进行释放。

上面的几个函数,都定义在jni.h中,这几个都是C的接口,也就是说,在xxx.c中,要(*jenv)->xxx(jenv, ...)来调用,而在xxx.cpp中,只需要jenv->(...)就可以了。

接下来,依次创建下面的文件:

// com_example_newjni_MainActivity.cpp
#include "com_example_newjni_MainActivity.h"
#include "JNIStringUTFChars.h"
#include "JNILog.h"

static JavaVM *GpVM;
static jclass GMainActivityClass;
static jclass GHelloResponseClass;

JNIEXPORT void JNICALL Java_com_example_newjni_MainActivity_Hello
  (JNIEnv *jenv, jobject obj, jstring name)
{
    jenv->GetJavaVM(&GpVM);
    GMainActivityClass = (jclass)jenv->NewGlobalRef(jenv->FindClass("com/example/newjni/MainActivity"));
    GHelloResponseClass = (jclass)jenv->NewGlobalRef(jenv->FindClass("com/example/newjni/HelloResponse"));

    JNIStringUTFChars jniStringUTFChars(jenv);
    JPrintf("Hi %s", jniStringUTFChars.Get(name));
}

由于jni里,jenv、打log的使用很频繁,我对这两者进行了封装。其他的后面解释。

// JNIStringUTFChars.h
#ifndef _JNIStringUTFChars_H_
#define _JNIStringUTFChars_H_

#include <jni.h>

class JNIStringUTFChars
{
public:
    JNIStringUTFChars(JNIEnv *jenv);
    ~JNIStringUTFChars();

    const char *Get(jstring str);
    void Release();

private:
    JNIEnv *m_jenv;
    jstring m_str;
    const char *m_pchar;
};

#endif
// JNIStringUTFChars.cpp
#include "JNIStringUTFChars.h"

JNIStringUTFChars::JNIStringUTFChars(JNIEnv *jenv)
{
    m_jenv = jenv;
    m_str = 0;
    m_pchar = 0;
}

JNIStringUTFChars::~JNIStringUTFChars()
{
    Release();
}

const char *JNIStringUTFChars::Get(jstring str)
{
    m_str = str;
    m_pchar = m_jenv->GetStringUTFChars(str, 0);
    return m_pchar;
}

void JNIStringUTFChars::Release()
{
    if(!m_pchar)
    {
        return;
    }
    m_jenv->ReleaseStringUTFChars(m_str, m_pchar);
    m_pchar = 0;
    m_str = 0;
}

有人可能要问,为什么初始化m_str,m_pchar没使用NULL?因为在只包含了jni.h的情况下,NULL是未声明的。

// JNILog.h
#ifndef _JNILog_H_
#define _JNILog_H_

#include <android/log.h>

#define JPrintf(format,...) __android_log_print(ANDROID_LOG_INFO, "ligNewJNI", __FILE__" | %05d | "format"\n", __LINE__, ##__VA_ARGS__)

#endif
// Android.mk
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE := NewJNI

LOCAL_C_INCLUDES :=     $(LOCAL_PATH)/
    
LOCAL_SRC_FILES :=     com_example_newjni_MainActivity.cpp    JNIStringUTFChars.cpp
    
LOCAL_LDLIBS:= -llog
include $(BUILD_SHARED_LIBRARY)
// Application.mk
APP_ABI := x86
APP_MODULES := NewJNI

我在这里,只生成了x86上的动态库,要想生成所有处理器上的动态库,将x86改为all;NewJNI是动态库名字,与Android.mk中LOCAL_MODULE相对应。

现在,在控制台中,cd到工程根目录,ndk-build,耐心等待生成libNewJNI.so。让工程跑在安卓模拟器上,就可以在控制台上看到Hi mynamepfd了。

因为直接使用jenv操作对象很繁琐,除了StringUTFChars,大家可以对自己要用到的jenv中的函数,都进行类似的封装。下面,将我封装的一些文件分享给大家。

// JNIStringUTF.h
#ifndef _JNIStringUTF_H_
#define _JNIStringUTF_H_

#include <jni.h>
#include <string>

class JNIStringUTF
{
public:
    JNIStringUTF(JNIEnv *jenv);
    ~JNIStringUTF();

    jstring New(const std::string &str);
    jstring GetString();

private:
    JNIEnv *m_jenv;
    jstring m_str;
};

#endif
// JNIStringUTF.cpp
#include "JNIStringUTF.h"

JNIStringUTF::JNIStringUTF(JNIEnv *jenv)
{
    m_jenv = jenv;
    m_str = 0;
}

JNIStringUTF::~JNIStringUTF()
{
}

jstring JNIStringUTF::New(const std::string &str)
{
    m_str = m_jenv->NewStringUTF(str.c_str());
    return m_str;
}

jstring JNIStringUTF::GetString()
{
    return m_str;
}
// JNIByteArray.h
#ifndef _JNIByteArray_H_
#define _JNIByteArray_H_

#include <jni.h>

class JNIByteArray
{
public:
    JNIByteArray(JNIEnv *jenv);
    ~JNIByteArray();

    jbyteArray New(jsize length);
    jbyteArray GetArray();

    void SetRegion(jsize start, jsize len, const jbyte *buf);

private:
    JNIEnv *m_jenv;
    jbyteArray m_array;
};

#endif
// JNIByteArray.cpp
#include "JNIByteArray.h"

JNIByteArray::JNIByteArray(JNIEnv *jenv)
{
    m_jenv = jenv;
    m_array = 0;
}

JNIByteArray::~JNIByteArray()
{

}

jbyteArray JNIByteArray::New(jsize length)
{
    m_array = m_jenv->NewByteArray(length);
    return m_array;
}

jbyteArray JNIByteArray::GetArray()
{
    return m_array;
}

void JNIByteArray::SetRegion(jsize start, jsize len, const jbyte *buf)
{
    if(!m_array)
    {
        return;
    }
    m_jenv->SetByteArrayRegion(m_array, start, len, buf);
}
// JNIObject.h
#ifndef _JNIObject_H_
#define _JNIObject_H_

#include <jni.h>
#include <string>

class JNIObject
{
public:
    JNIObject(JNIEnv *jenv, jclass clazz);
    ~JNIObject();

    jobject New();
    void From(jobject object);
    jobject GetObject();

    void SetIntField(const std::string &strField, int n);
    int GetIntField(const std::string &strField);

    // set传入gbk编码的字符串,get取得gbk编码的字符串
    void SetStringField(const std::string &strField, const std::string &str);
    std::string GetStringField(const std::string &strField);

    void SetObjectField(const std::string &strField, const std::string &strSign, jobject object);
    jobject GetObjectField(const std::string &strField, const std::string &strSign);

    void CallVoidMethod(const std::string &strMethod, const std::string &strSign, ...);

private:
    JNIEnv *m_jenv;
    jclass m_clazz;
    jobject m_object;
};

#endif
// JNIObject.cpp
#include "JNIObject.h"
#include "JNIStringUtil.h"
#include "JNILog.h"

JNIObject::JNIObject(JNIEnv *jenv, jclass clazz)
{
    m_jenv = jenv;
    m_clazz = clazz;
    m_object = NULL;
}

JNIObject::~JNIObject()
{

}

jobject JNIObject::New()
{
    jmethodID ctor = m_jenv->GetMethodID(m_clazz, "<init>", "()V");
    m_object = m_jenv->NewObject(m_clazz, ctor);
    return m_object;
}

void JNIObject::From(jobject object)
{
    m_object = object;
}

jobject JNIObject::GetObject()
{
    return m_object;
}

void JNIObject::SetIntField(const std::string &strField, int n)
{
    if(!m_object)
    {
        return;
    }
    jfieldID fieldID = m_jenv->GetFieldID(m_clazz, strField.c_str(), "I");
    m_jenv->SetIntField(m_object, fieldID, n);
}

int JNIObject::GetIntField(const std::string &strField)
{
    if(!m_object)
    {
        return 0;
    }
    jfieldID fieldID = m_jenv->GetFieldID(m_clazz, strField.c_str(), "I");
    return m_jenv->GetIntField(m_object, fieldID);
}

void JNIObject::SetStringField(const std::string &strField, const std::string &str)
{
    if(!m_object)
    {
        return;
    }
    SetObjectField(strField, "Ljava/lang/String;", JNIStringUtil::GbkToUtf8(m_jenv, str));
}

std::string JNIObject::GetStringField(const std::string &strField)
{
    if(!m_object)
    {
        return "";
    }
    jstring str = (jstring)GetObjectField(strField, "Ljava/lang/String;");
    return JNIStringUtil::Utf8ToGbk(m_jenv, str);
}

void JNIObject::SetObjectField(const std::string &strField, const std::string &strSign, jobject object)
{
    if(!m_object)
    {
        return;
    }
    jfieldID fieldID = m_jenv->GetFieldID(m_clazz, strField.c_str(), strSign.c_str());
    m_jenv->SetObjectField(m_object, fieldID, object);
}

jobject JNIObject::GetObjectField(const std::string &strField, const std::string &strSign)
{
    if(!m_object)
    {
        return NULL;
    }
    jfieldID fieldID = m_jenv->GetFieldID(m_clazz, strField.c_str(), strSign.c_str());
    return m_jenv->GetObjectField(m_object, fieldID);
}

void JNIObject::CallVoidMethod(const std::string &strMethod, const std::string &strSign, ...)
{
    if(!m_object)
    {
        return;
    }
    jmethodID methodID = m_jenv->GetMethodID(m_clazz, strMethod.c_str(), strSign.c_str());
    va_list args;
    va_start(args, strSign);
    m_jenv->CallVoidMethodV(m_object, methodID, args);
    va_end(args);
}

JNIObject中的一些函数用到了签名,这是为了让jni识别Java中的重载方法或重载变量。签名的产生可以参考其他文章,也可以在控制台下,cd到工程根目录\bin\classes下,

javap -s com.example.newjni.MainActivity

参数-s表示输出签名

// JNIStringUtil.h
#ifndef _JNIStringUtil_H_
#define _JNIStringUtil_H_

#include <jni.h>
#include <string>

class JNIStringUtil
{
public:
    // 传参数给as时使用
    static std::string Utf8ToGbk(JNIEnv *jenv, jstring strUtf8);
    // JNIObject使用
    static jstring GbkToUtf8(JNIEnv *jenv, const std::string &strGbk);
};

#endif
// JNIStringUtil.cpp
#include "JNIStringUtil.h"
#include "JNIStringUTF.h"

std::string JNIStringUtil::Utf8ToGbk(JNIEnv *jenv, jstring strUtf8)
{
    std::string strRet;

    jclass StringClass = jenv->FindClass("java/lang/String");
    jmethodID getBytes = jenv->GetMethodID(StringClass, "getBytes", "(Ljava/lang/String;)[B");

    JNIStringUTF jniStringUTF(jenv);
    jbyteArray bytes = (jbyteArray)jenv->CallObjectMethod(strUtf8, getBytes, jniStringUTF.New("gbk"));
    jsize len = jenv->GetArrayLength(bytes);
    jbyte *pBytes = jenv->GetByteArrayElements(bytes, JNI_FALSE);
    if(len != 0)
    {
        char *pBuf = new char[len+1];
        memcpy(pBuf, pBytes, len);
        pBuf[len] = 0;

        strRet = pBuf;
        delete[] pBuf;
        pBuf = NULL;
    }

    return strRet;
}

jstring JNIStringUtil::GbkToUtf8(JNIEnv *jenv, const std::string &strGbk)
{
    jclass StringClass = jenv->FindClass("java/lang/String");
    jmethodID ctor = jenv->GetMethodID(StringClass, "<init>", "([BLjava/lang/String;)V");

    jbyteArray bytes = jenv->NewByteArray(strGbk.length());
    jenv->SetByteArrayRegion(bytes, 0, strGbk.length(), (const jbyte*)strGbk.c_str());

    JNIStringUTF jniStringUTF(jenv);
    return (jstring)jenv->NewObject(StringClass, ctor, bytes, jniStringUTF.New("utf-8"));
}

C/C++调Java

与Java调C/C++是传递Java对象给C/C++不同,C/C++调Java,是操作Java对象,大家可以参考类JNIObject。在单线程情况下,也就是在Java调C/C++的函数中,C/C++立即反调Java,需要的jenv是Java传入的,jclass可以通过jenv->FindClass(...)获取。在大多数情况下,也就是多线程情况下,C/C++调Java发生在其他线程中。假设Hello("mynamepfd")不是打印Hello mynamepfd,而是向服务端发送一个请求,C/C++异步收到响应,在另一个函数想反调Java,打印Hi Java。当有这样的应用场景时,要在Java调C/C++的时候保存JavaVM*,还要NewGlobalRef要用到的jclass,因为在其他线程中,即使能取得JNIEnv*,但由于上下文不同,取到的JNIEnv*也是不能通过正确的ClassLoader找到自己定义的类,但是可以找到系统类,比如java.lang.Object、java.lang.String等,使用NewGlobalRef是因为jenv->FindClass返回的是局部引用,在native结束时,会被销毁,不对其分配全局引用,在其他线程使用它将造成不可预料的问题,如崩溃。然后在回调函数中,通过GpVm附加当前线程得到JNIEnv*。为了方便,我实现了如下宏定义:

#define _JNIENV_BEGIN \
    JNIEnv *jenv = NULL;    GpVM->AttachCurrentThread(&jenv, NULL);    JNIObject MainActivityObj(jenv, GMainActivityClass);    MainActivityObj.New();

#define _JNIENV_END \
    GpVM->DetachCurrentThread();

在回调函数中,要这样使用:

void fn(...)
{
    _JNIENV_BEGIN
    {

    }
    _JNIENV_END
}

为什么要加括号?这样做是为了形成_JNIENV_BEGIN和_JNIENV_END之间的局部作用域,让使用了jenv的对象在jenv释放前析构。假如我们使用了JNIStringUTFChars,Get了某个jstring,并且没有手动Release,如果没有括号,那么对该对象的析构将在END之后进行,此时jenv已被释放,再进行析构中的Release将造成不可预料的问题,如崩溃。

Android JNI入门教程

标签:

原文地址:http://www.cnblogs.com/mynamepfd/p/4710695.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!