手把手教你搭建 NDK 环境搭建

本文基于 Android Studio 3.4.2 、gradle:3.2.1

1、什么是 JNI、NDK?

JNI 是 Java Native Interface (Java 本地接口)的缩写,是 Java 与其他语言通信的桥梁。在 Android 中的应用主要为:音视频开发、热修复、插件化、逆向开发和系统源码调用,为了方便使用 JNI 技术,Android 提供了 NDK 工具集合,它和 JNI 开发本质上没有区别,NDK 是在 Android 中实现 JNI 的手段,

NDK 有两个主要作用:
  • 帮助开发者快速开发 C/C++ 的动态库

  • NDK 使用了交叉编译器,可以在一个平台上开发出另一个平台的二进制代码

2、Android 中 NDK 的使用

1)首先下载 NDK 的安装包

在 SDK Tools 里下载 NDK、LLDB、CMake

下载 NDK、LLDB、CMake
  • NDK : 即我们需要下载的工具,会生成到 SDK 根目录下的 ndk-bundle 目录下

  • CMake : 一个跨平台的编译构建工具,可以用简单的语句来描述所有平台的安装过程

  • LLDB : 一个高效的 C/C++ 的调试工具

2)编写界面

这里的界面很简单,一个 TextView 和一个 Button ,点击 Button 后调用 JNI 的方法修改 TextView 的值。

<?xml version="1.0" encoding="utf-8"?>

<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".JNIDemo.JNIActivity">

    <TextView

        android:id="@+id/jni_tv"

        android:layout_marginTop="20dp"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintEnd_toEndOf="parent"

        android:text="I'm a TextView"

        android:textAllCaps="false"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" />

    <Button

        android:id="@+id/jni_btn"

        app:layout_constraintTop_toBottomOf="@id/jni_tv"

        android:layout_marginTop="20dp"

        app:layout_constraintStart_toStartOf="parent"

        android:text="Call Method From JNI"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content" />

</android.support.constraint.ConstraintLayout>
3)编写 Activity 代码
/*

* Copyright (c) 2019\. Lorem ipsum dolor sit amet, consectetur adipiscing elit.

* Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.

* Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.

* Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.

*                                              Vestibulum commodo. Ut rhoncus gravida arcu.

*/

package com.learnandroid.learn_android.JNIDemo;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.TextView;

import android.widget.Toast;

import com.learnandroid.learn_android.R;

public class JNIActivity extends AppCompatActivity {

    private TextView jni_tv;

    private Button jni_btn;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_jni);

        jni_tv = findViewById(R.id.jni_tv);

        jni_btn = findViewById(R.id.jni_btn);

        jni_btn.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                jni_tv.setText(JNIUtils.sayHelloFromJNI());

            }

        });

    }

}
4)编写 JNIUtils
package com.learnandroid.learn_android.JNIDemo;

public class JNIUtils {

    static {

        System.loadLibrary("MyJNIHello");

    }

    public static native String sayHelloFromJNI();

}

这里的静态代码块中首先加载了需要使用的动态库,然后创建 native 方法。

5)生成头文件

在 Android Studio 的 Terminal 中,cd 到当前项目的根目录,然后执行 javah 命令

(注意将含有本地方法的类写完整的包名)

# binguner @ binguner in ~/AndroidStudioProjects/learn_android/app/src/main/java [10:12:17]

$ javah -d ../cpp com.learnandroid.learn_android.JNIDemo.JNIUtils

-d ../cpp 指定了头文件的生成位置:当前目录上一级下的 cpp 文件夹中。

然后打开 Project 目录下的 com_learnandroid_learn_android_JNIDemo_JNIUtils.h 可以看到它为我们生成的方法原型

/* DO NOT EDIT THIS FILE - it is machine generated */

#include <jni.h>

/* Header for class com_learnandroid_learn_android_JNIDemo_JNIUtils */

#ifndef _Included_com_learnandroid_learn_android_JNIDemo_JNIUtils

#define _Included_com_learnandroid_learn_android_JNIDemo_JNIUtils

#ifdef __cplusplus

extern "C" {

#endif

/*

* Class:     com_learnandroid_learn_android_JNIDemo_JNIUtils

* Method:    sayHelloFromJNI

* Signature: ()Ljava/lang/String;

*/

JNIEXPORT jstring JNICALL Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI

  (JNIEnv *, jclass);

#ifdef __cplusplus

}

