Android -- JNI开发(动态注册)

10 篇文章 1 订阅
3 篇文章 0 订阅

一、前言

注册JNI函数有两种方式:

  1. 静态注册
    这种方法比较常见,用的是javah -jni xxxx命令生成一组签名函数,并去实现这些函数。
    静态注册方式的弊端:
    (a)需要编译所有声明了native函数的Java类,每个所生成的class文件都得用javah命令生成一个头文件。
    (b)javah生成的JNI层函数名特别长,书写起来很不方便。
    (c)初次调用native函数时要根据函数名字搜索对应的JNI层函数来建立关联关系,这样会影响运行效率。
    静态注册在前面已经提到过,详情请见:Android – JNI开发(静态注册)
  2. 动态注册
    Java的Native方法和JNI函数是一一对应的,JNI中有一个JNINativeMethod结构体来保存这个对应关系。我们可以通过重写JNI_OnLoad函数来实现动态注册。
    下面通过一个演示Demo来分析一下动态注册的步骤。
    演示Demo下载:Gitee

二、方法解析

Android Studio目录结构图如下:

目录结构

  1. 实现如下布局文件,Activity界面有两个按键,分别用来演示Java调用C层和C调用Java层过程,同时有两个TextView显示调用结果。activity_main.xml如下:

    <?xml version="1.0" encoding="utf-8"?>
    <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAntiqueWhite"
        tools:context=".MainActivity">
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginTop="10dp"
            android:layout_marginLeft="10dp"
            android:layout_marginRight="10dp"
            android:layout_marginBottom="10dp"
            android:orientation="vertical"
            android:background="@color/colorHoneydew3">
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/colorPlum1"
                android:orientation="vertical"
                android:layout_weight="1">
    
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_weight="1">
    
                    <TextView
                        android:id="@+id/textview_java_to_c"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_centerInParent="true"
                        android:gravity="center"
                        android:text="@string/java_to_c"
                        android:textSize="24sp"
                        android:textStyle="bold" />
    
                </RelativeLayout>
    
                <RelativeLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_weight="1">
    
                    <TextView
                        android:id="@+id/textview_c_to_java"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_centerInParent="true"
                        android:gravity="center"
                        android:text="@string/c_to_java"
                        android:textSize="24sp"
                        android:textStyle="bold" />
    
                </RelativeLayout>
    
    
    
    
            </LinearLayout>
    
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_marginTop="10dp"
                android:background="@color/colorLightSkyBlue"
                android:layout_weight="9">
    
                <LinearLayout
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:layout_marginTop="5dp"
                    android:layout_marginLeft="5dp"
                    android:layout_marginRight="5dp"
                    android:layout_marginBottom="5dp"
                    android:background="@color/colorMediumPurple1"
                    android:orientation="horizontal">
    
                    <LinearLayout
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_weight="1">
    
                        <Button
                            android:id="@+id/button_java_to_c"
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"
                            android:background="@color/colorPaleGreen"
                            android:text="@string/java_to_c"
                            android:textAllCaps="false"
                            android:onClick="onClick"/>
                    </LinearLayout>
    
                    <LinearLayout
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        android:layout_weight="1">
    
                        <Button
                            android:id="@+id/button_c_to_java"
                            android:layout_width="match_parent"
                            android:layout_height="match_parent"
                            android:background="@color/colorMediumPurple1"
                            android:text="@string/c_to_java"
                            android:textAllCaps="false"
                            android:onClick="onClick"/>
                    </LinearLayout>
    
                </LinearLayout>
            </LinearLayout>
    
        </LinearLayout>
    
    </androidx.constraintlayout.widget.ConstraintLayout>
    
  2. 文件定义
    我们将C层文件定义为jni_test.c,编译出来的库文件命名为jni_test,app目录下的build.gradle做如下配置,

    android{}路径下添加一下配置:

        sourceSets.main{
            jniLibs.srcDir 'src/main/libs' // 库文件输出目录 
            jni.srcDirs = []
        }
    
        externalNativeBuild {
            ndkBuild {
                path file('src/main/jni/Android.mk') // Android.mk编译目录
            }
        }
    

    建立src/main/jni/Android.mk文件,主要是为了引入.c文件编译对应的库,里面内容为:

    LOCAL_PATH := $(call my-dir)
    
    LOCAL_MODULE := jni_test
    LOCAL_SRC_FILES := jni_test.c
    
    include $(BUILD_SHARED_LIBRARY)
    

    建立src/main/jni/Application.mk文件,主要是为了生成所有架构的库文件:

    APP_ABI := all
    

    编写JNITest.java文件,主要用于加载jni_test库,编写native方法及C层回调方法,里面有详细的注释:

    package com.example.jnitestdemo;
    
    import android.util.Log;
    
    public class JNITest {
    
        // Log tag
        private static final String LOG_TAG = "JNITest";
    
        private static MainView mView = null;
    
        static {
            System.loadLibrary("jni_test"); // 加载jni_test库
        }
    
        public JNITest(MainView mainView) {
            this.mView = mainView;
        }
    
        /* C层调用Java层 */
        public int CtoJavaReturn(int type) {
            debug("CtoJavaReturn: " + type);
            MainView view = JNITest.mView;
            mView.updateTextViewCtoJava(type);
    
            return 0;
        }
    
        /* Java调用C层 */
        public native String javaToC();
        public native String CtoJava();
    
    
        /* debug */
        private void debug(String info) {
            Log.d(LOG_TAG, info);
        }
    }
    
    

    编写jni_test.c文件,重写JNI_OnLoad函数,并结合registerNatives函数实现动态注册关联各个方法与函数,代码详情见注释:

    //
    // Created by jerry on 2021/3/31.
    //
    #include <jni.h>
    #include <string.h>
    #include <sys/system_properties.h>
    #include <android/log.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdio.h>
    #include <assert.h>
    #include <time.h>
    
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    /* 生成字符串 */
    char* getString(int length) {
        int flag, i;
        char *string;
        srand((unsigned) time(NULL));
        if ((string = (char *) malloc(length)) == NULL) {
            return NULL;
        }
    
        for (i = 0; i < length - 1; i++) {
            flag = rand() % 3;
            switch (flag) {
                case 0:
                    string[i] = 'A' + rand() % 26;
                    break;
                case 1:
                    string[i] = 'a' + rand() % 26;
                    break;
                case 2:
                    string[i] = '0' + rand() % 10;
                    break;
                default:
                    string[i] = 'x';
                    break;
            }
        }
        string[length - 1] = '\0';
        return string;
    }
    
    
    // #define JNIREG_CLASS "com.example.jnitestdemo.JNITest" // 指定要注册的类 Android 5.0之前用这种方式(.)隔开
    #define JNIREG_CLASS "com/example/jnitestdemo/JNITest"    // 指定要注册的类 Android 5.0之后用这种方式(/)隔开
    
    void c_to_java_test(JNIEnv * env)
    {
        srand(time(0));
        int type = rand() % 1000;
    
        jclass jClass = (*env)->FindClass(env, JNIREG_CLASS);
        if (jClass == 0) {
            return;
        }
    
        jobject jObject = (*env)->AllocObject(env,jClass);
        if (jObject == NULL) {
            return;
        }
    
        jmethodID jMethodId = (*env)->GetMethodID(env,jClass,"CtoJavaReturn", "(I)I");
        if (jMethodId == NULL) {
            return;
        }
    
        (**env).CallIntMethod(env,jObject,jMethodId,type);
    }
    
    JNIEXPORT jstring JNICALL jni_call_java_to_c(JNIEnv *env, jclass clazz)
    {
        return (*env)->NewStringUTF(env, getString(5));
    }
    
    JNIEXPORT jstring JNICALL jni_call_c_to_java(JNIEnv *env, jclass clazz)
    {
        c_to_java_test(env);
        return (*env)->NewStringUTF(env, "JNI Call C To Java.");;
    }
    
    
    /**
    * Table of methods associated with a single class.
    */
    static JNINativeMethod gMethods[] = {
            { "javaToC", "()Ljava/lang/String;", (void*)jni_call_java_to_c },
            { "CtoJava", "()Ljava/lang/String;", (void*)jni_call_c_to_java },
    };
    
    /*
    * Register several native methods for one class.
    */
    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;
    }
    
    
    /*
    * Register native methods for all classes we know about.
    */
    static int registerNatives(JNIEnv* env)
    {
        if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
                                   sizeof(gMethods) / sizeof(gMethods[0])))
            return JNI_FALSE;
    
        return JNI_TRUE;
    }
    
    /*
    * Set some test stuff up.
    *
    * Returns the JNI version on success, -1 on failure.
    */
    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved)
    {
        JNIEnv* env = NULL;
        jint result = -1;
    
        if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
            return -1;
        }
        assert(env != NULL);
    
        if (!registerNatives(env)) {//注册
            return -1;
        }
        /* success -- return valid version number */
        result = JNI_VERSION_1_4;
    
        return result;
    }
    
    #ifdef __cplusplus
    }
    #endif
    
    

    建立一个接口文件,用于回调MainActivity,代码如下:

    package com.example.jnitestdemo;
    
    public interface MainView {
    
        void updateTextViewCtoJava(int info);
    }
    
    

    最后完善MainActivity.java文件:

    package com.example.jnitestdemo;
    
    import androidx.annotation.NonNull;
    import androidx.appcompat.app.AppCompatActivity;
    
    import android.annotation.SuppressLint;
    import android.os.Bundle;
    import android.os.Handler;
    import android.os.Message;
    import android.util.Log;
    import android.view.View;
    import android.widget.TextView;
    
    public class MainActivity extends AppCompatActivity implements MainView{
    
        // Log tag
        private static final String LOG_TAG = "JNITestDemo";
    
        // TextView
        private TextView mTextViewJavatoC = null;
        private TextView mTextViewCtoJava = null;
    
        // msg.what
        private static final int UPDATE_UI_JAVA_TO_C = 0x01;
        private static final int UPDATE_UI_C_TO_JAVA = 0x02;
    
        // UIHandler
        private UIHandler mUIHandler = null;
    
        // JNITest
        private static JNITest mJniTest = null;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            debug("onCreate");
    
            initView();
            initDataSource();
        }
    
        /* initView */
        private void initView() {
            mTextViewJavatoC = findViewById(R.id.textview_java_to_c);
            mTextViewCtoJava = findViewById(R.id.textview_c_to_java);
        }
    
        /* initDataSource */
        private void initDataSource() {
            mUIHandler = new UIHandler();
            mJniTest = new JNITest(this);
        }
    
        /* jniCallJavaToC */
        private void jniCallJavaToC() {
            String text = mJniTest.javaToC();
            debug("jniCallJavaToC: " + text);
    
            Message message = new Message();
            message.what = UPDATE_UI_JAVA_TO_C;
            message.obj = text;
            mUIHandler.sendMessage(message);
        }
    
        /* jniCallCToJava */
        private void jniCallCToJava() {
            mJniTest.CtoJava();
        }
    
        /* Button onClick */
        public void onClick(View view) {
            switch (view.getId()) {
                case R.id.button_java_to_c:
                    debug("click java to c");
                    jniCallJavaToC();
                    break;
                case R.id.button_c_to_java:
                    debug("click c to java");
                    jniCallCToJava();
                    break;
                    default:
                        break;
            }
        }
    
        @Override
        public void updateTextViewCtoJava(int info) {
            Message message = new Message();
            message.what = UPDATE_UI_C_TO_JAVA;
            message.arg1 = info;
            mUIHandler.sendMessage(message);
        }
    
        /* UIHandler */
        @SuppressLint("HandlerLeak")
        private class UIHandler extends Handler {
            @Override
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case UPDATE_UI_JAVA_TO_C:
                        mTextViewJavatoC.setText(String.valueOf(msg.obj));
                        break;
                    case UPDATE_UI_C_TO_JAVA:
                        mTextViewCtoJava.setText(String.valueOf(msg.arg1));
                        break;
                        default:
                            break;
                }
            }
        }
    
        /* debug */
        private void debug(String info) {
            Log.d(LOG_TAG, info);
        }
    }
    
    

    三、注意事项

    1. 指定要注册的类在不同Android版本下的区别

      #define JNIREG_CLASS "com.example.jnitestdemo.JNITest" // 指定要注册的类 Android 5.0之前用这种方式(.)隔开
      #define JNIREG_CLASS "com/example/jnitestdemo/JNITest"    // 指定要注册的类 Android 5.0之后用这种方式(/)隔开
      
    2. 数值类型要一一对应,否则编译会报错。

    3. 我们上面虽然给库命名的为jni_test,但实际编译出来的库名称为libjni_test.so。这个我们不用去理会,AS会自动将库安装到对应目录下。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值