HelloGcc项目地址 https://github.com/goodluckforme/HelloGcc/graphs/contributors
这边在直接进入主题进入之前 我们先思考以下几个问题:
1.什么是JNI
2.编译JNI步骤和方式
3.Cmake全面降低JNI开发门槛。
最后我将一一列举在编译过程中遇到的问题 和新的思考。
什么是JNI:
这是非虫老师在《android 软件安全与逆向分析》试验7.6.2中给出的案例,着重静态分析Android NDK,解释道JNI(JAVA Native Interface)是Androd NDK的一部分,JNI提供的接口函数,可以在原生的C/C++代码中与JAVA代码进行数据交互,JNI接口函数指针被放到JNINativeInterace与JNIInvokeInterface两个结构体中如下:
#if defined(__cplusplus)
typedef _JNIENV JNIENV;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIENV;
typedef const struct JNIInvokeInterface* JavaVM;
JNIENV的首地址解释成JNINativeInterface的首地址,而JNINativeInterface存放是JNI的接口地址,如下汇编代码中0x29/4=167 即NewStringUTF的偏移地址。每个方法栈4个字节。
LDR R3,[R3,#0x29c];即取出(*env)->NewStringUTF()的地址
2.编译JNI步骤和方式
介绍一下 Android.mk和Application.mk的作用:
Android.mk:工程的编译脚本,描述了编译原生程序支持的ARM硬件指令集,工程编译脚本,StL支持
Application.mk:工程编译脚本,描述原生程序的编译选项、头文件、源文件及依赖库。
方式:
1.使用eclipse的自编写Android.mk方式
2.使用AndroidStudio编写gradle的方式自动生成Android.mk.
3.使用AndroidStudio的Cmake方式,方便快捷。
步骤:
第一代方式:
1.编写Android.mk和Application.mk
Android.mk文件
LOCAL_PATH := $(call my-dir) //固定写法 指的是当前目录
include $(CLEAR_VARS) //固定写法 常量
#LOCAL_ARM_MODE := arm //指定arm
LOCAL_MODULE := TestJniMethodsOne //指定so库名字
#APP_BUILD_SCRIPT:=./Android.mk //指定Android.mk 不需要
LOCAL_LDLIBS := -llog //指定链接库 就是java的import
LOCAL_SRC_FILES := com_xiaomakj_hellogcc_TestJniMethods.c //指定编译的C/C++ 指定多个用"/" 空格 或tab键 分隔
include $(BUILD_SHARED_LIBRARY) 指定目标文件类型为so
Application.mk
APP_ABI:all //指定编译指令集 一般为 "armeabi", "armeabi-v7a", "x86"
其实也可以在build_gradle中设置 ndk { abiFilters "armeabi", "armeabi-v7a", "x86" }
手动生成so我们自己任然需要自己在Application.mk添加相关配置。
//APP_ABI := armeabi armeabi-v7a x86
2.配置AS的exteranl Tool脚本,javah 、ndk_build 、ndk_build clean
在.externalNativeBuild\ndkBuild\debug\armeabi\ndkBuild_build_command.txt 下就可以看到相关的命令行
Ctrl +Shift +S =>Tools => Exteranl Tool=> ‘+’添加
Javah
Programma:$JDKPath$\bin\javah //javah路径
Parameters:-classpath $Classpath$ -v -jni $FileClass$ //注意这个其实是class有效的 需要build生成class 在使用该命令 不然会报找不到错误
Working directory:$SourcepathEntry$\..\jni //目标目录
ndk_build
Programma:D:\android-ndk-r10e\ndk-build.cmd
Parameters:-B //就是 ndk-build -B
Working directory:$ModuleFileDir$\src\main
ndk_build clean
Programma:D:\android-ndk-r10e\ndk-build.cmd
Parameters:clean
Working directory:$ModuleFileDir$\src\main
3.编写JNI类的native方法
非虫老师的TestJniMethods
package com.xiaomakj.hellogcc;
public class TestJniMethods {
public native void test();
public native String nativeMethod();
public native void newJniThreads(int i);
public native Object allocNativeBuffer(long size);
public native void freeNativeBuffer(Object obj);
static {
System.loadLibrary("TestJniMethodsOne");
}
}
4.分别使用脚本生成 c头文件 和 so库文件
5.复制.h头文件 使用C/C++实现该方法
//声明
/* 请在jnimethods.c上点右键,在属性中将文件编码改为utf-8 */
JNIEXPORT void JNICALL Java_com_droider_jnimethods_TestJniMethods_test
(JNIEnv *, jobject);
//实现
JNIEXPORT jstring nativeMethod(JNIEnv* env, jclass clazz){
const char * chs = "你好!NativeMethod";
return (*env)->NewStringUTF(env, chs);
}
生成so
如上 Working directory:
\src\main
最终会在app\src\main\libs下生成so
这也是为什么build_gradle中中需配置的原因
sourceSets.main {
jni.srcDirs = []//禁用as自动生成mk
jniLibs.srcDir "src/main/libs"//这是ndk_build生成的so目录
}
注意:
实现C方法的时候会遇到 JNI调用错误: No implementation found for native
这些完全是不懂C代码写法的小白才会犯的错误
方式二: 指定自定义Android.mk屏蔽AndroidStdio自动配置Android.mk
1.编写Android.mk和Application.mk文件
2.配置AS的build_gradle
//配置NDK_LIBS_OUT位jniLibs则不需要其他配置 否则配置
sourceSets.main {
jni.srcDirs = []//禁用as自动生成mk
jniLibs.srcDir "src/main/libs"
}
方式三 自动生成 ndk_build 但是javah需要手动 将在build时自动生成so到jniLibs
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
commandLine "D:\\android-ndk-r10e\\ndk-build.cmd",//配置ndk的路径
'NDK_PROJECT_PATH=build/intermediates/ndk',//ndk默认的生成so的文件
'NDK_LIBS_OUT=src/main/jniLibs',//配置的我们想要生成的so文件所在的位置
'APP_BUILD_SCRIPT=src/main/jni/Android.mk',//指定项目以这个mk的方式
'NDK_APPLOCATION_MK=src/main/jni/Application.mk'//指定项目以这个mk的方式
}
tasks.withType(JavaCompile) {
//使用ndkBuild
compileTask -> compileTask.dependsOn ndkBuild
}
方式三:As自己生成Android.mk 文件在 so库也会自动生成到intermediates/ndkbuild/下 所以 配置sourceSets.main.jniLibs.srcDir “src/main/libs”貌似没有意义。
1.编写Android.mk和Application.mk文件
2.配置AS的build_gradle
ndk {
moduleName "TestJniMethodsOne"//使用link C++ Gradle Project 之后 貌似没用了 abiFilters有用 这里的ndk会覆盖Android.mk的效果
moduleName "TestJniMethodsTwo"//这玩意不加也能生成多个So库
abiFilters "armeabi", "armeabi-v7a", "x86"//会覆盖Android.mk的效果
}
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'//指向Android.mk
//rebuild project 将自动在build的ndkbuild文件下生成so文件
}
}
这些是利用AS自动生成Android.mk的gradle配置方法.
https://blog.csdn.net/zjclugger/article/details/51305968
方式四:Cmake 这个方式比较新 也最简单 直接点灯
https://blog.csdn.net/b2259909/article/details/58591898
思考一:如何生成多个SO?
https://blog.csdn.net/dreamintheworld/article/details/49848333
https://blog.csdn.net/zjclugger/article/details/51305968 (推荐)
一共介绍了两种方法 推荐第二种
如下只需要复制一份即可
LOCAL_SRC_FILES := jniOne/com_xiaomakj_hellogcc_TestJniMethods.c
如果分包存放 需添加对应路径
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
#LOCAL_ARM_MODE := arm
LOCAL_MODULE := TestJniMethodsOne
#APP_BUILD_SCRIPT:=./Android.mk
LOCAL_LDLIBS := -llog
LOCAL_SRC_FILES := jniOne/com_xiaomakj_hellogcc_TestJniMethods.c
include $(BUILD_SHARED_LIBRARY)
include $(CLEAR_VARS)
#LOCAL_ARM_MODE := arm
LOCAL_MODULE := TestJniMethodsTwo
#APP_BUILD_SCRIPT:=./Android.mk
LOCAL_LDLIBS := -llog
LOCAL_SRC_FILES := jniTwo/com_xiaomakj_hellogcc_MuchJNIShow.cpp
include $(BUILD_SHARED_LIBRARY)
思考二:AS3.0中除了使用CMake外如何开启提示?
如下我已经做了许多尝试并没什么卵用
android.useDeprecatedNdk=true //过时
ndk.dir=D\:\\android-ndk-r10e
sdk.dir=D\:\\android_sdk
Link C++ Project with Gradle 这算是方法三的一种快捷方式
只是多了行,对JNI提示并没什么卵用
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
我的想法是 AS3.0之后抛弃了useDeprecatedNdk ndk_build的方式也没有获得提示,将AS降级试试,再不行就是见鬼了。 着实难受 还是拥抱Cmake吧。不过这个坑也是JNI初学者必须经历的。
思考三:C/C++代码太复杂 我们该如何去学习?
这些都是谷歌推出的最新的NDK案例 全部使用Cmake编写
https://github.com/googlesamples/android-ndk/tree/master/other-builds/ndkbuild
推荐使用第三种方式gradle配置如下
defaultConfig {
···
ndk {
moduleName "TestJniMethodsOne"//使用link C++ Gradle Project 之后 貌似没用了 abiFilters有用 这里的ndk会覆盖Android.mk的效果
moduleName "TestJniMethodsTwo"//这玩意不加也能生成多个So库
abiFilters "armeabi", "armeabi-v7a", "x86"
}
sourceSets.main {
jni.srcDirs = []//禁用as自动生成mk
jniLibs.srcDir "src/main/libs"
}
}
externalNativeBuild {
ndkBuild {
path 'src/main/jni/Android.mk'
}
}
最后介绍一篇C/C++的windosw环境下利用MakeFile的编译方法:
https://www.cnblogs.com/bingghost/p/5721423.html