JNIDemo和学习笔记

1.文章简介

本文想要介绍App开发过程中,JNI开发的基础流程;目的是为了获取App开发过程中的JNI开发相关技巧,并且有助于我们学习AOSP系统源码。

2.JNI简介

2.1.JNI定义

JNI是Java Native Interface的缩写,通过使用 Java本地接口书写程序,可以确保代码在不同的平台上方便移植。 从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。JNI一开始是为了本地已编译语言,尤其是C和C++而设计的,但是它并不妨碍你使用其他编程语言,只要调用约定受支持就可以了。使用java与本地已编译的代码交互,通常会丧失平台可移植性。但是,有些情况下这样做是可以接受的,甚至是必须的。例如,使用一些旧的库,与硬件、操作系统进行交互,或者为了提高程序的性能。JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。
在安卓系统中,JNI定义了 Android 从受管理代码(使用 Java 或 Kotlin 编程语言编写)编译的字节码与原生代码(使用 C/C++ 编写)互动的方式。

2.2.准备的学习方法和参考资料

预期的学习方法:
通过谷歌官方网站和博客分享,学习JNI相关编码、编译、运行情况;
写一个简单Demo,尝试实现Java和native代码的相互调用和传值;
写一个稍微复杂的Demo-TimerTick秒表功能;

参考资料:

https://developer.android.google.cn/ndk/samples/sample_hellojni?hl=zh_cn
https://developer.android.google.cn/ndk/guides?hl=zh_cn
https://developer.android.google.cn/training/articles/perf-jni
Jni入门blog:https://www.jianshu.com/p/87ce6f565d37
Jni log打印:https://blog.csdn.net/u012005313/article/details/52059053
Jni线程:https://blog.csdn.net/weixin_34161064/article/details/87996322
Ndk下载:https://developer.android.google.cn/ndk/downloads?hl=zh_cn
NDK遇到的问题: https://blog.csdn.net/acm2008/article/details/44747787/

2.3.JNI与NDK的关系

JNI是Java环境提供的一套与native交互的工具,想要跟android建立关系,就需要NDK的支持。
原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库,您可使用这些平台库管理原生 Activity 和访问实体设备组件,例如传感器和触摸输入。
NDK 可能不适合大多数 Android 编程初学者,这些初学者只需使用 Java 代码和框架 API 开发应用。然而,如果您需要实现以下一个或多个目标,那么 NDK 就能派上用场:
进一步提升设备性能,以降低延迟或运行游戏或物理模拟等计算密集型应用。
重复使用您自己或其他开发者的 C 或 C++ 库。

3.简单JNI Demo示例

本章涉及的代码,可以通过以下链接下载:
https://download.csdn.net/download/Railshiqian/39924879

3.1.简单JNI Demo描述

此处的JNI Demo会展示以下功能:
搭建NDK编译环境进行JNI开发,演示JNI静态方法注册功能,通过java接口从native接口获取一个字符串,并进行打印;
搭建NDK编译环境进行JNI开发,演示JNI动态方法注册功能,通过java接口调用native接口,传入一个long类型数据并进行打印;Java接口调用native接口后,native接口调用另一个java接口并传入一个字符串;
搭建Cmake编译环境进行JNI开发;

3.2.JNI Demo实施细节

以下内容会介绍JNI Demo的实施细节,包括以下几个步骤:
创建工程;
下载并配置NDK开发包;
编写Java和Native代码(包括静态绑定和动态绑定Java和Native方法);
把Native代码加入AndroidStudio编译apk的过程;

3.2.1.NDK编译环境静态绑定方法进行JNI开发

3.2.1.1.方法调用流程

在这里插入图片描述

3.2.1.2.创建一个App工程JniTest

在这里插入图片描述

3.2.1.3.下载并配置NDK路径

SDK Manager中下载NDK版本:
注意studio版本过低的话,可能不支持高版本的NDK。
在这里插入图片描述

为JniTest项目配置NDK版本,在Project Structure中,配置Android NDK Location选项,选择SDK路径下我们下载的NDK:
在这里插入图片描述

3.2.1.4.定义NdkStaticRegisterTools.java

NdkStaticRegisterTools.java用于跟native层进行交互,定义以下接口:
接口 描述
public static native String getStringFromNDK() 从native层获取一个字符串

在MainActivity的onCreate中调用此接口:

private void getStringFromNative() {
    String stringFromNative = NdkStaticRegisterTools.getStringFromNDK();
    Log.d("jniTest111", "stringFromNative:" + stringFromNative);
}

NdkStaticRegisterTools.java代码如下:

package com.demo.jnitest;

public class NdkStaticRegisterTools {

    static {
        System.loadLibrary("ndkdemotest-jni");
    }

    public static native String getStringFromNDK();

}
3.2.1.5.编译项目并生成native代码

选择build-Build APK(s)按钮编译可以生成APK,然后在Terminal终端环境中,执行以下操作,自动生成jni层的适配代码;
在此目录下JniTest/app/build/intermediates/classes/debug/, 执行
javah -jni com.demo.jnitest.NdkStaticRegisterTools命令,会在当前debug/目录生成com_demo_jnitest_NdkStaticRegisterTools.h文件;
在app/src/main/文件夹下创建jni文件夹,把此.h文件复制到jni文件夹中;

