• Android动态注册jni


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

    JNI简介 

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

    JNI的副作用

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

    1、程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。

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

    JNI使用场景

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

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

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

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

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

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

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

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

    3、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会在以后的项目中经常用到,有兴趣的可以一起研究下

  • 相关阅读:
    Oracle学习
    挑战练习11.5 恢复CrimeFragment的边距
    844. Backspace String Compare
    496. Next Greater Element I
    682. Baseball Game
    707. Design Linked List
    挑战练习10.6 实现高效的RecyclerView刷新
    挑战练习9.5 日期格式化
    挑战练习8.8 RecyclerView ViewType
    挑战练习6.5 限制作弊次数
  • 原文地址:https://www.cnblogs.com/cocoabird/p/8341963.html
Copyright © 2020-2023  润新知