Android NDK开发,使用ndk-build编译

目录

一,开发环境

二,配置NDK环境变量: 

三,在自己项目创建本地方法:

四,手动创建本地方法fromJNIString()对应的.h头文件

五,在jni目录下创建c或者c++文件,名字随意

六,配置build.gradle(Model:App)

七,编写Android.mk文件(自行百度吧,我也不太会) 如下是我的自动生成方式:

九,最后在MainActivity中加载我们生成的动态库:

                                         手动编译.so文件


一,开发环境

      win10   AndroidStudio 3.1.2  NDK版本:R16

二,配置NDK环境变量: 

       和配置Java JDK环境变量相同,不会的可以自行百度,配置NDK环境变量有很多种方式;

    Android NDK开发,使用ndk-build编译_第1张图片

     Android NDK开发,使用ndk-build编译_第2张图片

三,在自己项目创建本地方法:

即:在Java类中创建带有native的方法;

public class MainActivity extends AppCompatActivity {
    

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

    //创建的本地方法,具体功能在C或者C++中实现
    public native String fromJNIString();

}

四,手动创建本地方法fromJNIString()对应的.h头文件

1,在电脑的cmd 或者AndroidStudio的Terminal中输入javah -d D:\Demo\NDKDemo\app\src\main\jni -classpath D:\Demo\NDKDemo\app\src\main\java com.ang.ndkdemo.MainActivity

javah -d D:\Demo\NDKDemo\app\src\main\jni -classpath D:\Demo\NDKDemo\app\src\main\java 
com.ang.ndkdemo.MainActivity
  • a, -d  D:\Demo\NDKDemo\app\src\main\jni      创建jni文件夹并指定.h输出目录
  • b, D:\Demo\NDKDemo\app\src\main\jni           要创建的.h头文件输出的绝对路径
  • c, D:\Demo\NDKDemo\app\src\main\java  com.ang.ndkdemo.MainActivity    包含本地方法(fromJNIString())的类路径;注意不要写成了 D:\Demo\NDKDemo\app\src\main\java\com\ang\ndkdemo\MainActivity(把包名中的点“.”写成了斜杠“\”,这样写是不对的) ;com.ang.ndkdemo.MainActivity(注意是包名+类名);
  • 参数说明

-classpath :类搜索路径,这里表示从当前的D:\Demo\NDKDemo\app\src\main\java目录下查找

-d :将生成的头文件放到当前的jni目录下

-o : 指定生成的头文件名称,默认以类全路径名生成(包名+类名.h)

注意:-d和-o只能使用其中一个参数。

注意: -d D:\Demo\NDKDemo\app\src\main\jni 和 -classpath D:\Demo\NDKDemo\app\src\main\java  位置可以互换;一下写法和等价于上面的写法;

javah -classpath D:\Demo\NDKDemo\app\src\main\java -d D:\Demo\NDKDemo\app\src\main\jni com.ang.ndkdemo.MainActivity

            

补充:可以通过-o指定生成的头文件名称,如果不指定,默认以类全路径名生成(包名+类名.h)

javah -classpath E:\Demo\JNIDemo\app\src\main\java -o E:\Demo\JNIDemo\app\src\main\java\jni\JNITest.h com.ang.MainActivity

2,执行以上命令之后:就在项目的main文件夹下创建了jni文件夹,并且在jni文件夹下自动创建了.h头文件;头文件名也是自动生成的,命名规则是com_ang_ndkdemo_MainActivity.h(包名+类名.h)

              Android NDK开发,使用ndk-build编译_第3张图片

3,自动生成的com_ang_ndkdemo_MainActivity.h头文件代码

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_ang_ndkdemo_MainActivity */

#ifndef _Included_com_ang_ndkdemo_MainActivity
#define _Included_com_ang_ndkdemo_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_ang_ndkdemo_MainActivity
 * Method:    fromJNIString
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_ang_ndkdemo_MainActivity_fromJNIString
  (JNIEnv *, jobject);

/*JNIEnv* 是定义任意native函数的第一个参数(包括调用JNI的RegisterNatives函数注册的函数),指向JVM函数表的指针,函数表中的每一个入口指向一个JNI函数,每个函数用于访问JVM中特定的数据结构。*/

#ifdef __cplusplus
}
#endif
#endif

4,生成.h头文件时候,如果出现“找不到类文件”的错误请参考  https://blog.csdn.net/ezconn/article/details/82352531这篇文章

五,在jni目录下创建c或者c++文件;

文件名可以随意写,但需要注意文件类型;Hello.c文件(.c后缀的文件为C)代表内容是C代码;Hello.cpp(.cpp后缀的文件为C++)文件代表内容是C++代码;

Android NDK开发,使用ndk-build编译_第4张图片

Android NDK开发,使用ndk-build编译_第5张图片

C++代码(注意C和C++代码是有区别),以下分别给出C和C++两种实现方式:

  • a,Hello.c文件。在C中没有引用,传递的env是个两级指针,用(*env)->调用方法且方法中要传入env.   
