本文主要介绍android中NDK的使用,用简单NDK Demo作为演示。
NDK环境的配置这里不在详细介绍,可以参考: - Windows下配置 - Mac 下配置 在android studio中配置NDK环境比较简单,创建工程之后,使用快捷键(Ctrl + Alt + Shift + S)会跳出Project Structure对话框。点选SDK Location即可看到Android NDK location配置。可直接download即可,可以在官网下载NDK包,配置好路径就行。
创建工程前请确保NDK环境已配置成功。
创建Android工程,工程名称为NDKDemo(包名org.demo.ndk)。工程创建完成后,先编译运行,确保工程没有问题。 检查NDK配置情况。检查Projec Sturcture(Ctrl + Alt + Shift + S),查看是否设置好了NDK。也可以打开local.properties文件,检查是否设置了ndk.dir路径。我的NDK在D:\Android\android-ndk-r13b所以设置为 ndk.dir=D:\\Android\\android-ndk-r13b。打开gradle.properties文件,在最后一行添加。 android.useDeprecatedNdk=true在org.demo.ndk包中新建类,命名为NavHelper,作为测试示例DNK。NDK使用非常简单,只要将函数使用native修饰即可。这里定义了一个helloWorld的测试函数。 使用android studio左下角命令行工具,先定位到上图的java目录,在执行java命令: javah org.demo.ndk.NavHelper (包名 + 类名) 至此,生成C头文件,即得到了在C/C++中需要实现的函数原型声明。 创建jni工作目录。这个目录可由android studio来生成。 在模块app上单击右键—>New—>Folder—>JNI Folder —> Finish,则在src/main/目录下,创建了jni文件夹。 将第4步生成的C头文件移动到jni文件夹,并创建hello.cpp(名字可任取)的C++源文件,作为helloWorld函数的实现。在hello.cpp文件中添加内容如下: #include <string.h> #include <jni.h> #include "org_demo_ndk_NavHelper.h" /** */ JNIEXPORT jstring JNICALL Java_org_demo_ndk_NavHelper_helloWorld(JNIEnv *env, jobject thiz){ return env->NewStringUTF("Hello NDK !"); }在gradle中配置ndk(配置模块app中的gradle)。 在defaultConfig节点中放置ndk,设置moduleName(必须设置)作为NDK加载的名称和abiFilters(可选)输出指定三种abi体系结构下的so库。
android { compileSdkVersion 25 buildToolsVersion "25.0.2" defaultConfig { applicationId "org.demo.ndk" minSdkVersion 15 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk{ moduleName 'NDKDemoName'//作为NDK加载的名称,和System.loadLibrary("NDKDemoName");中名字一致 abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种abi体系结构下的so库,可忽略 } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } }至此,NDK示例的准备工作已经完成,在android studio上方点击Build—>rebuild可在模块 app/build/intermediates/ndk/debug/lib下看到编译生成的库文件。接下来,修改MainActivity和NavHelper来测试。
NavHelper中由static加载库文件,可就是在NavHelper创建对象时,就加载库文件。 package org.demo.ndk; /** * Created by Dell on 2017/5/5. */ public class NavHelper { static { System.loadLibrary("NDKDemoName");//加载库文件,名称和app.gradle文件中设置的moduleName一直 } public native String helloWorld(); } 在MainActivity中调用NavHelper中定义的helloWorld函数。 package org.demo.ndk; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.TextView; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //setContentView(R.layout.activity_main); TextView tv = new TextView(this); NavHelper navHelper = new NavHelper(); tv.setText(navHelper.helloWorld()); setContentView(tv); } }在使用NDK的时候,经常看到其他工程中出现Android.mk、Application.mk这样的文件,像示例工程中生存的库文件中app/build/intermediates/ndk/目录下也存在一个Android.mk的文件。
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := NDKDemoName LOCAL_LDFLAGS := -Wl,--build-id LOCAL_SRC_FILES := \ D:\AndroidStudioProjects\NDK\NDKDemo\app\src\main\jni\hello.cpp \ LOCAL_C_INCLUDES += D:\AndroidStudioProjects\NDK\NDKDemo\app\src\main\jni LOCAL_C_INCLUDES += D:\AndroidStudioProjects\NDK\NDKDemo\app\src\debug\jni include $(BUILD_SHARED_LIBRARY)那么这个文件的作用是什么。在这个网站有详细的讲解:http://android.mk/ 大致可描述为:Android.mk文件用来告知NDK Build 系统关于Source的信息。告知编译系统,在编译时如何处理NDK相关文件及生成库文件放置等。 - Android.mk 文件语法详解:参考链接 - 理解 Android Build 系统:参考链接
最简单的一个Android.mk例子:
LOCAL_PATH := $(call my-dir) #当前 Android.mk文件所在的目录的路径 include $(CLEAR_VARS) #负责清理LOCAL_xxx文件 LOCAL_MODULE := NDKDemoName LOCAL_SRC_FILES := hello.cpp include $(BUILD_SHARED_LIBRARY)因此可以看出,Android.mk文件决定了NDK如何编译C/C++文件。
我们的示例工程就是gradle自动编译完成的,可大致分以下步骤:
定义native函数,并由javah命令生成,C/C++函数头文件。
创建jni目录,编写C/C++实现。
在模块gradle中配置ndk节点,主要设置moduleName。
在gradle中由于许多的,参数都是默认的,所以配置起来比较方便。如果想要修改其中的部分参数可自行配制。比如在android节点下配置sourceSets:
``` sourceSets.main { // 你的源码目录, 如果设定为jni.srcDir=[] 表示禁用通过Gradle来编译本地c/c++代码 //jni.srcDir 'src/main/otherDir' // 你的.so库的实际路径。jniLib.srcDirs定义了编译打包时Gradle在哪里寻找生成的so库文件 //jniLibs.srcDir 'src/main/libs' } ```手动编译方式:
在jni文件中编辑Android.mk文件在android studio中选中Android.mk文件,右键—>Link C++ Project with Gradle—>选ndk-build—>Project Path选择刚编辑Android.mk文件路径—》OK。 完成之后,会在app.gradle中android节点中,生成如下代码: externalNativeBuild { ndkBuild { path 'src/main/jni/Android.mk' } }表明Android.mk文件和NDK 编译连接起来了。
编译时错误消息提示如下:
FAILURE: Build failed with an exception. * What went wrong: Execution failed for task ':app:compileDebugNdk'. > Error: Your project contains C++ files but it is not using a supported native build system. Consider using CMake or ndk-build integration with the stable Android Gradle plugin: https://developer.android.com/studio/projects/add-native-code.html or use the experimental plugin: https://developer.android.com/studio/build/experimental-plugin.html.解决方法: 在工程根目录gradle.properties文件最后一行添加:android.useDeprecatedNdk=true
程序执行时,直接在模拟器中显示 xxx has stop。Android Monitor中错误提示为:
AndroidRuntime: FATAL EXCEPTION: main Process: org.demo.ndk, PID: 8654 java.lang.UnsatisfiedLinkError: dalvik.system.PathClassLoader .... .................................................................. ... .... couldn't find "libNDKDemoName.so" at java.lang.Runtime.loadLibrary0(Runtime.java:984) at java.lang.System.loadLibrary(System.java:1530) at org.demo.ndk.NavHelper.<clinit>(NavHelper.java:10) at org.demo.ndk.MainActivity.onCreate(MainActivity.java:15) at android.app.Activity.performCreate(Activity.java:6679)可能解决方法: 在app.gradle(模块gradle文件中)检查defaultConfig节点下是否配置了ndk{…},如果没有则可能会出现这个问题。
执行程序是Android Monitor提示:
No implementation found for java.lang.String demo.jni.org.jnidemo.NavHelper.printHelloJNI(java.lang.String)表明没有正确加载库文件,因此,
检查是否有加载库文件语句(System.loadlibrary(“库文件名”)),并库文件名是否正确。检查库文件是否正确生成。(生成库文件在app/build/intermediates/ndk/debug/lib中)如果写的是C++源文件,需要添加该函数实现的头文件(头文件即javah命令产生的头文件)JNI调用错误: No implementation found for native 1、JNIEnv *env参数的使用 所有JNI接口的第一个参数是JNIEnv *env, 在C中,使用方法是 (*env)->NewStringUTF(env, “Hello from JNI!”); 但在C++中,其调用方法是 env->NewStringUTF(“Hello from JNI!”); 为什么有这种区别呢,看看jni.h中关于JNIEnv的定义就可以知道了:
#if defined(__cplusplus) typedef _JNIEnv JNIEnv; #else typedef const struct JNINativeInterface* JNIEnv; #endif可以看到,对于C和C++,定义有所不同,主要原因是C不支持类,所以采用了一种变通的方法。
2、接口找不到 在Java中调用JNI接口时,出现异常,察看日志,发现有如下错误: WARN/dalvikvm(422): No implementation found for native Lcom/whty/wcity/HelixPlayer;.setDllPath (Ljava/lang/String;)V 检查了几遍代码,Cpp中确实定义了这个接口,而且仔细对照了Java的包名、类名,确实没有错误,那为什么会出现这种问题呢。后来突然想到,JNI接口 都是以C的方式定义的,现在使用C++实现,函数定义前是否需要加上extern “C”呢?为此定义了一个头文件,在CPP文件中include该头文件,头文件加上如下代码片断:
#ifdef __cplusplus extern "C" { #endif #endif ... #ifdef __cplusplus }参考链接:http://blog.csdn.net/greenapple_shan/article/details/38504305
想要记录NDK中使用C和C++中的区别,主要原因是在写NDKDemo时,我创建的是C++的源文件,在源文件中返回一个字符串时一直报错(文中的helloWorld函数的实现)。故此,对比了下C源文件和C++源文件的实现。这部分可以和错误4联系起来看。
