NDK开发——Android Studio实现JNI

时间:2021-12-08 08:58:00

曾经,作为一名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 {

public native String hello();
}
接着在MainActivity里调用该本地方法:

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();
}
}


编译,生成编译文件,并对其生成.h格式头文件

在菜单选择 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
然后生成了一个头文件
NDK开发——Android Studio实现JNI

最后,在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点,容易出错:

NDK开发——Android Studio实现JNI
1. jni为文件夹名;

2. 需提供    SDK路径+android.jar   ,并用双引号括起来;(网上有方法貌似没有双引号也可以,但window上实测不可以,这里折腾了很久)

3. 中间用 英文分号 ";" 隔开;

4.  classes文件的路径;

5. 包名+类名,用点隔开,不是用斜杠;


以上两种方法都是差不多的,都是为了利用classes文件生成头文件,并放到jni文件夹下:

NDK开发——Android Studio实现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开发——Android Studio实现JNI

这是NDK的一个BUG,如果不创建这个空c文件,在编译时会抛异常,返回什么鬼 非零出口值 non-zero exit value 2

NDK开发——Android Studio实现JNI

在gradle.properties文件配置android.useDeprecatedNdk=true(重点,NDK第二个BUG)

如果不配置该处,也会抛异常

NDK开发——Android Studio实现JNI


在local.properties文件配置ndk路径,和sdk一样

NDK开发——Android Studio实现JNI


在app的build.gradle文件配置so库生成参数

在android的defaultConfig下添加ndk的参数设置

其中moduleName就是so库的名字,abiFilters就是架构文件夹名称

NDK开发——Android Studio实现JNI

若之前没配置好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目录下

NDK开发——Android Studio实现JNI


导出APK,安装到手机上,运行

Toast弹出hello world,调用了native方法hello( ), bingo~~~~

NDK开发——Android Studio实现JNI


-----------------------结束分割线----------------------


上面的方法是我根据网上的方法做的,只是在中间强调了两个折磨了我很久的异常解决方法而已。

由于不同的开发环境和不同的工具版本,NDK版本,可能导致步骤不一定奏效,实际还是需要亲自去弄过才知道。

还是那句话,纸上得来终觉浅,绝知此事要躬行~~~~