曾经,作为一名Android应用开发者,掌握NDK的开发是很有必要的,虽然之前我并没涉足(惭愧~),随着移动应用软件的核心业务,如加密处理,游戏开发移植等操作的需求增大,NDK的开发显得尤为重要。
现在,作为一命Android逆向分析工程师,分析NDK开发中涉及到的so库也是很有必要的,为了结合简单的NDK工程学习IDA PRO的逆向分析,懂得NDK的简单开发也是有一定的益处的。
于是,我找了很多网上很多的资源学习,但在实现第一个NDK程序中却遇到了不少的问题。
真的不得不说,纸上得来终觉浅,觉知此事要躬行~只有自己动手了,才会发现问题,才能解决问题,累积经验。
其实,NDK开发第一个程序是非常简单的,但很多技术博客的步骤却省略了一些Exception的处理,害得我折腾了好一阵子,毕竟开发的环境和工具有一定的差异,照搬别人的不一定会行得通,得自己折腾折腾才能清楚。
废话不多说,go go go!
--------------------开始分割线---------------
为了强调重点,一开始就说吧,这里的在Window上的NDK开发可能会遇到两个NDK的BUG,编译时会抛异常,下面会说到。
首先,下载NDK
官网:下载地址
选择合适的平台版本下载即可
新建工程
除了基本的MainActivity.class外,新建一个JniUtils.class文件,并在里面声明带有native关键字的本地方法:
JniUtils.class
public class JniUtils {接着在MainActivity里调用该本地方法:
public native String hello();
}
MainActivity.class
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
JniUtils utils=new JniUtils();
Toast.makeText(this, utils.hello(), Toast.LENGTH_SHORT).show();
}
}
在菜单选择 Build->Make Project,编译之后会在app/build/intermediates目录下生成classes文件夹,内有编译后的classes文件。
接着,Alt+F12打开AS的终端Terminal,此时的定位位置为工程的文件路径下。
我们接下来需要通过终端命令,利用javah这个命令,将JniUtils类生成头文件。
有两种方式,区别是命令的输入而已。
第一种方式:
利用cd命令,定位到build\intermediates\classes\debug下
D:\workspace\JniTest>cd app/build/intermediates/classes/debug接着,javah命令,生成头文件
D:\workspace\JniTest\app\build\intermediates\classes\debug>javah -jni com.samuelzhan.jnitest.JniUtils然后生成了一个头文件
最后,在app/src/main目录下创建一个jni目录,并将头文件剪切,放到jni文件夹下。
第二种方式:
cd命令到app/src/main目录下:
D:\workspace\JniTest>cd app/src/main接着,输入javah +参数 的命令
D:\workspace\JniTest\app\src\main>javah -d jni -classpath "D:\Android SDK\platforms\android-23\android.jar";D:\workspace\JniTest\app\build\intermediates\classes\debug com.samuelzhan.jnitest.JniUtils这里直接生成头文件直接放到app/src/main/jni目录下
但需要注意如下几点,尤其是2,3,4点,容易出错:
1. jni为文件夹名;
2. 需提供 SDK路径+android.jar ,并用双引号括起来;(网上有方法貌似没有双引号也可以,但window上实测不可以,这里折腾了很久)
3. 中间用 英文分号 ";" 隔开;
4. classes文件的路径;
5. 包名+类名,用点隔开,不是用斜杠;
以上两种方法都是差不多的,都是为了利用classes文件生成头文件,并放到jni文件夹下:
创建一个hello.c文件,实现头文件的方法
hello.c
#include "com_samuelzhan_jnitest_JniUtil.h"
JNIEXPORT jstring JNICALL Java_com_samuelzhan_jnitest_JniUtil_hello
(JNIEnv *env, jobject obj){
return (*env)->NewStringUTF(env,"hello world");
}
有点像java实现接口一样,头文件里面只有方法的声明而已,没有方法体,还需要去实现它,头文件就类似于接口类。
记得要添加#include 生成的头文件。
顺便一提,这里的写法,C和C++是有点小区别的,上面是C的写法,具体区别自行百度。
创建一个util.c文件,什么都不用写,留空,放到jni下即可(重点,NDK第一个BUG)
这是NDK的一个BUG,如果不创建这个空c文件,在编译时会抛异常,返回什么鬼 非零出口值 non-zero exit value 2
在gradle.properties文件配置android.useDeprecatedNdk=true(重点,NDK第二个BUG)
如果不配置该处,也会抛异常
在local.properties文件配置ndk路径,和sdk一样
在app的build.gradle文件配置so库生成参数
在android的defaultConfig下添加ndk的参数设置
其中moduleName就是so库的名字,abiFilters就是架构文件夹名称
若之前没配置好ndk路径,即上一步没做好,这步修改完build.gradle后会提示你Sync,Sync报错,提示你选择NDK路径
JniUtils.class内加载库
库的名字要和build.gradle一致
public class JniUtils {
static {
System.loadLibrary("jniTest");
}
public native String hello();
}
最后,一切OK后,菜单Build里选择Make Project 或者 Rebuild Projec,重新编译生成so库
so库生成在build/intermediates/ndk/debug/lib目录下
导出APK,安装到手机上,运行
Toast弹出hello world,调用了native方法hello( ), bingo~~~~
-----------------------结束分割线----------------------
上面的方法是我根据网上的方法做的,只是在中间强调了两个折磨了我很久的异常解决方法而已。
由于不同的开发环境和不同的工具版本,NDK版本,可能导致步骤不一定奏效,实际还是需要亲自去弄过才知道。
还是那句话,纸上得来终觉浅,绝知此事要躬行~~~~