内容如下,可见javah此命令已经帮我们生成了jni对应的接口:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_demo_jnitest_NdkStaticRegisterTools */

#ifndef _Included_com_demo_jnitest_NdkStaticRegisterTools
#define _Included_com_demo_jnitest_NdkStaticRegisterTools
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_demo_jnitest_NdkStaticRegisterTools
 * Method:    getStringFromNDK
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_demo_jnitest_NdkStaticRegisterTools_getStringFromNDK
        (JNIEnv *, jclass);

#ifdef __cplusplus
}
#endif
#endif

此时我们把.h文件复制复制到app/main/jni目录,并创建一个c++文件:
com_demo_jnitest_NdkStaticRegisterTools.cpp,修改为以下内容,在此方法中,返回了一个字符串"This str is from jni native world":

#include "com_demo_jnitest_NdkStaticRegisterTools.h"

JNIEXPORT jstring JNICALL Java_com_demo_jnitest_NdkStaticRegisterTools_getStringFromNDK
        (JNIEnv *env, jclass clazz) {
    return (env)->NewStringUTF("This str is from jni native world");
}

这样当java层调用此方式时,就会获得字符串"This str is from jni native world";

3.2.1.6.配置build.gradle,将JNI加入APK编译和打包

在app的build.gradle中添加jni编译配置,并在刚才创建的jni文件夹下创建Android.mk文件,这样编译apk时,.so文件就可以编译进apk文件中;
Android.mk内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := ndkdemotest-jni

LOCAL_SRC_FILES := com_demo_jnitest_NdkStaticRegisterTools.cpp

LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

app的build.gradle中配置如下,新增了以下红色字体部分配置:

