【Linux开发】JNI for Android实验

这是一项拖延了很久没做的事,为什么实验Android平台下的JNI实验?安卓系统现在太成熟了,开发人员也很多,相对于闭源的IOS系统来说,安卓系统在消费电子、医疗电子、工业控制等方面上的优势太大了,因为不可能在工业控制器上嵌入一个好几千的iPad。以我熟悉的工业控制方向为例,现在有很多基于Android系统的工控一体机(一体机指带有显示器工控板),可以做出非常友好且功能强大的人机交互软件,侵占了一部分组态屏的市场......

Linux + Qt 的组合也可以实现非常好的人机交互系统,而且该组合对系统的资源要求没有Android系统高。不过Android的开发人员比QT的开发人员多很多,而且有很多现成的Android APP可以直接使用,并且Android系统的应用的可移植性很强(虽然QT的可移植性也很强,但是做不到和手机能够通用APP)。

工控安卓一体机上使用的Android系统和我们手机上的Android系统是一样的,而且基本都是使用ARM处理器,配置性能也很强悍,例如下图是在网上找到的一种7寸安卓系统电容屏,基于全志处理器。

当然工控安卓一体机和Android智能手机也有不一样的地方,这体现在使用场景上。首先工控一体机的使用环境苛刻,需要对工作温度、湿度、电磁干扰等多种环境因素有很高的耐受力。例如一般商业级温度范围为0~70℃,我们的手机一般都是只满足商业级温度范围,而工控机的工作温度范围需要到达-40~85℃,手机在这个温度范围内可能已经罢工了,这点上很多PLC和基于单片机控制器都是可以满足的,但是据我之前调查所知,组态屏包括现在的Android工控机一体机都达不到这个温度范围,短板不在处理器和其它芯片组,而是在显示器,因为一般的LCD显示器中的液晶是一种类似于液态物质,其在非常低温的情况下,物理特性会改变,会导致显示不清楚甚至会“冻坏”显示器,能够达到-40℃的屏幕,我查了只有日本的三菱。

其次,工控一体机的主要目的是工业控制,也就是它不能只显示人机交互界面,还需要针对不同的使用场景添加一些手机上没有的模块,比如需要一体机能够通过RS485总线向工业设备发送控制数据,这个功能手机上肯定是用不到的,但是工控设备上用得太多了。手机APP开发的时候接触不到串口的开发,好像没有对应的库,但是我们必须要有这个功能,这时候JNI 就可以实现这个功能。

终于回到主题了,JNI 全称 Java Native Interface。

还是以Android系统下进行串口开发为例。由于Android是基于Linux系统的,在Linux系统上进行串口的开发比较方便,所以可以在Linux系统上开发一个程序,该程序可以操作硬件的串口设备进行收发数据,并提供了方便使用的接口,将该程序编译成Linux的库文件“.so”,这样其它Linux软件甚至Android系统都可以通过调用这个库文件实现串口的访问。在开发Android的应用程序时,使用库文件提供的接口函数,就可以实现安卓应用对串口的控制,类似的,也可以实现IIC、SPI、CAN等等设备的间接控制。

关于如何开发能够给Android应用开发使用的Linux库文件,需要参考Android的NDK(Native Development Kit)。Android官方对于NDK的解释为:“Android NDK 是一套允许您使用 C 和 C++ 等语言,以原生代码实现部分应用的工具集。在开发某些类型的应用时,这有助于您重复使用以这些语言编写的代码库。”

JNI 和 NDK 是有区别的,JNI是 Java 语言自带的,也就是说 Java 语言本身就可以访问 Native 的库文件(Windows上的DLL文件和Linux上的 .so 和 .a 文件),所以安卓应用自然可以访问Linux系统上的资源。NDK是Google提供的一套开发包,有了NDK之后可以直接在AS上开发C/C++程序,NDK中包含了编写C/C++程序需要的头文件和库文件,但是不如Linux系统的全。

引用https://www.cnblogs.com/jingzhishen/p/3951055.html的介绍:“对于JNI和NDK很多Android开发初学者没有搞明白这个问题:

JNI是 Java调用Native机制,是Java语言自己的特性全称为 Java Native Interface,类似的还有微软.Net Framework上的p/invoke,可以让C#或Visual Basic.Net调用C/C++的API,所以说JNI和Android没有关系,在PC上开发Java的应用,如果运行在Windows平台使用 JNI是是经常的,比如说读写Windows的注册表。

