Android Studio 2.3.3 图解配置NDK开发环境以及Hello Word To jni~

LZ-Says:半夜睡觉滚了地上了,无奈之下醒来了,想想最近几天因为一个括号导致JNI迟迟不能开展,心里面无奈又崩溃,索性直接起来整完得了~

前言

当前毕业的时候,感觉自己掌握了全世界,随着参加工作的时间一天天的增长,突然觉得丫的,啥都不会啊,要学的东西还是太多太多了。不过近来被飞大姐洗脑成功,<font color=#FF0000>万事不过几行代码而已,干它~

So 今天为大家带来简单的jni配置,使用,以及运行我们的第一个简单小demo`

Hi Jni

总是再说jni,jni,那么jni到底是什么东西,我们一起来看看:

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。

JNI标准至少要保证本地代码能工作在任何Java 虚拟机环境。

下面为大家附上官方Android平台架构图:

<center>
这里写图片描述
这里写图片描述

可以看到Android上层的Application和ApplicationFramework都是使用Java编写,底层包括系统和使用众多的Libraries都是C/C++编写的,所以上层Java要调用底层的C/C++函数库必须通过Java的JNI来实现。

Jni使用场景

当你开始着手准备一个使用JNI的项目时,请确认是否还有替代方案。应用程序使用JNI会带来一些副作用。下面给出几个方案,可以避免使用JNI的时候,达到与本地代码进行交互的效果:

1、JAVA程序和本地程序使用TCP/IP或者IPC进行交互。
2、当用JAVA程序连接本地数据库时,使用JDBC提供的API。
3、JAVA程序可以使用分布式对象技术,如JAVA IDL API。

这些方案的共同点是,JAVA和C处于不同的线程,或者不同的机器上。这样,当本地程序崩溃时,不会影响到JAVA程序。

下面这些场合中,同一进程内JNI的使用无法避免:

1、程序当中用到了JAVA API不提供的特殊系统环境才会有的特征。而跨进程操作又不现实。
2、你可能想访问一些己有的本地库,但又不想付出跨进程调用时的代价,如效率,内存,数据传递方面。
3、JAVA程序当中的一部分代码对效率要求非常高,如算法计算,图形渲染等。

<font color=#FF0000>总之,只有当你必须在同一进程中调用本地代码时,再使用JNI。

Jni缺陷

一旦使用JNI,JAVA程序就丧失了JAVA平台的两个优点:

1、程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。
2、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个类当中。这样就降低了JAVA和C之间的耦合性

Jni作用

JNI可以这样与本地程序进行交互:

1、你可以使用JNI来实现“本地方法”(native methods),并在JAVA程序中调用它们。

2、JNI支持一个“调用接口”(invocation interface),它允许你把一个JVM嵌入到本地程序中。本地程序可以链接一个实现了JVM的本地库,然后使用“调用接口”执行JAVA语言编写的软件模块。

简单了解以上内容后,我们开启正题,在Android开发中,我们该怎么使用jni,或者说是在Android Studio中,我们该怎么使用jni呢?表急,往下瞅瞅~

话说,我们开发android应用程序基础不就是下载官方相关的SDK,ADT啥啥啥的,同理jni也一样。

现在为大家简单介绍NDK~如下。

Hi NDK

Android NDK 是一套允许使用原生代码语言(例如 C 和 C++)实现部分应用的工具集。在开发某些类型应用时,这有助于重复使用以这些语言编写的代码库。

同理,官方也为我们提供了一个小例子,简单走马观花看一下:

public class MyActivity extends Activity {
  /**
  * 使用 C/C++ 语言实现的原生方法
  */
  public native void computeFoo();
}

native方法,当年看到这个东西感觉好高大上,没想到而今我也能玩玩,哈哈,GGG~

官方对于NDK是这样说的:

NDK 不适用于大多数初学的 Android 编程者,对许多类型的 Android 应用没什么价值。 因为它不可避免地会增加开发过程的复杂性,所以通常不值得使用。 但如果您需要执行以下操作,它可能很有用:

  • 从设备获取卓越性能以用于计算密集型应用,例如游戏或物理模拟;

  • 重复使用您自己或其他开发者的 C 或 C++ 库。

丫的,爷儿们好奇瞅瞅不行啊?

行~!

哈哈~

简单有个印象后,我们开始配置相关内容,为什么要说这个配置呢,主要有以下几个原因:

  • 虽说配置是傻瓜式无脑操作,但是对于LZ小白这样的人来说,依然觉得是一件很高大上的事儿,何况,丫的,连配置都不会,还开发个卵子?

  • 凡事儿亲历亲为,不经历,怎能有成长?

配置之前,我们还需要了解我们需要配置or下载哪儿些工具,以及这些东西都是干嘛的,不然稀里糊涂的,糟心。

同理,我们也需要去简单了解下使用NDK好处:

1、代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大;
2、可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的;
3、提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率;
4、便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。

NDK以及所需构建工具简介

  • NDK:这个还需要再说嘛?

  • CMake:外部构建工具;

CMake是一个开源的跨平台系列工具,旨在构建,测试和打包软件。

CMake用于使用简单的平台和编译器独立的配置文件来控制软件编译过程,并生成可以在选择的编译环境中使用的本地makefile和工作空间。

CMack工具套件由Kitware创建,以响应对开源项目(如ITK和VTK)的强大的跨平台构建环境的需求。

官方地址:https://cmake.org/ 有兴趣可以简单了解下~

  • LLDB:Android Studio 上面调试本地代码的工具

LLDB是下一代高性能调试器。它被构建为一组可重用的组件,可以高度利用较大的LLVM项目中的现有库,例如Clang表达式解析器和LLVM反汇编程序。

在Mac OS X中,LLDB是Xcode中的默认调试器,支持在桌面和iOS设备和模拟器上调试C,Objective-C和C ++。

LLDB项目中的所有代码都可以使用标准的 LLVM许可证(一种开放源代码“BSD风格”)许可证。

LLDB目前将调试信息转换成clang类型,以便它可以利用clang编译器基础架构。这允许LLDB在表达式中支持最新的C,C++,Objective C和Objective C ++语言特性和运行时间,而无需重新实现任何此功能。在编写表达式的函数,拆卸指令和提取指令详细信息等时,还可以利用编译器来处理所有ABI细节。

主要优点包括:

  1. C,C ++,Objective C的最新语言支持 ;
  2. 可以声明局部变量和类型的多行表达式;
  3. 支持时使用JIT表达式;
  4. 当JIT不能使用时,评估表达式中间表示(IR)

官方地址:http://lldb.llvm.org/

以上简单了解下就好了,至于为啥要这么搞,就是为了方便以后有需要直接翻出来看看~

NDK配置(包含构建工具,调试工具)

<font color=#FF0000>下载安装NDK,有俩种方式,其实都一样,只是一个需要手动下载,解压,配置目录,一个Android Studio自动完成以上操作。

配置NDK俩种方式

手动下载NDK

NDK下载地址如下:

https://developer.android.google.cn/ndk/downloads/index.html

大家可自行选择相应版本进行下载。

下载完成后,解压本地目录,在Android Studio中配置路径即可,如下图所示:

<center>
这里写图片描述
这里写图片描述

强大的Android Studio走起~

1.点击Project Structure,选择 Download Android NDK;

<center>
这里写图片描述
这里写图片描述

2.耐心等待吧,LZ下载比较快,解压比较慢~

<center>
这里写图片描述
这里写图片描述

3.下载解压完成,自动录入地址,省事儿哈~

<center>
这里写图片描述
这里写图片描述

到此,关于NDK下载安装俩种方式图解完毕~

我们看一下ndk目录各个作用,简单了解下。

  • docs: 帮助文档
  •       build/tools:linux的批处理文件
    
  •       platforms:编译c代码需要使用的头文件和类库
    
  •       prebuilt:预编译使用的二进制可执行文件
    
  •      sample:jni的使用例子
    
  •      source:ndk的源码
    
  •      toolchains:工具链
    
  •     ndk-build.cmd:编译打包c代码的一个指令,需要配置系统环境变量
    

配置构建工具以及调试工具

1.如下图所示,点击SDK Manager,选择下载安装CMake以及LLDB;

<center>
这里写图片描述
这里写图片描述

简单看一下版本信息,果断OK~

<center>
这里写图片描述
这里写图片描述

没啥可说的,等待,不过分分钟搞定~

<center>
这里写图片描述
这里写图片描述

到此,基本配置已完成,但是如何查验NDK是否成功安装了呢?

配置下环境变量瞅瞅呗~

配置环境变量

复制NDK地址,按如下图示进行操作即可。

<center>
这里写图片描述
这里写图片描述

<center>
这里写图片描述
这里写图片描述

cmd直接输入ndk-build回车

<center>
这里写图片描述
这里写图片描述

在这里吐槽下LZ之前遇到的坑。

LZ目录习惯命名为HLQWorkSofe(Android) or HLQWorkSofe(Java),在之前从来没有出现过问题,但是在NDK时候,却怎么也不行,最后无奈下只能把()去掉。

问题截图如下:

<center>
这里写图片描述
这里写图片描述

<center>
这里写图片描述
这里写图片描述

<font color=#FF0000>搞得LZ都快放弃了,最后一试,好了,给我郁闷的,大家注意啊~

创建个项目玩一玩

创建项目时,记得勾选下面的Include C++ support哦~

导入C++支持库,不然没法玩哈~
<center>

这里写图片描述
这里写图片描述

一路next下之后,关于最后一步选择时,在此作特殊说明下。

<center>
这里写图片描述
这里写图片描述
  • C++ Standard:使用下拉列表选择您希望使用哪种 C++ 标准。选择 Toolchain Default 会使用默认的 CMake 设置;

  • Exceptions Support:如果您希望启用对 C++ 异常处理的支持,请选中此复选框。如果启用此复选框,Android Studio 会将 -fexceptions 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake;

  • Runtime Type Information Support:如果您希望支持 RTTI,请选中此复选框。如果启用此复选框,Android Studio 会将 -frtti 标志添加到模块级 build.gradle 文件的 cppFlags 中,Gradle 会将其传递到 CMake。

点击Finish之后,我们稍等片刻~

查看默认生成例子

<center>
这里写图片描述
这里写图片描述

下面我们一起来探究下官方提供的例子,看看从例子中我们能得知什么对我们有用的信息。

从上图可看到,和以前唯一不同的就是多出了cpp目录以及External Build Files俩个目录,那么这俩个都有什么用呢?一起来看看。

  • cpp 目录存放所有 native code 的地方,包括源码,头文件,预编译项目等。对于新项目,Android Studio 创建了一个 C++ 模板文件:native-lib.cpp,并且将该文件放到了你的 app 模块的 src/main/cpp/ 目录下。这份模板代码提供了一个简答的 C++ 函数:stringFromJNI(),该函数返回一个字符串:”Hello from C++”

  • External Build Files 目录是存放 CMake 或 ndk-build 构建脚本的地方。有点类似于 build.gradle 文件告诉 Gradle 如何编译你的 APP 一样,CMake 和 ndk-build 也需要一个脚本来告知如何编译你的 native library。对于一个新的项目,Android Studio 创建了一个 CMake 脚本:CMakeLists.txt,并且将其放到了你的 module 的根目录下

首先还是来看看代码吧,比较直观也比较好理解。

1.查看activity代码

package cn.hlq.hlqjnipro;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

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

首先static静态块去加载so库,本地编写native方法,调用方法输出内容。

2.查看native-lib.cpp代码

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

extern "C"
JNIEXPORT jstring JNICALL
Java_cn_hlq_hlqjnipro_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

简单可以看到,首先定义hello变量,之后return。

3.查看CMakeLists.txt

简单的翻译了下,如有不正之处欢迎指出`

