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(){ AlertDialog.Builder builder=new AlertDialog.Builder(this); builder.setTitle("Native方法调用Java方法"); builder.setMessage("这是一个Native调用Java方法的例子!"); builder.show(); } public native String getStringFromNative(); public native int callShowDialogMethod(); static { System.loadLibrary("androidccpp"); } }
|
3、给类生成.class文件,如下所示
执行完后可在build/intermediates/classes/debug/co/lujun/jnitest(这里是当前我的路径)路径下查看。
4、生成头文件
在Android Studio终端下,进入到当前module的src/main目录下(为什么是该目录下执行命令,因为我们需要的.h头文件也需要在该目录下的jni文件夹中);
执行命令:javah -d jni -classpath <SDK_android.jar>;<生成的.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
| 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
| #include jni.h; #include android/log.h; #ifndef LOG_TAG #define LOG_TAG "JNI_TEST" #define LOGE(...) __android_log_print (ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) #endif #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"); (*env)->CallVoidMethod(env, jObj, method_id); 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