android中ndk的开发

简介: 前言(其实是吐槽)这是我看(android应用安全防护和逆向分析)遇到的第一个坑了,在章节2.1和2.2里,虽然作者很贴心的给了步骤教你如何搭建ndk的开发环境,但是,我要说的是,如果按照作者在2.1.2的五个步骤按部就班的来,你绝对!不可能!完成!主要的原因我就不再分析了,大约就是少了一堆乱七八糟的说明和步骤,这里我重新写一遍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到结束;
    img_2b71c51a8d986f8344d02c2f0c9fc192.png
    新建一个支持c++的项目.png
  1. 此时一个基本工程环境已经新建好了,不需要任何改动,我们直接可以点击build生成一个系统自带的示例so库也就是cpp目录里的native-lib,但在此之前,我们先看一下工程目录:
    img_1221d7682c5f29a99521f0dc2411a321.png
    工程目录.png

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

  1. 因为编译出so库,主要看的是CMakelists文件里的配置,下面我们重点关注一下CMakelists文件的内容,如图:
    img_e4b17953581764d2a4fdd14bffca9288.png
    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被坑事件的坑算是完美填上。

目录
相关文章
|
6天前
|
Android开发 开发者 Kotlin
探索安卓开发中的新特性
【9月更文挑战第14天】本文将引导你深入理解安卓开发领域的一些最新特性,并为你提供实用的代码示例。无论你是初学者还是经验丰富的开发者,这篇文章都会给你带来新的启示和灵感。让我们一起探索吧!
|
1天前
|
Android开发 开发者
安卓开发中的自定义视图:从入门到精通
【9月更文挑战第19天】在安卓开发的广阔天地中,自定义视图是一块充满魔力的土地。它不仅仅是代码的堆砌,更是艺术与科技的完美结合。通过掌握自定义视图,开发者能够打破常规,创造出独一无二的用户界面。本文将带你走进自定义视图的世界,从基础概念到实战应用,一步步展示如何用代码绘出心中的蓝图。无论你是初学者还是有经验的开发者,这篇文章都将为你打开一扇通往创意和效率的大门。让我们一起探索自定义视图的秘密,将你的应用打造成一件艺术品吧!
18 10
|
3天前
|
Java Linux Android开发
深入理解Android开发:从基础到高级
【9月更文挑战第17天】本文将深入探讨Android开发的各个方面,包括应用开发、操作系统等。我们将通过代码示例来展示如何创建一个简单的Android应用,并解释其背后的原理。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的信息和启示。
|
1天前
|
Java Android开发 C++
🚀Android NDK开发实战!Java与C++混合编程,打造极致性能体验!📊
在Android应用开发中,追求卓越性能是不变的主题。本文介绍如何利用Android NDK(Native Development Kit)结合Java与C++进行混合编程,提升应用性能。从环境搭建到JNI接口设计,再到实战示例,全面展示NDK的优势与应用技巧,助你打造高性能应用。通过具体案例,如计算斐波那契数列,详细讲解Java与C++的协作流程,帮助开发者掌握NDK开发精髓,实现高效计算与硬件交互。
7 1
|
10天前
|
IDE 开发工具 Android开发
安卓与iOS开发对比:平台选择对项目成功的影响
【9月更文挑战第10天】在移动应用开发的世界中,选择正确的平台是至关重要的。本文将深入探讨安卓和iOS这两大主要移动操作系统的开发环境,通过比较它们的市场份额、开发工具、编程语言和用户群体等方面,为开发者提供一个清晰的指南。我们将分析这两个平台的优势和劣势,并讨论如何根据项目需求和目标受众来做出最佳选择。无论你是初学者还是有经验的开发者,这篇文章都将帮助你更好地理解每个平台的特性,并指导你做出明智的决策。
|
6天前
|
XML 编解码 Android开发
安卓开发中的自定义视图控件
【9月更文挑战第14天】在安卓开发中,自定义视图控件是一种高级技巧,它可以让开发者根据项目需求创建出独特的用户界面元素。本文将通过一个简单示例,引导你了解如何在安卓项目中实现自定义视图控件,包括创建自定义控件类、处理绘制逻辑以及响应用户交互。无论你是初学者还是有经验的开发者,这篇文章都会为你提供有价值的见解和技巧。
14 3
|
8天前
|
API Android开发 iOS开发
安卓与iOS开发中的线程管理对比
【9月更文挑战第12天】在移动应用的世界中,安卓和iOS平台各自拥有庞大的用户群体。开发者们在这两个平台上构建应用时,线程管理是他们必须面对的关键挑战之一。本文将深入探讨两大平台在线程管理方面的异同,通过直观的代码示例,揭示它们各自的设计理念和实现方式,帮助读者更好地理解如何在安卓与iOS开发中高效地处理多线程任务。
|
10天前
|
开发框架 Android开发 iOS开发
探索安卓与iOS开发的差异:构建未来应用的指南
在移动应用开发的广阔天地中,安卓与iOS两大平台各占半壁江山。本文将深入浅出地对比这两大操作系统的开发环境、工具和用户体验设计,揭示它们在编程语言、开发工具以及市场定位上的根本差异。我们将从开发者的视角出发,逐步剖析如何根据项目需求和目标受众选择适合的平台,同时探讨跨平台开发框架的利与弊,为那些立志于打造下一个热门应用的开发者提供一份实用的指南。
27 5
|
10天前
|
开发工具 Android开发 iOS开发
安卓与iOS开发:平台选择的艺术与科学
在移动应用开发的广阔天地中,安卓与iOS两大平台如同东西方哲学的碰撞,既有共通之处又各具特色。本文将深入探讨这两个平台的设计理念、开发工具和市场定位,旨在为开发者提供一份简明扼要的指南,帮助他们在这场技术与商业的博弈中找到自己的道路。通过比较分析,我们将揭示每个平台的优势与局限,以及它们如何影响应用的性能、用户体验和市场接受度。无论你是初涉江湖的新手,还是经验丰富的老手,这篇文章都将为你的选择提供新的视角和思考。
26 5
|
11天前
|
安全 Android开发 开发者
探索安卓开发的未来:Kotlin的崛起与Flutter的挑战
在移动开发的广阔天地中,安卓平台始终占据着举足轻重的地位。随着技术的不断进步和开发者需求的多样化,Kotlin和Flutter成为了改变游戏规则的新玩家。本文将深入探讨Kotlin如何以其现代化的特性赢得开发者的青睐,以及Flutter凭借跨平台的能力如何挑战传统的安卓开发模式。通过实际案例分析,我们将揭示这两种技术如何塑造未来的安卓应用开发。
33 6