NDK[1]ndk、ADT、AndroidStudio
【参考链接】
向您的项目添加 C 和 C++ 代码https://developer.android.com/studio/projects/add-native-code.html
AndroidStudio 2.2 CMAKE 高效NDK开发http://m.blog.****.net/l_215851356/article/details/74691147
AndroidStudio进行 JNI /NDK 开发http://blog.****.net/zeqiao/article/details/77893167
ndk其实提供了两样东西
1、.h头文件、.a静态库文件、.so动态库文件
2、编译工具,用于编译生成适用于android系统的动态库文件(.so)
以在Windows下开发为例
ndk
先去下载ndk,这里以android-ndk-r14b为例,解压,可以了解一下其中的目录结构
根目录下有ndk-build.cmd文件
platforms目录下有用于各个android版本的不同架构的.h、.a、.so文件,
以android-23版本的arm64架构为例,显然也有jni.h头文件
因为后续要使用到ndk-build,所以可以先把ndk根目录添加到环境变量PATH中,使用时直接输入ndk-build即可
ADT
为了更直观的理解开发过程,虽然已经不再使用ADT开发工具了,但是还是讲一下ADT下的开发步骤
新建一个Android工程,在MainActivity中定义一个native方法
Java Code
1 | package com.example.helloworldfromc; |
先去生成需要的.h文件
在命令行中,定位到上面工程的src目录,执行javah –encoding utf-8 com.example.helloworldfromc.MainActivity,会在src目录下生成com_example_helloworldfromc_MainActivity.h文件
然后开始编写c/c++代码
在工程根目录下新建一个jni目录,c/c++相关的代码都会放到此目录下
将.h剪切到此目录下,在此目录下新建一个test.c文件,实现声明的方法,代码如下
(如果是test.cpp文件,执行的时候会出问题,所以这里用的test.c)
C++ Code
1 | #include "com_example_helloworldfromc_MainActivity.h" |
编译还需要编译脚本,新建一个Android.mk文件,内容如下
LOCAL_PATH := $(call my-dir)
include$(CLEAR_VARS)
#设置打包生成的库文件的名称#会自动生成为libtest.so LOCAL_MODULE := test #对应的C代码的文件 LOCAL_SRC_FILES := test.c
include$(BUILD_SHARED_LIBRARY) |
新建一个Application.mk文件,内容如下,用于设置要生成哪些ABI的.so,这里只要求生成armeabi-v7a的
有关ABI的问题后续会讲
APP_ABI := armeabi, armeabi-v7a |
此时工程的目录结构为
然后开始编译
将命令行定位到工程根目录,执行ndk-build,
会在工程下
生成obj目录,用于保存编译过程中生成的中间文件,可以删除
生成libs目录,里面有生成的.so,名称为libtest.so
接下来就可以在Java中进行调用了
C++ Code
1 | package com.example.helloworldfromc; |
编译生成的apk中就会包含lib文件夹,其中有.so
运行起来可以获取到C代码中返回的字符串
可以看出
在ADT中,需要自己先手动编译生成.so,然后再编译生成apk
这对于一名程序员来说显然是不能接受的
而在Android Studio中,可以一次编译,自动编译.so并添加到apk中
这里使用的Gradle版本为gradle-4.1,GradlePlugin版本为com.android.tools.build:gradle:3.0.0
Android Studio
AndroidStudio提供了两种方式来编译C/C++代码
一种依然是使用make脚本,一种是使用CMake脚本。
这两种方式都需要先在AndroidStudio中设置下ndk的路径,通过修改local.properties文件
make
使用make脚本的方式
依然是先在MainActivity中声明native方法,因为一会儿编译的时候会自动先编译so再加入到apk中,所以可以把调用也写了
package com.example.shadowfaxghh.demo;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
public class MainActivity extends Activity {
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
System.loadLibrary("test");
String str =getStringFromC();
Toast.makeText(this, str, Toast.LENGTH_SHORT).show();
}
public native String getStringFromC();
}
通过javah生成.h头文件
有说可以通过自动提示生成,不过我这里没
然后在src/main目录下新建一个目录jni,放入.h。新增一个test.c,实现代码
#include "com_example_shadowfaxghh_demo_MainActivity.h"
JNIEXPORT jstring JNICALL Java_com_example_shadowfaxghh_demo_MainActivity_getStringFromC
(JNIEnv *env, jobject jobj) {
return (*env)->NewStringUTF(env, "HelloWorld from C");
}
新增一个Android.mk文件,内容跟ADT中的相似(不过并不是完全一样,这个是我出错以后Gradle自动生成的,我把它拿出来使用了)
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#指定要生成的.so的文件名#libtest.so
LOCAL_MODULE := test
#链接
LOCAL_LDFLAGS := -Wl,--build-id
LOCAL_LDLIBS := \
-llog \
-lz \
-lm \
#指定.c源码的路径
LOCAL_SRC_FILES := \
C:\Users\ShadowfaxGHH\Desktop\NdkAndroidStudioDemo1\app\src\main\jni\test.c\
LOCAL_C_INCLUDES +=C:\Users\ShadowfaxGHH\Desktop\NdkAndroidStudioDemo1\app\src\debug\jni
LOCAL_C_INCLUDES +=C:\Users\ShadowfaxGHH\Desktop\NdkAndroidStudioDemo1\app\src\main\jni
include $(BUILD_SHARED_LIBRARY)
新增一个Application.mk文件,内容跟ADT中的一致。不过这个其实没什么用了,后面Gradle中会再设置ABI,应该会覆盖这里的设置。但是这里如果不设置又会报错。
APP_ABI := armeabi
此时工程的目录结构如下
接下来修改module的build.gradle
在android下添加externalNativeBuild块,指定Android.mk文件的路径
externalNativeBuild {
ndkBuild {
path "src/main/jni/Android.mk"
}
}
在defaultConfig下添加externalNativeBuild块
externalNativeBuild{
ndkBuild{
}
}
在defaultConfig下添加ndk块,指定要生成的ABI
ndk{
abiFilters 'armeabi-v7a'
}
这样就配置完成了,编译assembleDebug, 完成以后,虽然工程中看不到.so,但是app-debug.apk中有lib文件夹,其中有.so
(Application.mk中配置的是armeabi,gradle中配置的是armeabi-v7a,从这里也可以看出gradle的覆盖了Application.mk的)
运行起来可以正常调用
CMake
现在Google推荐使用新出的编译工具CMake
要使用CMake,需要先在Android SDK中安装
然后跟上面一样,在src/main目录下新建一个jni目录,放入.h和test.c文件。不再赘述。
不过不需要.mk了,取而代之的是CMake脚本。
在module根目录下新建一个CMakeLists.txt文件,内容如下
cmake_minimum_required(VERSION 3.4.1)
# TODO 添加自己的 C/C++源文件
# 指定生成的动态库的名称为testtt#libtesttt.so #源代码的路径为src/...
add_library( testtt
SHARED
src/main/jni/test.c )
# TODO 添加依赖的NDK中的库
find_library( log-lib
log )
# TODO 链接
target_link_libraries( testtt
${log-lib} )
此时工程的目录结构如下
接下来修改module的build.gradle
在android下添加externalNativeBuild块,指定CMakeLists.text文件的路径
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
在defaultConfig下添加externalNativeBuild块
externalNativeBuild{
cmake{
cFlags ""
cppFlags ""
}
}
在defaultConfig下添加ndk块,指定要生成的ABI (同上面一样)
ndk{
abiFilters 'armeabi-v7a'
}
这样就配置完成了,编译运行,跟上方效果一致,可以正常调用。
这两种方式的gradle的差异对比如下
此外,这里只是讲了一下CMake脚本的简单配置,更多问题可查阅参考链接。