android {
    compileSdkVersion 30
    defaultConfig {
        applicationId "com.chen.jnitimertick"
        minSdkVersion 19
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        ndk{
            moduleName "jnitimertick"
            abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        externalNativeBuild {
            ndkBuild {
                path 'src/main/jni/Android.mk'
            }
        }
        sourceSets.main {
            jni.srcDirs = []
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
}

经过以上配置后,进行build APK编译出来的apk文件,就打包了各个cpu架构相关的so文件,可以解压缩apk进行确认。
Activity启动后,会调用NdkStaticRegisterTools.getStringFromNDK()方法,触发System.loadLibrary(“ndkdemotest-jni”);及调用native String getStringFromNDK();方法,就可以获得native层返回的字符串数据。

至此,NDK编译环境静态绑定方法进行JNI开发的Demo已完成。

3.2.2.NDK编译环境动态绑定方法进行JNI开发

3.2.2.1.JNI动态绑定和静态绑定的区别

此章节用来描述NDK编译环境中,动态绑定方法进行JNI开发的流程;
3.2.1章节静态绑定方法,需要native层声明一个固定的方法名称,此方法名称是JAVA_包名_类型_方法名的组合,特别长,且在java方法和native方法进行一一匹配时有额外的时间消耗;
动态绑定方法,对native层方法没有这样的要求,可以是任意的c/c++方法;但是需要重写一个系统方法。指定java方法和native方法的一一对应关系;

3.2.2.2.动态绑定示例代码方法调用流程

在这里插入图片描述

3.2.2.3.JNI动态绑定实施过程

实施过程跟3.2.1章节差别在于,不再需要根据java class生成.h文件,因为动态绑定jni方法不需要那样一串很长的方法名。
定义Java层接口NdkActiveRegisterTools .java,有两个native接口和一个等待native调用的接口:

package com.demo.jnitest;

import android.util.Log;

public class NdkActiveRegisterTools {

    static {
        System.loadLibrary("ndkdemotest-jni");
    }

    public static native void sayHelloStatic(long temp);

    public static native void sayHiStatic(long temp);

    public static void expectedCalledByNative(long temp) {
        Log.d("jnitest111", "expectedCalledByNative response, temp:" + temp);
    }

}

定义一个c++文件ndkdemo_active_register_test.cpp,定义几个c++接口,并通过重写JNI_OnLoad(JavaVM *vm, void *reserved)系统方法,调用env->RegisterNatives(clazz, gMethods, numMethods) 把java方法和c++接口进行一一对应。
内容如下:

//
// Created by chen on 21-10-19.
//

#include <jni.h>
#include "Log4Android.h"
#include <stdio.h>
#include <stdlib.h>


static const char *className = "com/demo/jnitest/NdkActiveRegisterTools";

static void sayHiNativeMethod(JNIEnv *env, jclass clazz, jlong handle) {
    LOGI("native: say hi ### %ld", handle);
}

static void sayHelloNativeMethodCallJavaMethod(JNIEnv *env, jclass clazz, jlong handle) {
    LOGI("native: say hello ###, handle:%ld", handle);

    // call this java method.
    char* methodName = "expectedCalledByNative";
    jmethodID jmethodID1 = env->GetStaticMethodID(clazz, methodName, "(J)V");
    (env)->CallStaticVoidMethod(clazz, jmethodID1, 2l);

}


// 声明JNINativeMethod数组
static JNINativeMethod gJni_Methods_table[] = {
        {"sayHelloStatic", "(J)V", (void *) sayHelloNativeMethodCallJavaMethod},
        {"sayHiStatic",    "(J)V", (void *) sayHiNativeMethod},
};

#ifdef __cplusplus
extern "C" {
#endif

static int jniTestRegisterNativeMethods(JNIEnv *env, const char *className,
                                    const JNINativeMethod *gMethods, int numMethods) {
    jclass clazz;

    LOGI("Registering %s natives\n", className);
    clazz = (env)->FindClass(className);
    if (clazz == NULL) {
        LOGE("Native registration unable to find class '%s'\n", className);
        return -1;
    }

    int result = 0;
    //将java方法和native方法进行绑定
    if ((env)->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        LOGE("RegisterNatives failed for '%s'\n", className);
        result = -1;
    }

    (env)->DeleteLocalRef(clazz);
    return result;
}

// system.loadLibrary时调用
jint JNI_OnLoad(JavaVM *vm, void *reserved) {
    LOGI("enter jni_onload");

    JNIEnv *env = NULL;
    jint result = -1;

    if ((vm)->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) {
        return result;
    }

    jniTestRegisterNativeMethods(env, className, gJni_Methods_table,
                             sizeof(gJni_Methods_table) / sizeof(JNINativeMethod));

    return JNI_VERSION_1_4;
}

#ifdef __cplusplus
}
#endif

这样就实现了jni接口,不再需要静态绑定,即可进行jni方法调用。
在Activity中,我们就可以进行下面两个方法的调用:
NdkActiveRegisterTools.sayHelloStatic(1L);
NdkActiveRegisterTools.sayHiStatic(1L);
如果有两个Java类需要进行动态绑定,那么需要定义两个className字符串和两个JNINativeMethod gJni_Methods_table[]数组,调用两次jniTestRegisterNativeMethods方法即可。

至此JNI方法动态绑定示例已完成。

3.2.3.Cmake编译环境进行JNI开发

Cmake方式编译方式跟NDK不一样,java和c++编码方式,跟NDK是一致的。
参考https://www.jianshu.com/p/b4431ac22ec2,不再赘述。

4.TimerTick示例

4.1.TimerTick描述

TimerTick秒表功能,在界面上提供两个按钮(开始秒表和结束秒表功能),点击开始秒表按钮,通过c++开启一个线程,每一秒调用一次java层代码,将秒表数据抛到App的Java层进行显示;
此demo工程可以通过以下链接下载:
https://download.csdn.net/download/Railshiqian/38011524

4.2.TimerTick逻辑流程图

在这里插入图片描述

4.3.TimerTick实施细节

创建工程,并配置NDK环境的流程,请参考3.2.1章节;
创建的工程名为JniTimerTickTest,packageName包名为com.chen.jnitimertick。

4.3.1.定义TimerTick.java和MainActivity

MainActivity定义两个Button按钮和一个TextView,两个Button用来启动和停止秒表,TextView显示秒表数据;
TimerTick.java用于跟native层进行交互,定义以下几个接口:
接口 描述
public synchronized void startTimer() 启动秒表方法,点击按钮时调用此方法;
public synchronized void releaseTimer() 停止秒表方法,点击按钮时调用此方法;
public void timerTick(long seconds) Native层调用此方法,把计时输出到Java层Activity进行显示;
private native void startTimerNative(); 通知Native层开启线程,并进行秒表计时;
private native void releaseTimerNative(long handle); 通知Native层停止秒表计时,并停止线程;
注意此处handle代表了native层的一个c++对象!

MainActivity代码如下:

package com.chen.jnitimertick;

import android.app.Activity;
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 Activity implements View.OnClickListener {

    TimerTick timerTick;
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = findViewById(R.id.tv);
        findViewById(R.id.btn_start_timer).setOnClickListener(this);
        findViewById(R.id.btn_stop_timer).setOnClickListener(this);

        timerTick = new TimerTick();
        timerTick.setTimerListener(timerListener);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_start_timer:
                timerTick.startTimer();
                break;
            case R.id.btn_stop_timer:
                timerTick.releaseTimer();
                break;
        }
    }

    private TimerTick.ITimerListener timerListener = new TimerTick.ITimerListener() {
        @Override
        public void onTimerTick(long timer) {
            Message message = handler.obtainMessage();
            message.obj = timer;
            handler.sendMessage(message);
        }
    };

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            long timer = (long) msg.obj;
            textView.setText(timer + "");
        }
    };

}

TimerTick.java代码如下:

package com.chen.jnitimertick;

import android.util.Log;

public class TimerTick {

    private ITimerListener mTimerListener;
    private boolean isTimerStarted = false;
    // Avoid native object to be release
    public long mNativeTimerHandle = 0l;

    static {
        System.loadLibrary("jnitimertick");
    }

    public void setTimerListener(ITimerListener timerListener) {
        mTimerListener = timerListener;
    }

    public synchronized void startTimer() {
        if (isTimerStarted) {
            Log.d("jniTest111", "already started");
            return;
        }
        isTimerStarted = true;
        startTimerNative();
    }

