【北京迅为】iTOP-4412全能版使用手册-第八十八章 安卓JNI开发指南

iTOP-4412全能版采用四核Cortex-A9,主频为1.4GHz-1.6GHz,配备S5M8767 电源管理,集成USB HUB,选用高品质板对板连接器稳定可靠,大厂生产,做工精良。接口一应俱全,开发更简单,搭载全网通4G、支持WIFI、蓝牙、陀螺仪、CAN总线、RS485总线、500万摄像头等模块,稳定运行Android 4.0.3/Android 4.4操作,系统通用Linux-3.0.15+Qt操作系统(QT支持5.7版本),Ubuntu版本:12.04,接口智能分配 方便好用。

【交流群2】258811263(获取开源资料)

【迅为B站】北京迅为电子的个人空间-北京迅为电子个人主页-哔哩哔哩视频


第八十八章 安卓JNI开发指南

88.1 JNI介绍

什么是JNI?

JNI 的全称是 Java Native Interface,即 Java 本地接口,用一句话来说就是 JNI 是属于 Java 的,尽管我们是在安卓上使用,但是和安卓没有直接关系。我们只是通过 JNI 来在 Java 代码里面调用 C 或者 C++ 的代码。如下图所示:

通过上面的图我们可以发现 java 不仅可以通过 jni 来调用 c/c++,反过来 c/c++也可以来调用 java 的一些程序,所以,JNI 让 java 和 c/c++有了交互的功能。

如果大家看过我们的 QT 视频,那么这个 JNI 就有点类似于 QT 里面的 C 和 C++混合编程。既然我们可以通过 JNI 在 Java 代码里面调用 C 或者 C++,那么我们就可以通过安卓应用来控制底层硬件了,比如说打开 LED,关闭 LED 的操作。

为什么要学习JNI?

我们主要是做嵌入式开发,但是现在随着安卓工控和界面越来越来多,我们和安卓打的交道越来越多,几年前更是安卓系统开发人员一将难求,虽然说我们嵌入式开发人员并不需要去写上层的应用,比如手机 APP 等,但是我们要给写安卓上层应用的工程师提供相应的接口。那么我们就用到了 JNI,我们要生成相应的库提供给上次开发人员调用。所以我们要学。

JNI 环境安装

因为 JNI 是属于 java 的,我们需要先在电脑上安装 java 环境。Java 的版本需要大于 1.1,因为 1.1以后才支持 JNI

如何生成so 库?

生成 so 库我们有俩种方法, 第一种是使用 CMake , CMake 是跨平台编译工具, 要配合CMakeList.txt 和 gradle 来使用。第二种是使用 NDK 自带的脚本工具 ndk-build 配合 Android.mk 来使用。这里我只介绍第一种方法,使用 ndk-build 的方法现在不是太常用了。

88.2 使用CMake编译工具来生成so库

1.下载 CMake 编译工具

可以通过 AndroidStudio->SDK->SDK Tools->-CMake 来下载。如果在安装 AndroidStudio 的时候已经安装了,则不必再次安装,如下图所示:

 

2.建一个 Native c++项目

打开 AndroidStudio,选择新建一个项目,如下图所示:

 

然后选择 Native c++,并点击下一步,如下图所示: 

 

这里我们把工程名字设置成 JniTest,保存路径设置成 G:\adnroid\PRO\JNI(大家依据自己的电脑选择路径即可,路径不要有中文),语言设置为 Java,其他保持不变,然后点击下一步。如下图所示: 

 

C++标准这里我们选择 C++11,然后点击下一步,如下图所示: 

 

稍等片刻,编译完成如下图所示: 

 

我们新创建的这个工程会给我们自动创建一个 jni 的例子,我们来一起分析下这个例子,然后在来仿写我们自己的 jni,先学会爬,然后在学会跑,我们打开 MainActivity,如下图所示: 

 

在这个文件里面我们定义了一个 stringFromJNI()函数,并且我们调用了 native-lib 库,如下图所示: 

 

然后我们打开 src/main/cpp/native-lib.cpp 这个 cpp 文件,native-lib 就是这个文件编译得到的,如下图所示: 

 

然后之前我们已经提到了,使用 CMake 编译 so 库,我们需要配合 CMakeList.txt 和 gradle 来使用,那我我们来看一下我们创建的这个工程有没有这俩个文件,如下图所示: 

 

那我们编译生成的 so 库文件是在哪里呢?我们点击一下编译,如下图所示: 

 

然后我们切换到 project,进到 G:\adnroid\PRO\JNI\app\build\intermediates\cmake\debug\obj 下面,就可以看到我们生成的 so 库了,那么这个库你就可以给别人来使用了,如下图所示: 

 

88.3 分析例子的JNI代码