NDK是Google公司推出的帮助 Android开发者通过C/C++本地语言编写应用的开发包,包含了C/C++的头文件、库文件、说明文档和示例 代码,我们可以理解为Windows Platform SDK一样,是纯C/C++编写的,但是Android并不支持纯C/C++编写的应用,同时NDK提供的库和函数功能很有限,仅仅处理些算法效率敏感的 问题,所以Android123推荐初学者学好Java后再学习JNI。”

下面基于我买的一块全志A33的开发板进行一次JNI实验。开发板提供了开发好的操作硬件串口的库文件:

库文件一共有三个,分别针对armeabi、armeabi-v7a、x86三种CPU架构,不同的CPU架构会使用不同的文件。

下面使用Android Studio创建一个Empty Activity,什么界面都不需要动。为了可以调用库文件,需要使用java代码将so库文件提供的接口“提取出来”,例如下面的代码:

/*
 * Copyright 2009 Cedric Priscal
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License. 
 */

package android_serialport_api;

import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import android.util.Log;

public class SerialPort {

	private static final String TAG = "SerialPort";

	/*
	 * Do not remove or rename the field mFd: it is used by native method close();
	 */
	private FileDescriptor mFd;
	private FileInputStream mFileInputStream;
	private FileOutputStream mFileOutputStream;

	public SerialPort(File device, int baudrate, int flags) throws SecurityException, IOException {

		/* Check access permission */
		if (!device.canRead() || !device.canWrite()) {
			try {
				/* Missing read/write permission, trying to chmod the file */
				Process su;
				su = Runtime.getRuntime().exec("su");
				String cmd = "chmod 666 " + device.getAbsolutePath() + "\n"
						+ "exit\n";
				su.getOutputStream().write(cmd.getBytes());
				if ((su.waitFor() != 0) || !device.canRead()
						|| !device.canWrite()) {
					throw new SecurityException();
				}
			} catch (Exception e) {
				e.printStackTrace();
				throw new SecurityException();
			}
		}

		mFd = open(device.getAbsolutePath(), baudrate, flags);
		if (mFd == null) {
			Log.e(TAG, "native open returns null");
			throw new IOException();
		}
		mFileInputStream = new FileInputStream(mFd);
		mFileOutputStream = new FileOutputStream(mFd);
	}

	// Getters and setters
	public InputStream getInputStream() {
		return mFileInputStream;
	}

	public OutputStream getOutputStream() {
		return mFileOutputStream;
	}

	// JNI
	private native static FileDescriptor open(String path, int baudrate, int flags);
	public native void close();
	static {
		System.loadLibrary("serial_port");
	}
}

然后在MainActivity.java文件中添加一些代码:

package com.example.myapplication;

import androidx.appcompat.app.AppCompatActivity;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import android.os.Bundle;
import android_serialport_api.SerialPort;
import android_serialport_api.SerialPortFinder;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        init_serial();
        /*  串口发送字节 */
        try {
            ttyS1OutputStream.write( bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        /* 串口接受字节 */
        try {
            ttyS1InputStream.read( bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private byte[] bytes =  new byte[]{(byte)'a',(byte)'b',(byte)'c',(byte)'d',(byte)'e',(byte)'f'};
    private SerialPort  serialttyS1;
    private InputStream  ttyS1InputStream;
    private OutputStream  ttyS1OutputStream;

    /*  打开串口 */
    private void init_serial() {
        try {
            serialttyS1 =  new SerialPort( new File( "/dev/ttyS2"),115200,0);
            ttyS1InputStream =  serialttyS1.getInputStream();
            ttyS1OutputStream =  serialttyS1.getOutputStream();
        }  catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在Activity的onCreate函数中调用init_serial函数初始化串口,init_serial中打开了Linux文件系统中的“/dev/ttyS2”设备,这个设备文件对应开发板上的串口2,并且获取了该设备的输入输出流,使用输出流可以进行串口数据发送,使用输入流可以进行串口数据的读取,将开发板串口2接到电脑,实验结果:

程序打印出一串字符,然后程序会卡死在 ttyS1InputStream.read() 函数中,这是一个阻塞式的读取函数,程序会在收到一个串口数据之后返回,在此之前,软件将会卡住(时间长甚至会黑屏),输入一个字符之后,函数返回,程序有正常运行了。

上面的操作忽略了不少细节,在使用JNI库的时候,遇到不少问题,其中一个是:java.lang.UnsatisfiedLinkError: Native method not found。这可能是JNI库的位置存放位置或者package名的问题,反正瞎弄弄好了,参考:

https://bbs.csdn.net/topics/392011508

https://blog.csdn.net/cao_dayong/article/details/76274266

对安卓不精通,遇到一些比较低级的问题。

测试程序放在了Gitee上了,欢迎讨论:https://gitee.com/StrongBoy/Android_JNI_TEST_UART

 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值