# 使用Android Studio使用CMake的更多信息,阅读文档:https://d.android.com/studio/projects/add-native-code.html

# 设置CMake的最低版本构建本机所需库
cmake_minimum_required(VERSION 3.4.1)

# 创建和名称库,使它是静态的或共享,并提供了相对路径的源代码
# 可以定义多个图书馆,CMake将为你构建这些内容
# Gradle自动与你的APK包共享库

add_library( # 设置库名称
             native-lib

             # 集库作为一个共享库
             SHARED

             # 提供了一个相对路径你的源文件
             src/main/cpp/native-lib.cpp )

# 搜索指定预先构建的库和存储路径变量。因为CMake包括系统库搜索路径中默认情况下,只需要指定想添加公共NDK库的名称,在CMake验证库之前存在完成构建

find_library( # 设置path变量的名称
              log-lib

              # 在CMake定位前指定的NDK库名称
              log )

# 指定库CMake应该链接到目标库中,可以链接多个库,比如定义库,构建脚本,预先构建的第三方库或者系统库

target_link_libraries( # 指定目标库
                       native-lib

                       # 目标库到日志库的链接 包含在NDK
                       ${log-lib} )

一开始还觉的差不多点呢,结果看到这里越来越蒙圈,还是先运行一下,看看结果吧。