上一节我们了解我们创建的这个工程这样的一个 JNI 架构,那么这一节我们来编译一个自己的 so 库, 要编译自己的 so 库,我们就要先来写一个 JNI 的代码,所以我们先来一起分析下他给我们的这个例子的代码,如下图所示:

 

避免编译器按照 C++的方法来编译 C 函数

extern "C"

关键字 JNIEXPORT 的作用是表示这个函数可以被其他的函数调用,这个有点类似于 C++的 PUBLIC 修饰符

JNIEXPORT

关键字 JNICALL 为空,没有含义

JNICALL

关键字定义如下:

 

关键字 jstring 表示的是字符串类型,与之类似的还有 jint,jchar,与之 c 语言对应的类型就是 int 和char。

jstring

Java_com_example_jnitest_MainActivity_stringFromJNI 是函数名的名称,这个我们不能随便定义,我们使用的是静态注册,所以他有格式要求,与之对应的还有动态注册,这里我们不做介绍。

格式:Java _包名 _ 类名_Java 需要调用的方法名

按照这个格式我们可以知道,包名为 com_example_jnitest,也就是 MainActivity 所在的包名,如下图所示:

 

 类名为 MainActivity,也就是定义 native 函数的类,也就是 MainActivity,如下图所示:

 

Java 需要调用的方法名为 stringFromJNI,也就是我们定义的 public native String stringFromJNI(),如下图所示: 

 

参数 JNIEnv* env,与 java 进行交互的相关的函数

JNIEnv* env

定义如下:

 

参数 jobject,代表 native 函数的 java 类的实例。

定义了一个字符串 Hello from C++,并返回这个字符串

std::string hello = "Hello from C++";

return env->NewStringUTF(hello.c_str());

88.4 实战-仿写JNI代码

接下来我们来仿写一个这个方法,我们已打开 led 灯代码为例。

1.  我们先在 MainActivity 里面实现三个方法,分别对应打开,关闭,和控制,我们添加以下代码:

public native int LedOpen();

public native int LedClos();

public native int LedIoctl(int num,int en);

如下图所示:

 

2.然后我们打开 native-lib.cpp 文件,仿写 jin 代码,分别仿写打开,关闭,控制的实现,代码如下: 

extern "C" JNIEXPORT jint JNICALL 
Java_com_example_jnitest_MainActivity_LedOpen(
JNIEnv* env, jobject /* this */) {
return 0;
}
extern "C" JNIEXPORT jint JNICALL 
Java_com_example_jnitest_MainActivity_LedClos(
JNIEnv* env, 
jobject /* this */) {
return 0;
}
extern "C" JNIEXPORT jint JNICALL 
Java_com_example_jnitest_MainActivity_LedIoctl(
JNIEnv* env, 
jobject /* this */, 
jint num,
jint en) { 
return 0;
}

如下图所示:

 

3.接下来我们添加控制 led 的 c 代码,这部分的代码和我们直接用 C 控制 led 的代码是一样的,我们先添加需要的头文件,代码如下: 

#include <stdio.h> 
#include <stdlib.h> 
#include <fcntl.h> 
#include <errno.h> 
#include <unistd.h> 
#include <sys/types.h>
#include <sys/stat.h> #include <string.h> #include <stdint.h> #include <termios.h> #include <android/log.h> #include <sys/ioctl.h>

如下图所示:

 

4.我们在 Java_com_example_jnitest_MainActivity_LedOpen 里面添加打开 led 设备节点的 C 代码: 先添加头文件和 fd 句柄的定义 代码如下

#include <android/log.h>为安卓打印调试信息的头文件。

#include <android/log.h> 
int	fd = 0;

如下图所示:

 

然后在 Java_com_example_jnitest_MainActivity_LedOpen 里面添加打开 led 节点的代码,代码如下: 

fd = open("/dev/leds_ctl", O_RDWR | O_NDELAY | O_NOCTTY); if(fd <= 0){
    android_log_print(ANDROID_LOG_INFO, "serial", "open /dev/leds_ctl Error");
}else{
    android_log_print(ANDROID_LOG_INFO, "serial", "open /dev/leds_ctl Sucess fd=%d", fd);
}

如下图所示:

 

5.在 Java_com_example_jnitest_MainActivity_LedClos 里面添加关闭 fd 的代码,代码如下:

if(fd > 0) close(fd);

如下图所示:

 

6.在 Java_com_example_jnitest_MainActivity_LedIoctl 添加 led 控制的代码,代码如下:

ioctl(fd,num,en);

如下图所示:

 

7.我们点击一下编译,这样就可以在默认路径下生成我们这个 so 库了,如下图所示: 

 