    public synchronized void releaseTimer() {
        if (isTimerStarted) {
            isTimerStarted = false;
            releaseTimerNative(mNativeTimerHandle);
        } else {
            Log.d("jniTest111", "not call start, no need release");
        }
    }

    private native void startTimerNative();

    private native void releaseTimerNative(long handle);

    // called from native.
    public void timerTick(long temp) {
        Log.d("jnitest111", "timerTick:" + temp);
        mTimerListener.onTimerTick(temp);
    }

    public interface ITimerListener {
        void onTimerTick(long timer);
    }

}

activity_main.xml界面如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:hint="timer"
        android:padding="10dp" />

    <Button
        android:id="@+id/btn_start_timer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="startTimer"
        android:textSize="20sp" />

    <Button
        android:id="@+id/btn_stop_timer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="stopTimer"
        android:textSize="20sp" />

</LinearLayout>

4.3.2.编译项目并生成native代码

选择build-Build APK(s)按钮编译可以生成APK,然后在Terminal终端环境中,执行以下操作,自动生成jni层的适配代码;
在此目录下JniTimerTickTest/app/build/intermediates/classes/debug/, 执行
javah -jni com.chen.jnitimertick.TimerTick命令,会在当前debug/目录生成com_chen_jnitimertick_TimerTick.h文件;
在app/src/main/文件夹下创建jni文件夹,把此.h文件复制到jni文件夹中;

内容如下,可见javah此命令已经帮我们生成了jni对应的接口:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_chen_jnitimertick_TimerTick */

#ifndef _Included_com_chen_jnitimertick_TimerTick
#define _Included_com_chen_jnitimertick_TimerTick
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_chen_jnitimertick_TimerTick
 * Method:    startTimerNative
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_com_chen_jnitimertick_TimerTick_startTimerNative
  (JNIEnv *, jobject);

/*
 * Class:     com_chen_jnitimertick_TimerTick
 * Method:    releaseTimerNative
 * Signature: (J)V
 */
JNIEXPORT void JNICALL Java_com_chen_jnitimertick_TimerTick_releaseTimerNative
  (JNIEnv *, jobject, jlong);

#ifdef __cplusplus
}
#endif
#endif

此时我们把.h文件复制一份,并重命名为com_chen_jnitimertick_TimerTick.cpp,修改为以下内容,在这两个方法中,对接了java层的两个native方法:

#include <jni.h>
#include "com_chen_jnitimertick_TimerTick.h"

/*
 * Class:     com_chen_jnitimertick_TimerTick
 * Method:    startTimerNative
 * Signature: ()V
 */
void Java_com_chen_jnitimertick_TimerTick_startTimerNative(JNIEnv *env, jobject obj) {

};

void Java_com_chen_jnitimertick_TimerTick_releaseTimerNative(JNIEnv *env, jobject obj, jlong handleValue) {

};

4.3.3.配置build.gradle,将JNI加入APK编译和打包

在app的build.gradle中添加jni编译配置,并在刚才创建的jni文件夹下创建Android.mk文件,这样编译apk时,.so文件就可以编译进apk文件中;
Android.mk内容如下:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := jnitimertick

LOCAL_SRC_FILES := com_chen_jnitimertick_TimerTick.cpp

LOCAL_LDLIBS += -llog

include $(BUILD_SHARED_LIBRARY)

build.gradle中配置如下,新增了以下红色字体部分配置:

