参透Android的JNI,NDK

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u014736095/article/details/90201308

什么是NDK,跟JNI之间有什么关系

原生开发工具包 (NDK) :是一套工具,允许您为 Android 使用 C 和 C++ 代码,并提供众多平台库

Java 原生接口 (JNI) :JNI是一个协议,这个协议用来沟通java代码和外部的本地代码(c/c++),外部的c/c++代码也可以调用java代码。

需要NDK的原因

  • 进一步提升设备性能,以实现低延迟时间,或运行计算密集型应用,如游戏或物理模拟。
  • 重复使用您自己或其他开发者的 C 或 C++ 库。
  • 提高代码的安全性

如何使用NDK

要为您的应用编译和调试原生代码,您需要以下组件:

  • Android 原生开发工具包 (NDK):这套工具允许您为 Android 使用 C 和 C++ 代码。
  • CMake:一款外部构建工具,可与 Gradle 搭配使用来构建原生库。如果您只计划使用 ndk-build,则不需要此组件。
  • LLDB:一种调试程序,Android Studio 使用它来调试原生代码。

说到构建,使用 NDK 构建代码又有三种方法:

Android Studio 用于的默认工具是 CMake。由于很多现有项目都使用构建工具包编译其原生代码,Android Studio 还支持 ndk-build。所以我们今天所讲的是用CMake来构建的。

说太多,不但记不住,反而容易困,下面我们先来体验一下成功的感觉。10秒开启ndk大法传给你

一路点击next,finish即可。

创建完成后,项目会自动使用,创建完成后可能有些人的as原来没有安装ndk的,这个时候需要安装一下,或者有些人的ndk另外指定了旧版的,那么也要重新指定一下路径。指定路径步骤如下:

在Android NDK location重新指定位置即可。

AS自动帮我们生成了.cpp文件,并且修改了build.gradle进行了ndk配置,最后在MainActivity进行调用

调用c语言的方法就这三部,剩下的就是要编写c的代码了。逻辑都写在了.cpp文件中,仔细看会发现c文件中的代码看起来有点吃力。其实这里是c的语法,但是要需要用java原生接口JNI库才能将java与c打通。

JNI的使用

不难发现,他的对象,数据类型,都跟两种语言有所区别,如string类型,在jni中被定义为jstring,还有其他类型也是差不多是这样的规律(j+原来的基本数据类型)

JNIWXPORT和JNICALL是宏。

JNIEnv*指向一个位置,该位置包含一个指向函数表的指针,表中的每一项都是一个指向JNI函数的指针,native方法通过JNI函数访问JVM的中的数据 ,如下所示:

第二个参数对于非静态方法为jobject,对于静态方法为jclass。jobject表示调用native方法对象自身的引用,如同C++中的this指针;jclass表示定义native方法的类的引用。

MainActivity.java

package com.example.administrator.ndkapplication;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import java.util.Arrays;

public class MainActivity extends AppCompatActivity {

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI()+"\n"
                +stringFromJNI2()+"\n"
                + Arrays.toString(getIntArray(9))+"\n"
        );
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
    public native String stringFromJNI2();
    public native int[] getIntArray(int count);
}

native-lib.cpp

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

extern "C"
JNIEXPORT jstring

JNICALL
Java_com_example_administrator_ndkapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

//方法名必须为本地方法的全类名点改为下划线,穿入的两个参数必须这样写,
//第一个参数为Java虚拟机的内存地址的二级指针,用于本地方法与java虚拟机在内存中交互
//第二个参数为一个java对象,即是哪个对象调用了这个 c方法
extern "C"
JNIEXPORT jstring
JNICALL Java_com_example_administrator_ndkapplication_MainActivity_stringFromJNI2(JNIEnv* env,
                                                                                  jobject obj){
    //定义一个C语言字符串
    char* cstr = "hello form c";
    //返回值是java字符串,所以要将C语言的字符串转换成java的字符串
    //在jni.h 中定义了字符串转换函数的函数指针
    jstring jstr2 = env -> NewStringUTF(cstr);
    return jstr2;
}

/**
 * 从 Native 返回 int 数组,主要调用 set<Type>ArrayRegion 来填充数据,其他数据类型类似操作
 */
extern "C"
JNIEXPORT jintArray JNICALL
Java_com_example_administrator_ndkapplication_MainActivity_getIntArray(JNIEnv *env, jobject instance,
                                                             jint num) {
    jintArray intArray;
    intArray = env->NewIntArray(num);

    jint buf[num];
    for (int i = 0; i < num; ++i) {
        buf[i] = i * 2;
    }

    // 使用 setIntArrayRegion 来赋值
    env->SetIntArrayRegion(intArray, 0, num, buf);
    return intArray;
}

有关于jni还想要深入了解的,可以参阅官方文档,英文不好的只能问Google了。这里就不详细介绍了

.so文件如何生成

实际上在你跑项目的时候,so库就生成在build文件夹里了。路径为:app\build\intermediates\cmake\debug\obj,知道有部分同学不喜欢看文字,所以还是贴图吧。当你想应用到别的项目中时,这个so库直接拿过去用就可以了,非常方便。

源码传送门:https://download.csdn.net/download/u014736095/11177673

c调用java

使用jni调用java有三个步骤,如注释。

步骤2中的方法签名需要用到命令行,获取方法如下:

找到目标方法所在的,编译过后的class文件(在build\intermediates\classes\debuig文件夹下),通过命令行“javap -s 完整包名” 获取

猜你喜欢

转载自blog.csdn.net/u014736095/article/details/90201308