android中ndk的开发

前言(其实是吐槽)

这是我看(android应用安全防护和逆向分析)遇到的第一个坑了,在章节2.1和2.2里,虽然作者很贴心的给了步骤教你如何搭建ndk的开发环境,但是,我要说的是,如果按照作者在2.1.2的五个步骤按部就班的来,你绝对!不可能!完成!
主要的原因我就不再分析了,大约就是少了一堆乱七八糟的说明和步骤,这里我重新写一遍ndk开发相关。(如果你不信,可以尝试只按照2.1.2章节的五步来尝试)

搭建NDK开发环境

NDK相关概念

首先,普及一下ndk的概念,何谓ndk开发呢?
简而言之,就是让安卓(java)可以调用前人用c语言完成的库,这么做的好处主要有两个,
第一,节约代码量,提高工作(运行)效率,可以用之前c写好的很多很棒的库
第二,防止应用被逆向,因为java层的代码很容易被反编译逆向突破。
再介绍几个ndk里相关概念名词,
.c,.cpp,这几个不用多说了吧,就是c或者c++等文件的后缀名;
.so,这个是编译c文件得到的库文件的后缀名,.so文件大概就相当于windows上的.dll文件,他可以方便让别人调用;
.h,也就是通过javah命令编译类(class)文件编译出来的头文件,这玩意开始在c里用作声明管理,现在在jni里已经意义不大了,你可以当做没啥用;
jni,全名 java native interface,由他提供java和c之间的通信;

一个NDK开发的流程

编写Java代码(.java) —————> 编译生成字节码文件(.class) —————> 产生C头文件(.h) —————> 编写jni实现代码(.c) —————> 编译成链接库(.so)

基于Android Studio3 & CMAKE的NDK环境搭建
  1. 打开你的as(Android Studio),新建一个支持c++的项目(如图),然后一路next到结束;
    新建一个支持c++的项目.png
  1. 此时一个基本工程环境已经新建好了,不需要任何改动,我们直接可以点击build生成一个系统自带的示例so库也就是cpp目录里的native-lib,但在此之前,我们先看一下工程目录:
    工程目录.png

可以看到,相对于普通项目,我们的ndk的项目其实就是多了cpp目录,多了CMakelists.txt文件,此外build.gradle里多了一些配置;

  1. 因为编译出so库,主要看的是CMakelists文件里的配置,下面我们重点关注一下CMakelists文件的内容,如图:
    CMakeLists.png

默认的CMakelists文件里,主要有三个配置需要关注,分别是
add_library:
主要用作给生成的so库做配置,在三个注释的下面,每一个都代表了一个配置项(其实注释的英文已经说明一切了),
Sets the name of the library下,填写你希望生成的so库的名称,这里注意的是,你填写的名称,最终会被自动加上lib的前缀和.so的后缀,因此你只需要基础的名称就够了,最终生成的so文件会在工程的app-build-intermediates-cmake-debug-obj目录下,每个abi环境都有个文件夹,里面放着对应的so库;
Sets the library as a shared library下,填写你要生成的so库的类型,这里的SHARED是指动态链接库类型;
Provides a relative path to your source file(s)下,填写你要编译成so文件的对应的c源文件的路径;
find_library:
主要是用作添加我们本地库的依赖库,
Sets the name of the path variable下,填写你引用的库的别名,便于引用;
you want CMake to locate下,填写你要引用的依赖库;
target_link_libraries:
是为了关联我们自己的库和一些第三方库或者系统库,
Specifies the target library下,填写本地库的名称;
included in the NDK下,填写你要关联的库的名称。

  1. ok,现在让我们点击一下工具栏的锤子图标,就可以生成so库了。
补充一些tips
关于环境

android ndk开发需要设置环境变量以及安装sdk,ndk,cmake,lldb,如果没有,可以在as的tools选项里,点击sdk manager,在sdk tools栏里,把这些都勾上,点击确定即可;

关于默认生成的例子

按照常规的ndk开发流程,应该是先写java类,这个类里可能会引用到native的方法也就是拿c实现的方法;然后javah命令编译这个java类,生成.h的头文件(这一步可以不用);再写一个.c或者.cpp实现在你java类里引用的这个native方法;最后编译你的c文件,生成最终的so库。
在系统默认的例子里,你点一下编译按钮就能得到so库,可能对这个流程不太明白,下面我们具体说一下:
先看一下MainActivity类的代码:


public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}

