Ndk开发过程遇到或大或小的问题,决定用一篇博客记录下来。同时为之后的逆向原生做准备。
开发环境
- android studio
- Ndk
步骤
1 准备native函数
2 生成头文件
3 编写native函数对应的c++或c程序
4 准备 Android.mk Application.mk文件
5 ndk -build生成so文件
在正式开发开始之前,我们先做一些准备工作,在android studio 准备两个使用插件帮我们简化,javah -jni生成头文件,以及ndk -build生成so文件的过程。
File -> Settings...->Tools -> External Tools
点绿+号键添加,在这只贴出怎么配置,对as插件有兴趣的同学,请自行百度。
首先是添加javah -h插件
Program:$JDKPath$\bin\javah.exe
Parameters:-classpath . -jni -d $ModuleFileDir$/src/main/jni $FileClass$
Working directory:$ModuleFileDir$\src\main\java
添加 ndk -build插件
Promgram:D:\AppData\Local\Android\sdk\ndk-bundle\ndk-build.cmd(取决与自己的ndk路径,可在local.properties 中查看)
Working directory:$ModuleFileDir$\src\main\
正式开始我们的Ndk开发:
第一步: 准备native函数
新建一个工程,在MainActivity里我们准备了三个native函数, 前2个我们用静态注册的方法实现,之后采用动态注册 第三个函数。
可以看到1 ,2的不同在于,1是非静态函数,2是静态函数。java层的逻辑是三个Button,点击Button分别从三个函数中获取字符串,并通过Toast展示。
类的路径是:com.conghuachaodan.myjnitest.MainActivity
在MainActivity里通过静态字段加载so
static {
System.loadLibrary("JniTest");
}
第二步 生成头文件并编写c++(c)程序
这一步我们将使用我们刚添加的javah -jni插件,至于javah命令不清楚的可自行百度,这里我们主要是了解jni开发流程,以及为逆向做准备。我们在MainActivity处右击选择External Tools选择我们javah -jni 插件
这时候我们可以看到多了一个jni文件夹,多出了一个.h的头文件。
在jni文件夹新建test.cpp,包含我们刚才的头文件,从头文件从拷出前两个函数实现形式,并进行相应补充(补充参数及函数体,这里我们就简单返回可以区别这三个函数的简单字符串),实现如下:
JNIEXPORT jstring JNICALL Java_com_conghuachaodan_myjnitest_MainActivity_getFirString
(JNIEnv * env, jobject object){
return env -> NewStringUTF( "This is First String");
}
/*
* Class: com_conghuachaodan_myjnitest_MainActivity
* Method: getSecString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_conghuachaodan_myjnitest_MainActivity_getSecString
(JNIEnv * env, jclass clazz){
return env -> NewStringUTF("This is Second String");
}
静态注册最显著的特点就是函数名,例如:Java_com_conghuachaodan_myjnitest_MainActivity_getFirString 是我们类全路径加函数名,以_分隔。
在这里我们可以看出java层非静态函数和静态函数在native层实现的区别在于第二参,非静态函数是jobject object,代表是一个对象,静态函数则是jclass clazz,代表的是一个类。这是很容易理解的。在java中非static函数为实例化对象所有,而static函数为类所有。
接下来实现我们的动态注册。先贴出代码如下:
jstring native_String(JNIEnv *env, jobject object) {
return env -> NewStringUTF("This is Third String");
}
static JNINativeMethod getMethods[] =
{ {"getThirdString","()Ljava/lang/String;",(void *) native_String} };
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv *env = NULL;
jint result = JNI_FALSE;
if (vm->GetEnv((void**) &env,JNI_VERSION_1_6) != JNI_OK)
return result;
if (env == NULL)
return result;
jclass clazz = env -> FindClass("com/conghuachaodan/myjnitest/MainActivity");
if (clazz == NULL)
return result;
if (env -> RegisterNatives( clazz,getMethods, sizeof(getMethods) / sizeof(getMethods[0])) < 0)
return result;
result = JNI_VERSION_1_6;
return result;
}
动态注册的主要步骤就是在JNI_OnLoad中通过env -> RegisterNatives(...)实现。
在JNI_OnLoad中首先通过vm得到env,在RegisterNatives(...)中有三个参数,第一个clazz是我们的目标类也就是MainActivity,这里我们通过env -> FindClass(...)在参数中传入全路径,以 "/"分隔。第二个参数是我们要注册的函数在native实现的函数数组,第三参则是我们需要动态注册函数的数量。
native_String函数是我们需要注册的第三个java native函数的实现。添加到JNINativeMethod getMethods[]中
static JNINativeMethod getMethods[] =
{ {"getThirdString","()Ljava/lang/String;",(void *) native_String} };
在数组中我们可以清楚看到,每个JNINativeMethod分为三项,第一项是对应java的函数名称,第二项对应参数形式及返回值,第三项则是我们在native实现函数的函数指针。到此我们在native层的代码编写完成。
贴出全部代码:
//
// Created by Administrator on 2017/10/16.
//
#include <jni.h>
#include <stdlib.h>
#include <Android/log.h>
#define TAG "MainActivity"
#include "com_conghuachaodan_myjnitest_MainActivity.h"
/*
* Class: com_conghuachaodan_myjnitest_MainActivity
* Method: getFirString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_conghuachaodan_myjnitest_MainActivity_getFirString
(JNIEnv * env, jobject object){
return env -> NewStringUTF( "This is First String");
}
/*
* Class: com_conghuachaodan_myjnitest_MainActivity
* Method: getSecString
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_conghuachaodan_myjnitest_MainActivity_getSecString
(JNIEnv * env, jclass clazz){
return env -> NewStringUTF("This is Second String");
}
jstring native_String(JNIEnv *env, jobject object) {
return env -> NewStringUTF("This is Third String");
}
static JNINativeMethod getMethods[] =
{ {"getThirdString","()Ljava/lang/String;",(void *) native_String} };
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) {
JNIEnv *env = NULL;
jint result = JNI_FALSE;
if (vm->GetEnv((void**) &env,JNI_VERSION_1_6) != JNI_OK)
return result;
if (env == NULL)
return result;
jclass clazz = env -> FindClass("com/conghuachaodan/myjnitest/MainActivity");
if (clazz == NULL)
return result;
if (env -> RegisterNatives( clazz,getMethods, sizeof(getMethods) / sizeof(getMethods[0])) < 0)
return result;
result = JNI_VERSION_1_6;
return result;
}
第三步 添加Android.mk,Application.mk,内容如下,不多作解释
//Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_C_INCLUDES := $(LOCAL_PATH)/include LOCAL_LDLIBS += -L$(SYSROOT)/usr/lib -llog LOCAL_MODULE := JniTest LOCAL_SRC_FILES := test.cpp include $(BUILD_SHARED_LIBRARY)//Application.mk
APP_ABI := armeabi我们的准备完成,接下来将进行build
第四步 进行ndk -build
选中test.cpp,右击->External Tools选择我们的插件ndk -build,此时不出问题,我们应该已经多了libs,obj文件夹
在main下新建jnilibs文件夹,将jni下的文件和libs,obj下的文件移至这个目录下。
在build.gradle(app) android{...}中添加
sourceSets {
main {
jniLibs.srcDir 'libs'
}
}
至此,我们的静态注册和动态注册已经完成了