Android Studio下JNI开发的基本步骤

3,906 阅读4分钟

这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战

  • 第一步 设置ndk路径 配置相应的开发环境

image.png

  • 第二步 配置快捷键 Settings -> External tools中配置javah,javap,ndk-build快捷方式,(这一步主要是为了简化命令行输入,使用原生命令行也是可以的)

image.png image.png javah参数配置(直接拷贝): Program: JDKPathJDKPath\bin\javah.exe Parameters: -classpath . -jni -o ModuleFileDirModuleFileDir/src/main/jni/PromptPrompt FileClassFileClass Working directory: ModuleFileDirModuleFileDir\src\main\Java Parameters的另外一种写法: -classpath . -jni -d ModuleFileDirModuleFileDir/src/main/jni FileClassFileClass

image.png

javap参数配置(直接拷贝):* Program: JDKPathJDKPath\bin\javap Parameters: -s -p FileClassFileClass Working directory: ModuleFileDirModuleFileDir\build\intermediates\classes\debug

image.png

ndk-build参数配置(直接拷贝):

Program: D:\Android_NDK\android-ndk-r11b\ndk-build.cmd

Working directory: ModuleFileDirModuleFileDir\src\main

  • 第三步 创建java类 引用即将创建的链接库,以及创建所需要的本地方法
static {
    System.loadLibrary("MyJni");//导入指定的so库文件名称 
}
public native String getStringFromNative();//本地方法
public native String getString_From_c();

  • 第四步 使用之前配置的javah快捷键快速生成.h头文件

image.png 此时 会自动创建jni目录并生成头文件

image.png

  • 第五步 参考头文件 在jni目录下开始编写C/C++代码![]

image.png 注:

项目结构切换成 Android状态时,jni文件夹显示成 cpp名字!

当切换成project时就显示成jni文件夹!!

如下图:

image.png

image.png

添加如下代码:

image.png

  • 第六步 配置gradle
 ndk{
        moduleName "MyJni"//编译后so库的名字
        ldLibs "log"//连接的库,可以有多个
        abiFilters "armeabi","armeabi-v7a","x86"//指定so库运行的cpu架构,有armeabi armeabi-v7a arm64-v8a x86 x86_64 mips mips64这些,常用的是armeabi和armeabi-v7a
}

点击Androidstudio 菜单栏 Build ->ReBuildProject 后自动生成Android.mk文件

image.png 把Android.mk文件拷贝到 main/jni文件夹下 然后右键--->External Tools -->ndk-build 生成 .so文件!!

image.png

image.png 其次 在项目的gradle.properties 文件中添加

android.useDeprecatedNdk=true
  • 第七步 运行java代码 调取c库

image.png

注意事项

  • 加载生成的动态库指定的文件名( System.loadLibrary("MyJni");)和生成.so时指定的名字(build.gradle中的ndk{moduleName "MyJni" }),还有Android.mk中LOCAL_MODULE := MyJni三者名称需要保持一致;

  • 异常提示不支持c++

    Error: Flag android.useDeprecatedNdk is no longer supported and will be removed in the next version of Android Studio.  Please switch to a supported build system.
      Consider using CMake or ndk-build integration. For more information, go to:
       https://d.android.com/r/studio-ui/add-native-code.html#ndkCompile
       To get started, you can use the sample ndk-build script the Android
       plugin generated for you at:
       E:\IT\youban\module_c++\build\intermediates\ndk\release\Android.mk
      Alternatively, you can use the experimental plugin:
       https://developer.android.com/r/tools/experimental-plugin.html
      To continue using the deprecated NDK compile for another 60 days, set 
      android.deprecatedNdkCompileLease=1570504380180 in gradle.properties
    

    解决方案:在build.gradle中添加如下配置即可:

    android{
    
    //增加之后如下信息之后,右键项目的时候Link C++ Project with Gradle选项不再显示;
    //    externalNativeBuild {
    //        ndkBuild {
    //            path file('src/main/jni/Android.mk')
    //        }
    //    }
    
    }
    

附:C调java代码

Java中代码如下:
static {
    
    System.loadLibrary("MyJni");//导入指定的so库文件
}

public void show(){
    System.out.println("hahaha  C++调了我");
}
C中代码如下:
//在c代码里面调用java代码里面的方法

//1 . 找到java代码的 class文件
jclass dpclazz = (*env)->FindClass(env, "com/insworks/module_ccc/CCCTestActivity");
if (dpclazz == 0) {
    return (*(*env)).NewStringUTF(env, "NDK 没有找到指定的类");
}

//2 寻找class里面的方法
jmethodID method1 = (*env)->GetMethodID(env, dpclazz, "show", "()V");
if (method1 == 0) {
    return (*(*env)).NewStringUTF(env, "NDK 没有找到方法");
}

//3.实例化类
jobject jobject1 = (*env)->AllocObject(env, dpclazz);

//4 .调用这个方法
(*env)->CallVoidMethod(env, jobject1, method1);

image.png

image.png

image.png

注意事项:

  1. 原生方法在C和C++的调用方式不同,例如:
/* C */
return (*env)->NewStringUTF(env, "Hello World");
/* C++ */
return env->NewStringUTF("Hello World");

​ 在C语言中,JNIEnv是指向JNINativeInterface结构的指针,使用它必须要解引用。而第一个参数还是env,学过C和C++语言都知道,C语言是面向过程语言,NewStringUTF只是一个函数指针,调用该方法还不清楚调用者,所以要传递env,而C++就不用,因为C++是面向对象语言,这个就不解释咯

2.关于C++调用C函数或者变量

//在C++中引用C语言中的函数和变量,在包含C语言头文件时(假设为cExample.h),需进行以下处理:
//  extern "C"
//  {
//    #include "cExample.h";
//  }

3.关于so文件的名称问题

Android.mk文件中的LOCAL_MODULE 决定了so文件的名称,LOCAL_MODULE 的名称可以手动修改也可以在build.gradle中配置:

       ndk {
//            moduleName "native_datahelp"//编译后so库的名称

        }

4.关于System.loadLibrary();

为什么loadLibrary中填入的名称不一致,却能依然运行成功不报错?

目前自测发现 build文件中出现很多编译后的so库 有可能是旧so库未及时清除的原因,可以过研究研究build文件夹,里面藏着非常多的秘密

5.ndk-build不执行照样运行成功

C/C++源码文件改动后自动调用ndk-build编译生成新的so库存放在build文件夹中,可以拷贝直接使用,ndk-build生成正式的so库,这跟apk打包是同样的道理 Android.mk文件是必须存在的,否则无法编译生成so库,也无法编译识别源文件