Android开发之JNI编程

Android开发中,为了保护代码(比如接口之类的核心代码),主要的方式有混淆和加固。混淆是Google官方的方式,加固这种第三方的不多说,使用的恶心。本文介绍Android开发中的JNI编程,通过用C/C++去编写核心代码,然后在去调用达到保护代码的目的。

开发环境:Android Studio1.3.1、NDK-r10e。

1、配置好NDK、SDK、JDK等路径,如下图所示

接下来,按照“官方”的流程:

a.编写带有native方法的Java类(本地方法接口),并生成.class文件;

b.使用javah命令生成.h头文件;

c.编写代码实现头文件中的方法;

d.使用NDK生成.so文件

2、添加本地方法接口

主要是添加了两个本方法(一个用于Java调用C/C++函数,另一个是被用于C/C++调用的Java方法)以及声明所调用的Library(后面将会讲解)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView textView = (TextView) findViewById(R.id.textview);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
callShowDialogMethod();//
}
});
textView.setText(getStringFromNative());
}
public void showMessage(){// 将被C/C++函数调用的方法,用于弹出一个Dialog
AlertDialog.Builder builder=new AlertDialog.Builder(this);
builder.setTitle("Native方法调用Java方法");
builder.setMessage("这是一个Native调用Java方法的例子!");
builder.show();
}
public native String getStringFromNative();// Java调用C/C++函数
public native int callShowDialogMethod();// C/C++调用的Java方法
static {
System.loadLibrary("androidccpp");// 声明所调用的库名称,载入.so文件
}
}

3、给类生成.class文件,如下所示

执行完后可在build/intermediates/classes/debug/co/lujun/jnitest(这里是当前我的路径)路径下查看。

4、生成头文件

在Android Studio终端下,进入到当前module的src/main目录下(为什么是该目录下执行命令,因为我们需要的.h头文件也需要在该目录下的jni文件夹中);

执行命令:javah -d jni -classpath ;<生成的.class文件目录> 生成的.class文件完整名称

javah -d jni -classpath F:...省略…\sdk\platforms\android-23\android.jar;....\build\intermediates
\classes\debug co.lujun.myapplication.MainActivity

如图所示:

成功后发现src/main目录下多出了一个jni文件夹,其中包含我们需要的头文件,如图:

内容如下:

当然,我们也可以写一个.sh(Linux)或.bat(Windows)文件,放在src/main目录下,以后直接执行(需安装Bash support插件),省去每次敲重复命令的操作,代码如下:

1
2
3
4
5
6
7
8
9
#!/bin/sh
export ProjectPath=$(cd "../$(dirname "$1")"; pwd)
export ANDROID_JAR_PATH="F:\code\android\adt-bundle-windows-x86_64-20140702\sdk\platforms\android-23\android.jar"
export DEBUG_CLASS_PATH="..\..\build\intermediates\classes\debug"
export TargetClassName="co.lujun.myapplication.MainActivity"
export SourceFile="${ProjectPath}/jnitest/src/main/java"
cd "${SourceFile}"
javah -d jni -classpath ${ANDROID_JAR_PATH};${DEBUG_CLASS_PATH} ${TargetClassName}
1
2
3
4
5
6
#!/bin/bat
set ANDROID_JAR_PATH=F:\code\android\adt-bundle-windows-x86_64-20140702\sdk\platforms\android-23\android.jar
set DEBUG_CLASS_PATH=..\..\build\intermediates\classes\debug
set TargetClassName=co.lujun.jnitest.MainActivity
javah -d jni -classpath %ANDROID_JAR_PATH%;%DEBUG_CLASS_PATH% %TargetClassName%

5、编写C/C++文件实现头文件中的方法

对应上面生成的头文件,在src/main/jni目录下,就可以编写我们的C/C++文件了。这里我们创建了一个main.c文件,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* DO NOT EDIT THIS FILE - it is machine generated */
#include jni.h;
#include android/log.h;
#ifndef LOG_TAG
#define LOG_TAG "JNI_TEST" // 定义的日志的TAG
#define LOGE(...) __android_log_print (ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
#endif
/* Header for class co_lujun_jnitest_MainActivity */
#ifndef _Included_co_lujun_jnitest_MainActivity
#define _Included_co_lujun_jnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Java调用此函数,打印日志并返回一个字符串
* Class: co_lujun_jnitest_MainActivity
* Method: getStringFromNative
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_co_lujun_jnitest_MainActivity_getStringFromNative
(JNIEnv* env, jobject jObj)
{
LOGE("this is a jni test log!");
return (*env)->NewStringUTF(env,"called native method and pass you this message!");
}
/*
* Java方法调用此函数后,此函数会调用类中的showMessage()方法显示一个Dialog
* Class: co_lujun_jnitest_MainActivity
* Method: callShowDialogMethod
* Signature: (Ljava/lang/int;;)V
*/
JNIEXPORT jint JNICALL Java_co_lujun_jnitest_MainActivity_callShowDialogMethod
(JNIEnv* env, jobject jObj)
{
jclass clazz = (*env)->GetObjectClass(env, jObj);// 获取对象的类
jmethodID method_id = (*env)->GetMethodID(env, clazz, "showMessage", "()V");// 获取方法ID
(*env)->CallVoidMethod(env, jObj, method_id);// 使用Call<Type>Method系列的函数来调用接口方法
return 0;
}
#ifdef __cplusplus
}
#endif
#endif

*其中”()V”,括号是参数,V是返回值(void),如有参数string,即为”(Ljava/lang/String;)V”;如果返回string,则为”(Ljava/lang/String;)Ljava/lang/String;”。

JNI语法可阅读这篇文章http://zhuruenzhe2011-163-com.iteye.com/blog/1630531

6、配置build.gradle文件,以生成.so文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
android {
compileSdkVersion 23
buildToolsVersion "22.0.1"
defaultConfig {
applicationId "co.lujun.jnitest"
//...
ndk {
moduleName "androidccpp"
ldLibs "log", "z", "m"
abiFilters "armeabi", "armeabi-v7a", "x86"
}
}
}
1
2
3
moduleName "androidccpp"    //对应的Library的名字,生成后的.so文件的全称对应为libandroidccpp.so
ldLibs "log", "z", "m"    // 链接时使用到的库,比如有Log打印
abiFilters "armeabi", "armeabi-v7a", "x86"    // 生成三种abi体系结构下的so库

7、生成.so文件

这一步很简单,只需要build/Rebuild Project就行。生成的.so文件如下图,对应不同的abi体系结构。

最后就可以运行程序了!

Java方法调用C/C++函数时打印出来的日志。

*在project的build.gradle文件中,使用的gradle版本是1.2.3,最新的1.3.0好像不支持,如果使用1.3.0,AS会提示要求使用实验版本的gradle构建。如下:

使用实验版Gradle plugin:http://tools.android.com/tech-docs/new-build-system/gradle-experimental

源码:https://gitlab.com/lujun/JNIDemo

参考:

http://blog.csdn.net/super_level/article/details/21238817

http://blog.csdn.net/sodino/article/details/41946607

http://blog.csdn.net/a345017062/article/details/8068925