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环境变量有很多种方式;

 

    

 

     

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

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

 

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

 

  
  
  1. public class MainActivity extends AppCompatActivity {
  2.    
  3.     @Override
  4.     protected void onCreate( Bundle savedInstanceState) {
  5.         super.onCreate(savedInstanceState);
  6.        setContentView( R.layout.activity_main);
  7.    }
  8.     //创建的本地方法,具体功能在C或者C++中实现
  9.    public native String fromJNIString();
  10. }
 
四,手动创建本地方法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

 

  
  
  1. javah -d D:\ Demo\ NDKDemo\ app\ src\ main\ jni -classpath  D:\ Demo\ NDKDemo\ app\ src\ main\ java
  2. 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)

 

              

 

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

 

  
  
  1. / * DO NOT EDIT THIS FILE - it is machine generated * /
  2. #include <jni.h >
  3. / * Header for class com_ang_ndkdemo_MainActivity * /
  4. #ifndef _Included_com_ang_ndkdemo_MainActivity
  5. #define _Included_com_ang_ndkdemo_MainActivity
  6. #ifdef __cplusplus
  7. extern "C" {
  8. #endif
  9. / *
  10. * Class:     com_ang_ndkdemo_MainActivity
  11. * Method:    fromJNIString
  12. * Signature: ()Ljava/lang/String;
  13. */
  14. JNIEXPORT jstring JNICALL Java_com_ang_ndkdemo_MainActivity_fromJNIString
  15.  (JNIEnv *, jobject);
  16. / *JNIEnv * 是定义任意 native函数的第一个参数(包括调用JNI的RegisterNatives函数注册的函数),指向JVM函数表的指针,函数表中的每一个入口指向一个JNI函数,每个函数用于访问JVM中特定的数据结构。 * /
  17. #ifdef __cplusplus
  18. }
  19. #endif
  20. #endif
 

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

 

注意:

 

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

 

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

 

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

 

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

 
五,在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.   
 

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

  
  
  1. #include <com_ang_ndkdemo_MainActivity.h>
  2. #include <stdio.h>
  3. JNIEXPORT jstring JNICALL
  4. Java_com_ang_ndkdemo_MainActivity_fromJNIString (JNIEnv *env, jobject obj)
  5. {
  6.     return env-> NewStringUTF( "I am From Native C");
  7. }
 

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

 

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

 

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

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

也可以不配置ndk{},这里只是指定编译出哪几种对应的abi架构的.so库,如果不配置,会根据ndk-build默认输出对应的abi架构的.so库;最好配置,不然不能编译出自己想要的对应ABI架构的.so,如果自己的项目中已经引用其他的.so库还要做适配;

 

  

 

  
  
  1.    defaultConfig {
  2.        applicationId "com.ang.demo"
  3.        minSdkVersion 19
  4.        targetSdkVersion 28
  5.        versionCode 1
  6.        versionName "1.0"
  7.        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
  8.         / /ndk编译生成.so文件
  9.        ndk{
  10.            moduleName "Java2c"     / /生成的so名字,Android.mk文件中已经指定了,这里可以不写
  11.            abiFilters "armeabi", "armeabi-v7a", "x86"   / /输出指定三种abi体系结构下的so库。
  12.        }
  13.    }
 
七,编写Android.mk文件
 

Android.mk文件一般包含如下信息就够了,差不多可算得上一个模板;根据自己的.so库名和C或者C++文件名修改一下就可以用了;

 

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

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

 

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

 

 报错:

 

        

 

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

 

         

 

Android.mk文件如下: 

 

  
  
  1. LOCAL_PATH : = $( call my-dir)
  2. include $(CLEAR_VARS)
  3. LOCAL_MODULE : = Java 2c
  4. LOCAL_LDFLAGS : = -Wl,--build-id
  5. LOCAL_SRC_FILES : = \
  6.     D:\Demo\NDKDemo\app\src\main\jni\Hello.cpp \
  7. LOCAL_C_INCLUDES + = D:\Demo\NDKDemo\app\src\debug\jni
  8. LOCAL_C_INCLUDES + = D:\Demo\NDKDemo\app\src\main\jni
  9. include $(BUILD_SHARED_LIBRARY)
 
八,修改默认编译工具 
 

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

 

 

 

 

 

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

注意:有的时候需要再次显示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 就是配置的脚本路径;

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

注意:加载生成的动态库指定的文件名(System.loadLibrary("Java2c");)和生成.so时指定的名字(buil.gradle中的ndk{moduleName "Java2c" }),还有Android.mk中LOCAL_MODULE := Java2c三者是否一致;

 

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

 

  
  
  1. public class MainActivity extends AppCompatActivity {
  2.     @Override
  3.     protected void onCreate( Bundle savedInstanceState) {
  4.         super.onCreate(savedInstanceState);
  5.        setContentView( R.layout.activity_main);
  6.         Toast.makeText( this, new Java2C().fromJNIString(), Toast. LENGTH_LONG).show();
  7.    }
  8.     //加载.so库 Java2c为库名
  9.    static {
  10.         System.loadLibrary( "Java2c");
  11.    }
  12.    public native String fromJNIString();
  13. }
 

关于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
 

  
  
  1.     / / gradle高版本新写法
  2.    sourceSets {
  3.        main {
  4.            jniLibs.srcDirs = [ 'src/main/libs']
  5.        }
  6.    }
 
  • 2, 可以把生成的.so复制到main目录下和java同级的jniLibs(如果没有,手动创建一个)目录下,默认的库文件路径就是src/main/jniLibs,这样就不用在build.gradle(Model:app)中配置sourceSets了;
 

              

 
  • 3, 也可以把.so库复制到app目录下的libs目录中,这里也需要在build.gradle(Model.app)配置  注意:和2的区别
 

  
  
  1.     sourceSets {
  2.         main {
  3.           jniLibs.srcDirs = [ 'libs']
  4.        }
  5.    }
 

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

 
其他相关
 

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

 

1,基于 Make 的 ndk-build。

 

2,CMake。

 

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

 

 

 
   

如果对您有所帮助的话

   

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

 
               
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值