你可以当做这是系统给你先写好的一个引用了native方法的java类,也就是说这就是ndk第一步编写java类的产物;
在static里,System.loadLibrary("native-lib")这句会先于onCreate函数触发,这句是载入我们生成的动态链接库.so文件,这里只需要填写CMakeLists里面填写的名称即可;然后你会看到下面引用了这个库里的native方法,也就是这句public native String stringFromJNI();这就是系统帮你做好的第一步编写java类;
然后我们需要编写实现这个native方法的.c文件,这里系统也帮你写好了就是在cpp文件夹里的那个native-lib.cpp的文件,这个文件里实现了stringFromJNI()这个方法;
最后,我们点击编译,成功生成了so文件,这就是系统的例子里,ndk开发的完整流程。

Hello World 工程引发的被坑事件

前面是介绍了如何在as3的环境下进行ndk开发以及解析了系统自带的例子,接下来我们肯定要仿照系统的例子,自己走一遍ndk开发的流程,当然第一个工程肯定就是通用的HelloWorld了。

在小黄书(android应用安全防护和逆向分析)里,也有对helloworld工程的举例,流程是:新建一个java类,在java类里申明一个native方法,再在main方法里载入动态链接库,新建一个实例调用native方法;然后使用vc6.0编译器编写好native方法,生成.dll动态链接库文件;最后把该dll文件写入环境变量内,运行java类;具体代码和流程参见小黄书p14-p15页。

不过,其实不用如此麻烦,之所以用vc写c的内容,是因为当时as编译器对c的编写支持的还不够好,但as3以后,这块已经开始逐步完善了,因此,我们可以不用按照书里的流程,直接使用as3来写c的内容,不过,这里有一个坑,开始我是想仿造小黄书的示例代码,单独写一个具有main方法的java类来调用本地方法,大概是这样:

package com.example.test.testndk;

public class HelloWorld {

    public native String stringFromJNITest();
    static {
        
        System.loadLibrary("JNITest");
    }


    public static void main(String[] args){
        HelloWorld hello = new HelloWorld();
        String hi = hello.stringFromJNITest();
        System.out.println(hi);
    }
}

JNITest是我事先写好的cpp文件编译成的.so库,里面实现了stringFromJNITest()方法,.cpp内容是这样的:

#include <jni.h>
#include <string>

extern "C" JNIEXPORT jstring

JNICALL
Java_com_example_test_testndk_MainActivity_stringFromJNITest(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello world";
    return env->NewStringUTF(hello.c_str());
}

以上其实就是我们自己实现一个ndk开发的流程,即先写一个java类,在类中声明一个native方法,然后写.cpp实现这个native方法,编译cpp成so库,在java类里用loadlibrary加载这个so库。

本例中,这是一个简单的native方法就是返回了一串字符串“Hello World”而已,原本我以为到此结束,点击运行这个java类我们的Hello World之旅就结束了,但是没想到报了个错:java.lang.UnsatisfiedLinkError: no JNITest in java.library.path。

开始我怀疑是不是CMakelists.txt的配置问题,但是我把这个so库在android工程的mainactivity里引用,居然没有问题,也就是说,应该不是配置的路径问题。经过在群里讨论以及百度,又开始怀疑是不是java.library.path的问题,因为据了解,其实你用as3运行android工程的mainactivity,编译器是会自动替你生成so库用的(只要你CMakelists配置好了),所以不会有路径问题,但是当你新建一个java类,写上main方法,独立运行这个java类的时候,其实是按java工程的方式找你的动态链接库文件的,而这个方式,在win下,是在环境变量path里找;在linux下,是在系统变量LD_LIBRARY_PATH里找,为了证明这个结论,我在系统的环境变量path里,添加了so库所在的目录路径。

但是,依旧报错。。no JNITest in java.library.path;这时群友又提醒我,我是win的系统,而win的动态链接库的格式是.dll,因此光配置了路径,但因为我的格式是 .so,因此也找不到;现搭一个linux系统来做实验成本太高,我采用了一个取巧的方式,直接把生成的.so文件后缀改为了dll,因为如果是后缀的原因,那么会报错,但肯定不会再报找不到的错,算是侧面论证。

我把后缀改为dll之后,再次运行!奇迹发生了,果然,还是报on JNITest in java.library.path,为什么呢,我猜可能是win下和android下,寻找规则不同,在android工程里,只需要写库名,不需要写前缀lib和后缀 .so,在win下,则需要写上前缀lib;最后,我把 System.loadLibrary("JNITest");改为System.loadLibrary("libJNITest");再次点击运行!

这次报错不同了,不再是找不到,而是Can't load this .dll (machine code=0x34) on a AMD 64-bit platform;终于,本次Hello World被坑事件的坑算是完美填上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容