按照刚才的简单分析,这个demo输出的结果应该为:Hello from C++ 。一起来验证一下呗~

<center>
这里写图片描述
这里写图片描述

嗯,确实,输出了,有些似懂非懂的,继续研究~

经过查询,发现一个之前从未关注的内容,那就是从编译到运行示例 APP 的流程到底是怎样呢,如下:

  1. Gradle 调用外部构建脚本,也就是 CMakeLists.txt;
  2. CMake 会根据构建脚本的指令去编译一个 C++ 源文件,也就是 native-lib.cpp,并将编译后的产物扔进共享对象库中,并将其命名为 libnative-lib.so,然后 Gradle 将其打包到 APK 中;
  3. 在运行期间,APP 的 MainActivity 会调用 System.loadLibrary() 方法,加载 native library。而这个库的原生函数,stringFromJNI(),就可以为 APP 所用了;
  4. MainActivity.onCreate() 方法会调用 stringFromJNI(),然后返回 “Hello from C++”,并更新 TextView 的显示;

注意:Instant Run 并不兼容使用了 native code 的项目。Android Studio 会自动禁止 Instant Run 功能。

如果你想验证一下 Gradle 是否将 native library 打包进了 APK,你可以使用 APK Analyzer:

  • 选择 Build > Analyze APK。

  • 从 app/build/outputs/apk/ 路径中选择 APK,并点击 OK。

  • 如下图,在 APK Analyzer 窗口中,选择 lib/<ABI>/,你就可以看见 libnative-lib.so