#endif

#endif
6)实现 C++ 代码

在 cpp 目录下新建一个 C++ 文件,命名为 MyJNIDemo

新建 C++ 文件

这时候如果直接在 C++ 文件里编写代码,是没有代码提示的,这时候需要用 CMake 工具。

首先编辑 app 的 build.gradle 文件,添加如下内容

android -> defaultConfig 下添加

// 使用Cmake工具

externalNativeBuild {

    cmake {

        cppFlags ""

        //生成多个版本的so文件,指定需要编译的cpu架构

        abiFilters "armeabi-v7a"

    }

}

android -> 下添加

// 配置CMakeLists.txt路径

externalNativeBuild {

    cmake {

        //编译后so文件的名字

        **path "src/main/cpp/CMakeLists.txt"**

    }

}

再编辑 CMakeLists.txt 文件(cmake脚本配置文件,cmake会根据该脚本文件中的指令去编译相关的C/C++源文件,并将编译后产物生成共享库或静态块,然后Gradle将其打包到APK中)

# 设置 CMake 的最低版本

cmake_minimum_required(VERSION 3.4.1)

# 第一个参数:创建并命名一个 lib,会自动生成这个 lib 的 so 库,

# 第二个参数:将它设置为 STATIC (静态库,以 .a 结尾)或者 SHARED(动态库以 .so 结尾),

# 最后一个参:数提供一个相对的源码路径

# 可以用 add_library 设置多个 lib,CMake 会自动构建并把 lib 包打包到 apk 中。

add_library( # Sets the name of the library.

        MyJNIHello

        # Sets the library as a shared library.

        SHARED

        # Provides a relative path to your source file(s).

        MyJNIHello.cpp

        )

# 搜索一个预置的 lib,并把它的路径保存为一个变量

# CMake 默认在搜索路径中包含了系统的 lib,我们只需要给想要添加的 NDK lib 设置一个名称即可。

# CMake 会在完成构建之间检查它是否存在

find_library( # Sets the name of the path variable.

        log-lib

        # Specifies the name of the NDK library that

        # you want CMake to locate.

        log)

# 将指定的库关联起来

target_link_libraries( # Specifies the target library.

        MyJNIHello

        # Links the target library to the log library

        # included in the NDK.

        ${log-lib}

        )

这时候点击 Sync Now,编写 C++ 文件有代码提示了

C++ 的代码如下

//

// Created by Binguner on 2019-08-13.

//

#include <jni.h>

//#include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h"

extern "C"

JNIEXPORT jstring JNICALL

Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI

        (JNIEnv *env, jclass jclass1) {

    return env->NewStringUTF("JNIHELLO");

}

这里实现了 Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI 方法,并返回了一个 「JNIHELLO」的字符串,
我为什么注释掉 #include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h” 呢?

因为 JNI 生成的头文件目的是帮助我们得到 native 方法的原型,自己写原型容易的话命名可能出错,你也可以看到这个方法的名称非常复杂。但是如果我们能自己写对这个名字,不用 include 这个头文件也可以,CMake 会我们自动生成这个方法。

比如我又在 JNIUtils 中添加了一个 test1() 的方法

新建 native 方法

点击代码提示之后,它会自动在 C++ 代码中生成相应的方法,我们只要实现其中的方法即可

自动创建方法

7)运行 App

未点击按钮

点击按钮后
点击按钮

8)JNI 中打印日志

在 C++ 文件中添加如下内容:

//

// Created by Binguner on 2019-08-13.

//

#include <android/log.h>

#include <jni.h>

//#include "com_learnandroid_learn_android_JNIDemo_JNIUtils.h"

#define LOG_TAG "System.out.c"

#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)

#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)

#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__)

#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)

extern "C"

JNIEXPORT jstring JNICALL

Java_com_learnandroid_learn_1android_JNIDemo_JNIUtils_sayHelloFromJNI

        (JNIEnv *env, jclass jclass1) {

    LOGD("TAGD,a=%d,b=%d",1,2);

    return env->NewStringUTF("JNIHELLO");

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

推荐阅读更多精彩内容