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

1、开发环境

      win10   AndroidStudio 3.1.2  NDK 版本:R16java

2、配置 NDK 环境变量: 

       和配置 Java JDK 环境变量相同,不会的能够自行百度,配置 NDK 环境变量有不少种方式;android

    

     

3、在本身项目建立本地方法:

即:在 Java 类中建立带有 native 的方法;c++

项目或者应用的包名:com.ang.ndkdemomarkdown

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();
 
}
复制代码

4、手动建立本地方法 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 目录下app

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

注意:-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)

              

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

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* 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 头文件时候,若是出现 “找不到类文件” 的错误请参考  blog.csdn.net/ezconn/arti… 这篇文章

注意:

a. 包名或类名或方法名中含下划线 _ 要用 _1 链接;

b. 重载的本地方法命名要用双下划线 __ 链接;

c. 参数签名的斜杠 “/” 改成下划线 “_” 链接,分号 “;” 改成 “_2” 链接,左方括号 “[” 改成 “_3” 链接;

另外,对于 Java 的 native 方法,static 和非 static 方法的区别在于第二个参数,static 的为 jclass,非 static 的 为 jobject;JNI 函数中是没有修饰符的。 

5、在 jni 目录下建立 c 或者 c++ 文件;

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

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

  • a,Hello.c 文件。在 C 中没有引用,传递的 env 是个两级指针,用(*env)-> 调用方法且方法中要传入 env.   
#include <jni.h>
#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 <com_ang_ndkdemo_MainActivity.h>
#include <stdio.h>
 
JNIEXPORT jstring JNICALL
Java_com_ang_ndkdemo_MainActivity_fromJNIString(JNIEnv *env, jobject obj)
{
    return env->NewStringUTF("I am From Native C");
}
复制代码

Java 的 native 方法是如何连接 C/C++ 中的函数的呢?能够经过静态和动态的方式注册 JNI。 以上是经过静态注册的方式。

静态注册:根据函数名创建 Java 本地方法和 JNI 函数的一一对应关系。

动态注册:直接告诉 Java native 方法其在 JNI 中对应函数的指针。

6、配置 build.gradle(Model:App)

也能够不配置 ndk{}, 这里只是指定编译出哪几种对应的 abi 架构的. so 库,若是不配置,会根据 ndk-build 默认输出对应的 abi 架构的. so 库;最好配置,否则不能编译出本身想要的对应 ABI 架构的. so,若是本身的项目中已经引用其余的. so 库还要作适配;

  

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名字,Android.mk文件中已经指定了,这里能够不写
            abiFilters "armeabi", "armeabi-v7a", "x86"  //输出指定三种abi体系结构下的so库。
        }
    }
复制代码

7、编写 Android.mk 文件

Android.mk 文件通常包含以下信息就够了,差很少可算得上一个模板;根据本身的. so 库名和 C 或者 C++ 文件名修改一下就能够用了;

LOCAL_PATH:= $(call my-dir)#不用修改
 
 
include $(CLEAR_VARS)#不用修改
 
LOCAL_MODULE:= hello #动态库名称
LOCAL_SRC_FILES:= hello.c #C文件,里面就是咱们写的C代码
 
include $(BUILD_SHARED_LIBRARY)#生成.so动态库
 
#include $(BUILD_STATIC_LIBRARY) 编译出.a的静态库
复制代码

还有一种方式,就是让 androidstudio 自动生成;以下是我获取自动生成的 Android.mk 文件的方式:

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

 报错:

        

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

         

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)
复制代码

8、修改默认编译工具

鼠标右键项目,点击 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'} }

//增长以后以下信息以后,右键项目的时候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 同步一下;再次右键项目就能够出现了;

**相关知识:**要将 Gradle 关联到原生库,须要提供一个指向 CMake 或 ndk-build 脚本文件的路径。在构建应用时,Gradle 会以依赖项的形式运 CMake 或 ndk-build,并将共享的. so 库打包到 APK 中。externalNativeBuild 就是配置的脚本路径;

9、最后在 MainActivity 中加载咱们生成的动态库:

注意:加载生成的动态库指定的文件名(System.loadLibrary("Java2c");)和生成. so 时指定的名字(buil.gradle 中的 ndk{moduleName "Java2c" }),还有 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 库放到指定的路径下;若是须要. so 库(给其余项目使用,例如使用百度地图服务,就要使用其提供的. so 库)以下手动编译并使用. so 库;

                                         手动编译. so 文件

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

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

      

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

      

c, 应用. so 动态库   

  • 1, 若是不更改手动生成后的. so 库的位置,须要在 build.gradle(Model.app) 配置,由于. so 库的默认存放位置是 src/main/jniLibs
// gradle高版本新写法
    sourceSets {
        main {
            jniLibs.srcDirs = ['src/main/libs']
        }
    }
复制代码
  • 2, 能够把生成的. so 复制到 main 目录下和 java 同级的 jniLibs(若是没有,手动建立一个)目录下,默认的库文件路径就是 src/main/jniLibs,这样就不用在 build.gradle(Model:app) 中配置 sourceSets 了;

              

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

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

其余相关

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

1.基于 Make 的 ndk-build。

2.CMake。

3.独立工具链,用于与其余编译系统集成,或与基于 configure 的项目搭配使用。

若是对您有所帮助的话

不妨加个关注,点个赞哈,您的每一个小小举动都是对我莫大的支持!

相关文章
相关标签/搜索