我们切换到 project 下面,在默认路径下可以看到我们生成的 so 库,如下图所示: 

 

  1. 我们可以看到,我们生成的库有 arm64-v8a,armeabi-v7a,x86,x86_64 的库,但是我们实际上并不需要这么多库,在现阶段,我们需要需要 armeabi-v7a 就可以满足大部分的需求了,armeabi-v7a 兼容X86,ARMV7,ARMV8,而且这个库生成的路径很不好找,我们能不能改一下他生成的路径并只生成我们需要的库呢?答案当然是可以的。

(1)更改 so 库的生成目录,我们打开 CMakeList.txt,添加下面的一句设置:

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

简化这个设置就是:

set(原路径,目标路径,生成那些平台对应的库文件)

参数解析如下:

CMAKE_CURRENT_SOURCE_DIR:这个是 cmake 的库的原路径

/../jniLibs/:这个是指与 CMakeList.txt 所在目录的同一级目录

ANDROID_ABI :生成那些平台对应的库文件。

我们打开 CMakeList.txt,在里面添加这句代码,代码如下:

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY

${CMAKE_CURRENT_SOURCE_DIR}/../jniLibs/${ANDROID_ABI})

如下图所示:

 

(2)生成指定平台的 so 库,我们打开 gradle,这句代码的意思就是说添加只生成 armeabi-v7a 的库

abiFilters "armeabi-v7a"

如下图所示:

 

(3)我们把之前生成的 so 的库删掉,不然编译会出问题,因为不能同时有俩个一样的库,如下图所示: 

 

(4)点击编译,编译完成我们就可以在 main 目录下看到我们生成的 armeabi-v7a 的库了,如下图所示: 

 

这个我们就得到了 so 库,这样既保护了我们的代码,又方便别人使用,那么我们要怎么使用呢?

88.5 使用编译好的so库(实战)

上一小节我们已经编译了 so 库,那么我们或者其他人拿到这个 so 后要怎么使用呢?这一节我们通过写一个简单的 app 来给大家演示下。

我们打开 AS,然后新建一个项目,选择一个空的 Activity,如下图所示:

 

这里我要注意一下,我们的包名要和我们调用的 jni 库的包名一样,否则会出问题,之前我们在写 jni 的时候,我们的包名如下图所示:

包名:

 

那么我们在新建工程的时候包名也要是这个,如下图所示: 

 

创建成功以后,我们找到我们新创建的这个工程的所在目录,然后把我们之前编译好的 so 库放进去。存放 so 库的文件夹一定要为 jniLibs,名字不要改,如下图所示: 

 

Jnilibs 文件夹放的就是我们生成的 so 库,如下图所示: 

 

然后我们点击一下 AS 软件的同步,如下所示: 

 

同步完成以后就可以在工程目录下看到我们这个 so 的文件夹了,如下图所示: 

 

然后我们在 MainActivity 里面引用我们这个库,我们添加以下代码:

static {

System.loadLibrary("native-lib");

}

如下图所示:

然后我们声明一下方法,在 MainActivity.java 里面添加以下代码,代码如下:

public native int LedOpen();

public native int LedClos();

public native int LedIoctl(int num,int en);

如下图所示:

 

然后我们打开 activity_main.xml 文件, 

 

然后删掉以下代码:

 设置俩个 button,代码如下:

<LinearLayout
android:layout_width="match_parent" 
android:layout_height="match_parent" 
android:orientation="vertical" 
android:gravity="center"
>
<Button
android:id="@+id/button2"
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:text="LED1 OFF" />

<Button
android:id="@+id/button1" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:layout_marginTop="87dp" 
android:text="LED1 ON" />
</LinearLayout>

 添加完如下图所示:

 

然后打开 MainActivity.java,声明俩个按键,并且在按键的点击事件里面打开设备节点和进行控制 led 灯的操作,代码如下:

声明俩个按键:

private Button led1_on;

private Button led1_off;

按键的点击事件

led1_on = (Button) findViewById(R.id.button1); 
led1_off = (Button) findViewById(R.id.button2);
led1_on.setOnClickListener(new View.OnClickListener() { 
@Override
public void onClick(View view) { 
LedOpen();
LedIoctl(1, 1);
}
});
led1_off.setOnClickListener(new View.OnClickListener() 
{ 
@Override
public void onClick(View view) { 
LedOpen();
LedIoctl(0, 0);
}
});
}

如下图所示:

 

然后添加以下代码:

import android.view.View;

import android.widget.Button;

如下图所示:

 

然后我们连接开发板,这里我们使用的是 IMX6Q 开发板安卓 4.4 系统,这里一定要使用开发板,不然看不到点灯效果,连接好以后,我们点击运行,如下图所示:

然后我们就可以看到 app 已经成功安装到了开发板上,并且可以控制 led 灯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值