Android动态注册jni

最近整理了之前关于jni的代码,这里梳理下,供之后参考。java

JNI简介 

JNI是Java Native Interface的缩写,它提供了若干的接口实现了Java和其余语言的通讯(主要是c、c++)。从Java1.1开始,JNI标准成为java平台的一部分,它容许Java代码和其余语言写的代码进行交互。jni是Android中java和c++之间链接的桥梁,jni是jvm提供的一种与native方法对接的方式。android

JNI的反作用

一旦使用JNI,JAVA程序就丧失了JAVA平台的两个优势:c++

一、程序再也不跨平台。要想跨平台,必须在不一样的系统环境下从新编译本地语言部分。算法

二、程序再也不是绝对安全的,本地代码的不当使用可能致使整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个类当中。这样就下降了JAVA和C之间的耦合性。数据库

JNI使用场景

当你开始着手准备一个使用JNI的项目时,请确认是否还有替代方案。应用程序使用JNI会带来一些反作用。下面给出几个方案,能够避免使用JNI的时候,达到与本地代码进行交互的效果:安全

一、JAVA程序和本地程序使用TCP/IP或者IPC进行交互。app

二、当用JAVA程序链接本地数据库时,使用JDBC提供的API。jvm

三、JAVA程序可使用分布式对象技术,如JAVA IDL API。分布式

这些方案的共同点是,JAVA和C处于不一样的线程,或者不一样的机器上。这样,当本地程序崩溃时,不会影响到JAVA程序。ide

下面这些场合中,同一进程内JNI的使用没法避免:

一、程序当中用到了JAVA API不提供的特殊系统环境才会有的特征。而跨进程操做又不现实。

二、你可能想访问一些己有的本地库,但又不想付出跨进程调用时的代价,如效率,内存,数据传递方面。

三、JAVA程序当中的一部分代码对效率要求很是高,如算法计算,图形渲染等。

总之,只有当你必须在同一进程中调用本地代码时,再使用JNI。

JNI注册方法

注册方法有两种:静态注册和动态注册

静态注册

1,在Java文件中定义native方法。

2,在cmd命令行模式中切换目录到定义native方法class文件(或者java文件)存放位置。

3,用javah 和javac命令生成包含native方法的.h头文件。

4,实现native方法,用ndk-build编译生成.so库。

静态注册方法步骤比较繁琐,在项目中我比较偏向动态注册方法。

动态注册JNI

首先建立一个Android项目,勾选上include c++ support,MainActivity.java中内容:

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("JNITest");
    }

    public String TAG = "MainActivity";
    public JNITest jniTest;
    TextView mTextView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        TextView tv = (TextView) findViewById(R.id.sample_text);
        mTextView = (TextView)findViewById(R.id.textView);
        jniTest = new JNITest();
        int sum = jniTest.addInt(4,3);

        // Example of a call to a native method 
        mTextView.setText(jniTest.getString()+"  "+sum);
        Log.d(TAG,"set text after....");
    }
}

再建立一个java文件定义所须要的native方法,这里定义了两个方法

package com.example.szq.testjni;
public class JNITest {
    public JNITest(){
    }
    public native String getString();
    public native int addInt(int a,int b);
}  

在src/main目录下建立jni文件夹,并新建JNITest.c和Android.mk两个文件

#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <assert.h>

#define JNIREG_CLASS "com/example/szq/testjni/JNITest" //定义native方法的java文件

//实现
jstring jni_getstr(JNIEnv* jniEnv,jobject ob)
{
    return (*jniEnv)->NewStringUTF(jniEnv,"动态注册JNI test");
}

jint jni_add(JNIEnv* jniEnv,jobject ob, jint a,jint b)
{
    return a+b;
}

static JNINativeMethod gMethods[] = {
        {"getString", "()Ljava/lang/String;", (void*)jni_getstr},
        {"addInt", "(II)I", (void*)jni_add},
};

static int registerNativeMethods(JNIEnv* env
        , const char* className
        , JNINativeMethod* gMethods, int numMethods) {
    jclass clazz;
    clazz = (*env)->FindClass(env, className);
    if (clazz == NULL) {
        return JNI_FALSE;
    }
    if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
        return JNI_FALSE;
    }
    return JNI_TRUE;
}

/*
* 为全部类注册本地方法
*/
static int registerNatives(JNIEnv* env) {
    int re = registerNativeMethods(env, JNIREG_CLASS,gMethods,
            sizeof(gMethods)/sizeof(gMethods[0]));
    return re;
}

/*
* System.loadLibrary("lib")时会调用
* 若是成功返回JNI版本, 失败返回-1
*/
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
    JNIEnv* env = NULL;
    jint result = -1;
    if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) {
        return -1;
    }
    assert(env != NULL);
    if (!registerNatives(env)) {//注册
        return -1;
    }
    //成功
    result = JNI_VERSION_1_6;
    return result;
}

配置Android.mk

LOCAL_PATH := $(call my-dir)  
include $(CLEAR_VARS)  
LOCAL_MODULE := JNITest
LOCAL_SRC_FILES := JNITest.c
LOCAL_LDFLAGS += -llog
include $(BUILD_SHARED_LIBRARY)

在命令行模式下切换到jni目录,运行ndk-build会生成.so库(前提是ndk环境先配置好),将.so文件copy到src/main/libs中,构建项目,就能运行出下面的结果:

注:生成的.so文件必定要在与jni同一层的libs文件夹中

NDK自动编译配置

配置build.gradle,由于在构建项目时,编译器会自动加载gradle文件,因此在gradle中加入编译的任务(task)就能编译jni中的c文件了,配置以下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 26
    defaultConfig {
        applicationId "com.example.szq.testjni"
        minSdkVersion 18
        targetSdkVersion 26
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        ndk{
            moduleName "JNITest"
            ldLibs "log", "z", "m"
            abiFilters "armeabi", "armeabi-v7a", "x86"
            //用于指定应用应该使用哪一个标准库,此处添加c++库支持
            stl "stlport_static"        //  支持stl
            cFlags "-fexceptions"        // 支持exception
        }
        tasks.withType(JavaCompile) {
            compileTask -> compileTask.dependsOn 'ndkBuild', 'copyJniLibs'
        }
        sourceSets.main{
            jniLibs.srcDirs = ['libs']
        }
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}

task ndkBuild(type: Exec) {
//    def ndkDir = project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder()
    def ndkDir = project.android.ndkDirectory
    commandLine "$ndkDir\\ndk-build.cmd", '-C', 'src/main/jni',
            "NDK_OUT=$buildDir/ndk/obj",
            "NDK_APP_DST_DIR=$buildDir/ndk/libs/\$(TARGET_ARCH_ABI)"
}

task copyJniLibs(type: Copy) {
   from fileTree(dir: file(buildDir.absolutePath + '/ndk/libs'), include: '**/*.so')
    into file('src/main/jniLibs')
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:26.1.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
}

这样直接构建项目,构建完成就能运行程序了。

在Android studio 3.0版本中添加了更加方便的CMake来编译jni,配置文件是CMakeLists.txt,CMake会在之后的项目中常常用到,有兴趣的能够一块儿研究下

相关文章
相关标签/搜索