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被坑事件的坑算是完美填上。

目录
相关文章
|
24天前
|
Java Android开发
Android 开发获取通知栏权限时会出现两个应用图标
Android 开发获取通知栏权限时会出现两个应用图标
12 0
|
1月前
|
XML 缓存 Android开发
Android开发,使用kotlin学习多媒体功能(详细)
Android开发,使用kotlin学习多媒体功能(详细)
101 0
|
1月前
|
设计模式 人工智能 开发工具
安卓应用开发:构建未来移动体验
【2月更文挑战第17天】 随着智能手机的普及和移动互联网技术的不断进步,安卓应用开发已成为一个热门领域。本文将深入探讨安卓平台的应用开发流程、关键技术以及未来发展趋势。通过分析安卓系统的架构、开发工具和框架,本文旨在为开发者提供全面的技术指导,帮助他们构建高效、创新的移动应用,以满足不断变化的市场需求。
18 1
|
1月前
|
机器学习/深度学习 调度 Android开发
安卓应用开发:打造高效通知管理系统
【2月更文挑战第14天】 在移动操作系统中,通知管理是影响用户体验的关键因素之一。本文将探讨如何在安卓平台上构建一个高效的通知管理系统,包括服务、频道和通知的优化策略。我们将讨论最新的安卓开发工具和技术,以及如何通过这些工具提高通知的可见性和用户互动性,同时确保不会对用户造成干扰。
33 1
|
1天前
|
Linux 编译器 Android开发
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
在Linux环境下,本文指导如何交叉编译x265的so库以适应Android。首先,需安装cmake和下载android-ndk-r21e。接着,下载x265源码,修改crosscompile.cmake的编译器设置。配置x265源码,使用指定的NDK路径,并在配置界面修改相关选项。随后,修改编译规则,编译并安装x265,调整pc描述文件并更新PKG_CONFIG_PATH。最后,修改FFmpeg配置脚本启用x265支持,编译安装FFmpeg,将生成的so文件导入Android工程,调整gradle配置以确保顺利运行。
18 1
FFmpeg开发笔记(九)Linux交叉编译Android的x265库
|
15天前
|
XML 开发工具 Android开发
构建高效的安卓应用:使用Jetpack Compose优化UI开发
【4月更文挑战第7天】 随着Android开发不断进化,开发者面临着提高应用性能与简化UI构建流程的双重挑战。本文将探讨如何使用Jetpack Compose这一现代UI工具包来优化安卓应用的开发流程,并提升用户界面的流畅性与一致性。通过介绍Jetpack Compose的核心概念、与传统方法的区别以及实际集成步骤,我们旨在提供一种高效且可靠的解决方案,以帮助开发者构建响应迅速且用户体验优良的安卓应用。
|
18天前
|
监控 算法 Android开发
安卓应用开发:打造高效启动流程
【4月更文挑战第5天】 在移动应用的世界中,用户的第一印象至关重要。特别是对于安卓应用而言,启动时间是用户体验的关键指标之一。本文将深入探讨如何优化安卓应用的启动流程,从而减少启动时间,提升用户满意度。我们将从分析应用启动流程的各个阶段入手,提出一系列实用的技术策略,包括代码层面的优化、资源加载的管理以及异步初始化等,帮助开发者构建快速响应的安卓应用。
|
18天前
|
Java Android开发
Android开发之使用OpenGL实现翻书动画
本文讲述了如何使用OpenGL实现更平滑、逼真的电子书翻页动画,以解决传统贝塞尔曲线方法存在的卡顿和阴影问题。作者分享了一个改造后的外国代码示例,提供了从前往后和从后往前的翻页效果动图。文章附带了`GlTurnActivity`的Java代码片段,展示如何加载和显示书籍图片。完整工程代码可在作者的GitHub找到:https://github.com/aqi00/note/tree/master/ExmOpenGL。
19 1
Android开发之使用OpenGL实现翻书动画
|
18天前
|
Android开发 开发者
Android开发之OpenGL的画笔工具GL10
这篇文章简述了OpenGL通过GL10进行三维图形绘制,强调颜色取值范围为0.0到1.0,背景和画笔颜色设置方法;介绍了三维坐标系及与之相关的旋转、平移和缩放操作;最后探讨了坐标矩阵变换,包括设置绘图区域、调整镜头参数和改变观测方位。示例代码展示了如何使用这些方法创建简单的三维立方体。
15 1
Android开发之OpenGL的画笔工具GL10
|
24天前
|
Android开发
Android开发小技巧:怎样在 textview 前面加上一个小图标。
Android开发小技巧:怎样在 textview 前面加上一个小图标。
12 0