#include 
#include "com_ang_ndkdemo_MainActivity.h"
 JNIEXPORT jstring JNICALL
 Java_com_ang_ndkdemo_MainActivity_fromJNIString(JNIEnv* env, jobject obj) {
     return (*env)->NewStringUTF(env,"I am From Native C");
 }
  • b, Hello.cpp文件。C++中env为一级指针,用env->调用方法,无需传入env;C++语言在编译的时候为了解决函数的多态问题,会将函数名和参数联合起来生成一个中间的函数名称,而C语言则不会,因此会造成链接时找不到对应函数的情况,此时C函数就需要用extern "C"进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间函数名;exter  "C"{jni代码}。
#include 
#include 

JNIEXPORT jstring JNICALL
Java_com_ang_ndkdemo_MainActivity_fromJNIString(JNIEnv *env, jobject obj)
{
    return env->NewStringUTF("I am From Native C");
}

六,配置build.gradle(Model:App)

  Android NDK开发,使用ndk-build编译_第6张图片

    defaultConfig {
        applicationId "com.ang.demo"
        minSdkVersion 19
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"

        //ndk编译生成.so文件
        ndk{
            moduleName "Java2c"         //生成的so名字
            abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。
        }
    }

七,编写Android.mk文件(自行百度吧,我也不太会) 如下是我的自动生成方式:

a, 紧接着步骤六之后,点击Androidstudio 菜单栏 Build ->ReBuildProject

 报错:

        Android NDK开发,使用ndk-build编译_第7张图片

b, 在app ——> build ——>intermediater——>ndk(自动创建)目录下自动创建了一个Android.mk文件

         Android NDK开发,使用ndk-build编译_第8张图片

Android.mk文件如下: 

LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)

LOCAL_MODULE := Java2c
LOCAL_LDFLAGS := -Wl,--build-id
LOCAL_SRC_FILES := \
	D:\Demo\NDKDemo\app\src\main\jni\Hello.cpp \

LOCAL_C_INCLUDES += D:\Demo\NDKDemo\app\src\debug\jni
LOCAL_C_INCLUDES += D:\Demo\NDKDemo\app\src\main\jni

include $(BUILD_SHARED_LIBRARY)

八,Android.mk文件复制到jni目录下; 鼠标右键,点击Link C++ Project with Gradle修改Androidstudio默认编译工具,在BuildSystem栏选择ndk—build,在ProjectPath选项栏,找到刚才复制的Android.mk文件(其实就是Android.mk文件路径),点击OK之后就在build.gradle(Model:App)的android{}中自动生成了externalNativeBuild { ndkBuild { path 'src/main/jni/Android.mk' } }

Android NDK开发,使用ndk-build编译_第9张图片

Android NDK开发,使用ndk-build编译_第10张图片

Android NDK开发,使用ndk-build编译_第11张图片

Android NDK开发,使用ndk-build编译_第12张图片

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

注意:有的时候需要再次显示Link C++ Project with Gradle选项,删掉externalNativeBuild {  ndkBuild {   path 'src/main/jni/Android.mk'   } }点击sync now同步一下;再次右键项目就可以出现了;

九,最后在MainActivity中加载我们生成的动态库:

注意:加载生成的动态库指定的文件名和生成.so时指定的名字,还有Android.mk中LOCAL_MODULE := Java2c三者是否一致;

例如:下图加载生成的动态库指定的文件名为:Java2JNI 和上面生成.so时指定的名字为:Java2c 还有Android.mk中LOCAL_MODULE := Java2c三者不一致,就会出现UnsatisfiedLinkError异常;

public class MainActivity extends AppCompatActivity {


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toast.makeText(this,new Java2C().fromJNIString(),Toast.LENGTH_LONG).show();
    }
    //加载.so库 Java2c为库名
    static {
        System.loadLibrary("Java2c");
    }

    public native String fromJNIString();

}

 

 关于UnsatisfiedLinkError异常的原因还有很多,这里针对NDK开发总结几种可能的原因:https://blog.csdn.net/ezconn/article/details/82531893                                                                            


                                         手动编译.so文件

从步骤八开始的第二种方式,不指定Androidstudio编译工具(Cmake或者ndk-build),直接手动生成.so库

a, cmd 或者Android studio的Terminal 中进入jni的上一级目录

      Android NDK开发,使用ndk-build编译_第13张图片

b, 输入ndk-build命令,在jni同级的目录中生成了一个libs文件夹,里面生成了各个cup架构对应的.so文件,

      Android NDK开发,使用ndk-build编译_第14张图片

c, 应用.so库   

  • 1, 如果不更改手动生成后的.so库的位置,需要在build.gradle(Model.app)配置 
    // gradle高版本新写法
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/libs']
        }
    }
  • 2, 可以把生成的.so复制到main目录下和java同级的jniLibs(如果没有,手动创建一个)目录下

              Android NDK开发,使用ndk-build编译_第15张图片

  • 3, 也可以把.so库复制到app目录下的libs目录中,这里也需要在build.gradle(Model.app)配置  注意:和2的区别
    sourceSets {
        main {
           jniLibs.srcDirs = ['libs']
        }
    }

d, 注意:这种手动生成.so库的方式,使用ndk17生成失败,以上都是应用的ndk16,由于ndk17已不在支持mips、armeabi CPU架构,

End

其他相关

使用 NDK 编译代码主要有三种方法:

基于 Make 的 ndk-build。
CMake。
独立工具链,用于与其他编译系统集成,或与基于 configure 的项目搭配使用。

你可能感兴趣的:(NDK)