<center>
这里写图片描述
这里写图片描述

转自:http://blog.csdn.net/wl9739/article/details/52607010

仿造demo来一下,首先布局中新增一个TextView,activity中编写一个native方法,调用native方法并输出结果,如下:

        TextView tv1 = (TextView) findViewById(R.id.sample_text_1);
        tv1.setText(hlqFromJNI());
    public native String hlqFromJNI();

在cpp中这么玩下:

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

extern "C" {
JNIEXPORT jstring JNICALL
Java_cn_hlq_hlqjnipro_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

JNIEXPORT jstring JNICALL
Java_cn_hlq_hlqjnipro_MainActivity_hlqFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "真恶心啊 Fuck";
    return env->NewStringUTF(hello.c_str());
}

}

大家有没有注意到Java_cn_hlq_hlqjnipro_MainActivity_hlqFromJNI这个名称,方法名必须为Java_包名全路径_方法名

运行结果如下:

<center>
这里写图片描述
这里写图片描述

一般情况下,Gradle 会将你的本地库构建成 .so 文件,然后将其打包到你的 APK 中。如果你想 Gradle 构建并打包某个特定的 ABI 。你可以在你的 module 层级的 build.gradle 文件中使用 ndk.abiFilters 标签来指定他们:

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      cmake {...}
      // or ndkBuild {...}
    }

    ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your APK.
      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                   'arm64-v8a'
    }
  }
  buildTypes {...}
  externalNativeBuild {...}
}

提供给别人使用

ReBuild Project之后,将生成好的so库提供有需要的人即可,不过要记得调用的时候包名要一致,不然会报一个找不到so的问题。

LZ简单写了一个测试demo,如下图:

<center>
这里写图片描述
这里写图片描述

运行结果如下:

<center>
这里写图片描述
这里写图片描述

比较恶心的就是包名这块,等以后再看看有没有好的解决办法吧。

拓展APP构建流程

查看官方文档中,突然发现有关构建流程内容,一起了解下,如下:

构建流程涉及许多将您的项目转换成 Android 应用软件包 (APK)的工具和流程。构建流程非常灵活,因此了解它的一些底层工作原理会很有帮助。

典型 Android 应用模块的构建流程图如下:

<center>
这里写图片描述
这里写图片描述

典型 Android 应用模块的构建流程通常依循下列步骤:

1.编译器将您的源代码转换成 DEX(Dalvik Executable) 文件(其中包括运行在 Android 设备上的字节码),将所有其他内容转换成已编译资源;

2.APK 打包器将 DEX 文件和已编译资源合并成单个 APK。不过,必须先签署 APK,才能将应用安装并部署到 Android 设备上;

3.APK 打包器使用调试或发布密钥库签署您的 APK:

a. 如果您构建的是调试版本的应用(即专用于测试和分析的应用),打包器会使用调试密钥库签署您的应用。Android Studio 自动使用调试密钥库配置新项目;

b. 如果您构建的是打算向外发布的发布版本应用,打包器会使用发布密钥库签署您的应用

4.在生成最终 APK 之前,打包器会使用 zipalign 工具对应用进行优化,减少其在设备上运行时的内存占用

构建流程结束时,您将获得可用来进行部署、测试的调试 APK,或者可用来发布给外部用户的发布 APK。

参考文献

  1. http://baike.baidu.com/link?url=QrA-HKewzfPKP5UejCXH8JhE4yQs5MhwG6EBYI8imO9k8zYIlM0h2DYffNSqdK8dG6LZLfT0dK5ocK2NEsOUJq
  2. http://blog.csdn.net/tongseng/article/details/53005123
  3. http://www.cnblogs.com/bastard/archive/2012/05/19/2508913.html
  4. http://blog.csdn.net/baidu_26352053/article/details/53931045
  5. https://developer.android.google.cn/ndk/guides/concepts.html
  6. https://developer.android.google.cn/studio/build/index.html;
  7. http://blog.csdn.net/wl9739/article/details/52607010

结束语

这篇文章写的够揪心,个人感觉乱糟糟的,实际学习的时候也是乱糟糟的,几乎都是靠着各种搜索,况且还有一些不靠谱的博文误导或者说是版本有些老。

望观看者海涵`有问题及时沟通。

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

推荐阅读更多精彩内容