android {
    compileSdkVersion 30
    defaultConfig {
        applicationId "com.chen.jnitimertick"
        minSdkVersion 19
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        ndk{
            moduleName "jnitimertick"
            abiFilters "armeabi", "armeabi-v7a", "x86", "x86_64", "arm64-v8a"
        }
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        externalNativeBuild {
            ndkBuild {
                path 'src/main/jni/Android.mk'
            }
        }
        sourceSets.main {
            jni.srcDirs = []
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
}

经过以上配置后,进行build APK编译出来的apk文件,就打包了各个cpu架构相关的so文件,可以解压缩apk进行确认。

4.3.4.补充JNI代码,实现秒表逻辑

以上流程,我们已经搭建好了JNI的框架,编译apk并运行app时,已经可以把so文件打包到apk文件中,并可以实现Java和native方法的调用。
现在需要补充秒表的功能,首先定义timer_tick相关cpp和.h文件;Timer_tick.h内容如下,其中定义了class TimerTick类,及类中的方法和成员变量,包括开始秒表和结束秒表的方法,计时数据long timer_seconds秒数,内部线程pthread_t pthread_local,和javaVM相关引用变量。

//
// Created by chen on 21-10-19.
//

#ifndef _Included_timer_tick
#define _Included_timer_tick

#include <jni.h>
#include "Log4Android.h"
#include <stdio.h>
#include <stdlib.h>
#include "timer_tick.h"

// 要调用的本地方法
void startTimerNative(JNIEnv *env, jobject obj);

// 要调用的本地方法
void releaseTimerNative(JNIEnv *env, jobject obj, jlong handleValue);

static const char *timer_tick_java_class = "com/chen/jnitimertick/TimerTick";

class TimerTick {
public:
    TimerTick(JNIEnv *env, jobject thiz);

    ~TimerTick();

    void startTick();

    static void *tickTick(void *pHandle);

    void stopTick();

    long timer_seconds;
    pthread_t pthread_local;

    jfieldID nativeHandle;
    jmethodID timerTickMethod;
    JavaVM *local_java_v;

    jobject mObject;
private:
    int isTickStart;
};

#endif

timer_tick.cpp实现计时器功能:

//
// Created by chen on 21-10-19.
//

#include <jni.h>
#include "Log4Android.h"
#include <stdio.h>
#include <stdlib.h>
#include "timer_tick.h"
#include <pthread.h>
#include <unistd.h>

TimerTick::TimerTick(JNIEnv *env, jobject obj) {
    jclass clazz = (env)->FindClass(timer_tick_java_class);
    nativeHandle = (env)->GetFieldID(clazz, "mNativeTimerHandle", "J");
    timerTickMethod = (env)->GetMethodID(clazz, "timerTick", "(J)V");
    mObject = env->NewWeakGlobalRef(obj);
    (env)->GetJavaVM(&(local_java_v));
}

TimerTick::~TimerTick() {

    LOGD("delete TimerTick start");
    timerTickMethod = NULL;
    JNIEnv *jniEnv = NULL;
    local_java_v->GetEnv((void **) &jniEnv, JNI_VERSION_1_4);
    LOGD("myjvm->AttachCurrentThread");
    local_java_v->AttachCurrentThread(&jniEnv, NULL);
    jniEnv->SetLongField(mObject, nativeHandle, (jlong) 0L);
    jniEnv->DeleteWeakGlobalRef(mObject);
    nativeHandle = NULL;
    LOGD("delete TimerTick end");

}

void TimerTick::startTick() {
    LOGD("startTick");
    pthread_create(&pthread_local, NULL, tickTick, this);
}

void *TimerTick::tickTick(void *pHandle) {
    TimerTick *timerTick = (TimerTick *) (pHandle);
    timerTick->timer_seconds = 0;
    timerTick->isTickStart = 1;

    JNIEnv *jniEnv = NULL;
    timerTick->local_java_v->GetEnv((void **) &jniEnv, JNI_VERSION_1_6);
    LOGD("myjvm->AttachCurrentThread");
    timerTick->local_java_v->AttachCurrentThread(&jniEnv, NULL);

    while (timerTick->isTickStart == 1) {
        // calljava method
        jniEnv->CallVoidMethod(
                timerTick->mObject, timerTick->timerTickMethod,
                timerTick->timer_seconds);
        if (timerTick->timer_seconds >= LONG_MAX) {
            timerTick->timer_seconds = 0;
        } else {
            timerTick->timer_seconds++;
        }
        sleep(1);
    }
    timerTick->local_java_v->DetachCurrentThread();
    LOGD("TimerTick thread End");
    return NULL;
}

void TimerTick::stopTick() {
    LOGD("stopTick start");
    isTickStart = -1;
    void *status;
    //pthread_kill(timerTick->pthread_local, SIGUSR1);
    pthread_join(pthread_local, &status);
    delete this;
    LOGI("releaseTimerNative end");
}

在com_chen_jnitimertick_TimerTick.cpp的jni方法中调用timer_tick.cpp中的方法,启动秒表:

#include <jni.h>
#include "com_chen_jnitimertick_TimerTick.h"
#include "timer_tick.h"

void Java_com_chen_jnitimertick_TimerTick_startTimerNative(JNIEnv *env, jobject obj) {
    LOGI("startTimerNatived");

    TimerTick *timerTick = new TimerTick(env, obj);
    (env)->SetLongField(obj, timerTick->nativeHandle, (jlong) timerTick);
    timerTick->startTick();
    LOGI("startTimerNatived, timerTick:%ld", timerTick);

};

void Java_com_chen_jnitimertick_TimerTick_releaseTimerNative(JNIEnv *env, jobject obj,
                                                             jlong handleValue) {
    LOGI("releaseTimerNative start");
    TimerTick *timerTick = reinterpret_cast<TimerTick *>(handleValue);
    timerTick->stopTick();
    LOGI("releaseTimerNative, end, timerTick:%ld", timerTick);
};

这样,在MainActivity点击时,会调用到native层TimerTick方法,启动秒表,并每一秒上传数据给界面进行显示。

TimerTick Demo示例到此完成。

5.总结

5.1.jni关键数据结构描述

可以参考以下链接:
https://developer.android.google.cn/training/articles/perf-jni?hl=zh_cn
以下是个表格,格式乱了~~
数据结构 获取方式 介绍
JNIEnv Java方法调用到Native方法时,会传入一个JNIEnv指针对象;
也可以通过JavaVM获取当前线程的JNIEnv对象:
JNIEnv *jniEnv = NULL;
jniEnv->GetEnv((void **) &jniEnv, JNI_VERSION_1_6); 线程相关,每个线程有自己的JNIEnv对象;
可以通过JNIEnv对象调用Java方法,获取Java
层的方法jmethodID,变量jfieldId和变量的值;
JavaVM 通过JNIEnv的方法获取:
JavaVM *local_java_v;
jniEnv->GetJavaVM(&(local_java_v)); 通过JavaVM对象,可以获取当前线程相关的JNIEnv对象,然后就可以调用Java方法和获取Java
层的数据。
jobject Java方法调用到Native方法时,会传入一个jobject对象,此对象是Java层调用native方法的那个Java对象:
startTimerNative(JNIEnv *env, jobject obj);

Jobject对象可以用以下方式进行赋值,否则在其他线程中无法使用:
jobject mObject = jniEnv->NewWeakGlobalRef(obj); Java对象在Native层的体现,Native调用Java层的非static方法时,需要传入一个jobject类型的变量;
jmethodID 获取方式如下:
jmethodID timerTickMethod = (env)->GetMethodID(clazz, “timerTick”, “(J)V”); Java方法在Native层的体现,Native调用Java层的方法时,需要传入一个jmethodID 类型的变量;
jni.h:
void CallVoidMethod(jobject obj, jmethodID methodID, …)
void CallStaticVoidMethod(jclass clazz, jmethodID methodID, …)

jfieldID 获取方式如下:
jfieldID nativeHandle = (env)->GetFieldID(clazz, “mNativeTimerHandle”, “J”);
Native层在获取/设置Java层的变量数据时,需要此变量;
Jni.h:
jlong GetLongField(jobject obj, jfieldID fieldID);
void SetObjectField(jobject obj, jfieldID fieldID, jobject value);

5.2.java调用c++方法的方式

Java调用C++方法,需要以下两个要素:
Java中调用System.loadLibrary(so文件的名字),注意如果so以lib开头,如libAAA.so,则只需要System.loadLibrary(“AAA”);
so文件中按照静态/动态绑定方式注册Native方法;
Java层方法是static类型的,那么native层方法中的变量是jclass,如:
public static native void sayHiStatic(long temp);
static void sayHiNativeMethod(JNIEnv *env, jclass clazz, jlong handle);

Java层方法是非static类型的,那么native层方法中的变量是jobject,如:
private native void startTimerNative();
void Java_com_chen_jnitimertick_TimerTick_startTimerNative(JNIEnv *env, jobject obj);

5.3.C++调用java的方式

C++程序可以通过JNIEnv对象调用到java层代码,这些方法声明在jni.h中,如:
jobject (CallObjectMethod)(JNIEnv, jobject, jmethodID, …);
jboolean (CallBooleanMethod)(JNIEnv, jobject, jmethodID, …);
jobject (CallStaticObjectMethod)(JNIEnv, jclass, jmethodID, …);
jbyte (CallStaticByteMethod)(JNIEnv, jclass, jmethodID, …);

5.4.Java方法签名简介

参考https://www.jianshu.com/p/b71aeb4ed13d
Java中有方法重载的特性,多个方法可以有同一个方法名称,只需要有不同的返回值类型、参数个数和参数类型。
那么Native方法在调用Java方法,获取jmethodID时,不能只凭借“方法名称”来指定调用的方法,需要把方法返回值类型和参数个数,参数类型都考虑进来,这就需要“方法签名”的概念。

用以下获取jmethodID的代码来举例分析:
jmethodID timerTickMethod = jniEnv->GetMethodID(clazz, “timerTick”, “(J)V”);
Java代码示例:
// called from native.
public void timerTick(long temp) {
Log.d(“jnitest111”, “timerTick:” + temp);
mTimerListener.onTimerTick(temp);
}

我们可以看到,jniEnv->GetMethodID的第三个参数"(J)V",其实指向了Java层J类型参数,V类型返回值的这个方法!
那么这个J和V是如何定义的呢!

函数签名格式如下:
(第1个参数类型+第2个参数类型+第3个参数类型…)返回值类型。
当参数不是基本数据类型,是引用类型的时候,参数类型的模板为"L包名",其中包名的.(点)要换成"/";如java.lang.String类型变量,就是Ljava/lang/String,android.view.Menu类型变量就是Landroid/view/Menu。

基本数据类型的标志,和Java类型的对应关系如下图
类型标示 Java类型
Z boolean
B byte
C char
S short
I int
J long
F float
D double
[ 数组
[D double[]
[Ljava/lang/Object String[]

那么回到我们的示例代码void timerTick(long temp),方法签名就是(J)V,也就是我们传入的第三个参数!

以下进行一些举例,来增加对方法签名的印象:
Java方法 方法签名
int test() ()I
double getBigger(double arg0, double arg1) (DD)D
String getName() ()Ljava/lang/String
void nDrawText(long nativeCanvas, String text, int start, int end, float x, float y, int flags, long nativePaint) (JLjava/lang/String;IIFFIJ)V
void nDrawText(long nativeCanvas, char[] text, int index, int count, float x, float y, int flags, long nativePaint); (J[CIIFFIJ)V

6.AOSP中的JNI实践

6.1.MessageQueue中的JNI实践

MessageQueue.java中的native方法:
/home/chen/disk2/project/aosp/q_aosp/frameworks/base/core/java/android/os/MessageQueue.java

package android.os;
public final class MessageQueue {

    private long mPtr; // used by native code

private native static long nativeInit();

MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }
    boolean enqueueMessage(Message msg, long when) {
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
 }

}

Native方法nativeInit动态注册对应的Java方法
搜索MessageQueue_nativeInit,可以搜索到以下方法:
/home/chen/disk2/project/aosp/q_aosp/frameworks/base/core/jni/android_os_MessageQueue.cpp

// 方法动态注册的调用链为:AndroidRuntime.cpp->start->startReg(env)->register_jni_procs(gRegJNI, NELEM(gRegJNI),env)->gRegJNI[]->REG_JNI(register_android_os_MessageQueue)->gMessageQueueMethods

static const JNINativeMethod gMessageQueueMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
    { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
    { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
    { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
    { "nativeIsPolling", "(J)Z", (void*)android_os_MessageQueue_nativeIsPolling },
    { "nativeSetFileDescriptorEvents", "(JII)V",
            (void*)android_os_MessageQueue_nativeSetFileDescriptorEvents },
};


static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    if (!nativeMessageQueue) {
        jniThrowRuntimeException(env, "Unable to allocate native queue");
        return 0;
    }

    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

创建NativeMessageQueue对象,并将此对象的指针数据,保存到java层MessageQueue对象的mPtr中,以免局部变量NativeMessageQueue对象在方法离开时自动释放掉。

nativeWake(mPtr):
/home/chen/disk2/project/aosp/q_aosp/frameworks/base/core/jni/android_os_MessageQueue.cpp

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->wake();
}

void NativeMessageQueue::wake() {
    mLooper->wake();
}

/home/chen/disk2/project/aosp/q_aosp/system/core/libutils/Looper.cpp

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd.get(), &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
        ....
    }
}

以上逻辑的write方法,向文件描述符中写入一个1,代表唤醒此线程;
以下逻辑,是等待唤醒的阻塞位置,由Java层nativePollOnce方法调用到这里,没有消息处理的话就会阻塞到read函数;
/home/chen/disk2/project/aosp/q_aosp/system/core/libutils/Looper.cpp

void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ awoken", this);
#endif

    uint64_t counter;
    TEMP_FAILURE_RETRY(read(mWakeEventFd.get(), &counter, sizeof(uint64_t)));
}

6.2.View中的native实践

/home/chen/disk2/project/aosp/q_aosp/frameworks/base/graphics/java/android/graphics/Canvas.java

public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
        @NonNull Paint paint) {
    super.drawText(text, start, end, x, y, paint);
}

/home/chen/disk2/project/aosp/q_aosp/frameworks/base/graphics/java/android/graphics/BaseCanvas.java

public void drawText(@NonNull CharSequence text, int start, int end, float x, float y,
        @NonNull Paint paint) {
    if ((start | end | (end - start) | (text.length() - end)) < 0) {
        throw new IndexOutOfBoundsException();
    }
    throwIfHasHwBitmapInSwMode(paint);
    if (text instanceof String || text instanceof SpannedString ||
            text instanceof SpannableString) {
        nDrawText(mNativeCanvasWrapper, text.toString(), start, end, x, y,
                paint.mBidiFlags, paint.getNativeInstance());
    } else ...
}

private static native void nDrawText(long nativeCanvas, String text, int start, int end,
float x, float y, int flags, long nativePaint);
尝试搜索BaseCanvas_nDrawText字符串,没找到,然后尝试搜索nDrawText字符串:

/home/chen/disk2/project/aosp/q_aosp/frameworks/base/core/jni/android_graphics_Canvas.cpp

static const JNINativeMethod gDrawMethods[] = {
    {"nDrawColor","(JII)V", (void*) CanvasJNI::drawColor},
    {"nDrawColor","(JJJI)V", (void*) CanvasJNI::drawColorLong},
    {"nDrawPaint","(JJ)V", (void*) CanvasJNI::drawPaint},
    {"nDrawPoint", "(JFFJ)V", (void*) CanvasJNI::drawPoint},
    {"nDrawPoints", "(J[FIIJ)V", (void*) CanvasJNI::drawPoints},
    {"nDrawLine", "(JFFFFJ)V", (void*) CanvasJNI::drawLine},
    {"nDrawLines", "(J[FIIJ)V", (void*) CanvasJNI::drawLines},
    {"nDrawRect","(JFFFFJ)V", (void*) CanvasJNI::drawRect},
    {"nDrawRegion", "(JJJ)V", (void*) CanvasJNI::drawRegion },
    {"nDrawRoundRect","(JFFFFFFJ)V", (void*) CanvasJNI::drawRoundRect},
    {"nDrawDoubleRoundRect", "(JFFFFFFFFFFFFJ)V", (void*) CanvasJNI::drawDoubleRoundRectXY},
    {"nDrawDoubleRoundRect", "(JFFFF[FFFFF[FJ)V", (void*) CanvasJNI::drawDoubleRoundRectRadii},
    {"nDrawCircle","(JFFFJ)V", (void*) CanvasJNI::drawCircle},
    {"nDrawOval","(JFFFFJ)V", (void*) CanvasJNI::drawOval},
    {"nDrawArc","(JFFFFFFZJ)V", (void*) CanvasJNI::drawArc},
    {"nDrawPath","(JJJ)V", (void*) CanvasJNI::drawPath},
    {"nDrawVertices", "(JII[FI[FI[II[SIIJ)V", (void*)CanvasJNI::drawVertices},
    {"nDrawNinePatch", "(JJJFFFFJII)V", (void*)CanvasJNI::drawNinePatch},
    {"nDrawBitmapMatrix", "(JJJJ)V", (void*)CanvasJNI::drawBitmapMatrix},
    {"nDrawBitmapMesh", "(JJII[FI[IIJ)V", (void*)CanvasJNI::drawBitmapMesh},
    {"nDrawBitmap","(JJFFJIII)V", (void*) CanvasJNI::drawBitmap},
    {"nDrawBitmap","(JJFFFFFFFFJII)V", (void*) CanvasJNI::drawBitmapRect},
    {"nDrawBitmap", "(J[IIIFFIIZJ)V", (void*)CanvasJNI::drawBitmapArray},
    {"nDrawText","(J[CIIFFIJ)V", (void*) CanvasJNI::drawTextChars},
    {"nDrawText","(JLjava/lang/String;IIFFIJ)V", (void*) CanvasJNI::drawTextString},
    {"nDrawTextRun","(J[CIIIIFFZJJ)V", (void*) CanvasJNI::drawTextRunChars},
    {"nDrawTextRun","(JLjava/lang/String;IIIIFFZJ)V", (void*) CanvasJNI::drawTextRunString},
    {"nDrawTextOnPath","(J[CIIJFFIJ)V", (void*) CanvasJNI::drawTextOnPathChars},
    {"nDrawTextOnPath","(JLjava/lang/String;JFFIJ)V", (void*) CanvasJNI::drawTextOnPathString},
};

static void drawTextString(JNIEnv* env, jobject, jlong canvasHandle, jstring strObj,
                           jint start, jint end, jfloat x, jfloat y, jint bidiFlags,
                           jlong paintHandle) {
    ScopedStringChars text(env, strObj);
    Paint* paint = reinterpret_cast<Paint*>(paintHandle);
    const Typeface* typeface = paint->getAndroidTypeface();
    const int count = end - start;
    // drawTextString and drawTextChars doesn't use context info
    get_canvas(canvasHandle)->drawText(
            text.get() + start, count,  // text buffer
            0, count,  // draw range
            0, count,  // context range
            x, y,  // draw position
            static_cast<minikin::Bidi>(bidiFlags), *paint, typeface, nullptr /* measured text */);
}

AOSP中使用jni的demo示例到此结束。

7.遇到的问题

1,新建studio项目,遇到无法生成gradle项目,报错如下:
Error:A problem occurred configuring project ‘:app’.
需要确认你安装了ndk,如果没有安装那么会报这个错误。或者ndk配置路径错误。

2,出现以下错误:Invalid revision: 3.18.1-g262b901
错误是由于CMake版本过高造成的
在SDK Manager中,卸载高版本,再下载个低版本CMake即可,比如3.6版本。

3,在Native文件中无法使用c++函数,编译错误
错误原因是文件的命名,.c和.cpp文件编译链接的库不一致,发现.c文件无法访问c++的函数,将.c改为.cpp解决编译问题。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
当您在Java程序中需要调用C/C++代码时,可以使用Java Native Interface(JNI)来实现。下面是一个简单的JNI示例,演示了如何将Java方法与C函数相互调用: 1. 创建一个Java类,例如"JNIDemo.java",其中包含您想要调用的本地方法: ```java public class JNIDemo { // 本地方法声明 public native void sayHello(); // 加载本地库 static { System.loadLibrary("jni_demo"); // 加载名为"jni_demo"的本地库 } // 测试 public static void main(String[] args) { JNIDemo demo = new JNIDemo(); demo.sayHello(); // 调用本地方法 } } ``` 2. 在命令行中使用`javac`编译Java类:`javac JNIDemo.java`。 3. 生成C头文件,可以使用`javah`工具:`javah JNIDemo`。这将生成名为"JNIDemo.h"的头文件。 4. 创建一个C源文件,例如"jni_demo.c",实现您在Java中声明的本地方法: ```c #include <stdio.h> #include "JNIDemo.h" JNIEXPORT void JNICALL Java_JNIDemo_sayHello(JNIEnv *env, jobject obj) { printf("Hello from C!\n"); } ``` 5. 在命令行中使用C编译器编译C源文件,并生成共享库文件(DLL或SO): - 对于Windows(使用MinGW):`gcc -shared -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" jni_demo.c -o jni_demo.dll` - 对于Linux/Mac:`gcc -shared -I"$JAVA_HOME/include" -I"$JAVA_HOME/include/linux" jni_demo.c -o libjni_demo.so` 注意:请将`$JAVA_HOME`替换为您Java安装的实际路径。 6. 运行Java程序:`java JNIDemo`。您将看到输出:"Hello from C!"。 这是一个简单的JNI示例,演示了如何在Java和C之间进行方法调用。您可以根据自己的需求扩展和定制此示例。希望对您有帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值