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 #!/bin/bat set ANDROID_JAR_PATH=F:\code\android\adt-bundle-windows-x86_64-20140702\sdk\platforms\android-23\android.jarset DEBUG_CLASS_PATH=..\..\build\intermediates\classes\debugset TargetClassName=co.lujun.jnitest.MainActivityjavah -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 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!" ); } 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