Android Studio 串口jni开发

发布于 2019-09-26 作者 风铃 47次 浏览 版块 前端


1.开发环境



2. 创建新工程

创建SerialPortDemo工程, Minimum SDK 选择 API 19: Android 4.4。



3. 工程环境

安装CMake/LLDB/NDK, Gradle Version 4.4





4. 切换工程显示方式



5.修改build.gradle



6.修改app的build.gradle



7.创建jni目录



SerialPort.h

#include <jni.h>

#ifndef _Included_com_imlaidian_serialportdemo_serialapi_SerialPort
#define _Included_com_imlaidian_serialportdemo_serialapi_SerialPort

#ifdef __cplusplus
extern "C" {
#endif

// 命名格式: java_包名_类目录_类名_接口
JNIEXPORT jobject JNICALL Java_com_imlaidian_serialportdemo_serialapi_SerialPort_open
(JNIEnv *, jclass, jstring, jint, jint);

JNIEXPORT void JNICALL Java_com_imlaidian_serialportdemo_serialapi_SerialPort_close
(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif

#endif

SerialPort.c

/*
* Copyright 2009-2011 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.
*/

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.h>

#include "termios.h"
#include "SerialPort.h"

#include "android/log.h"
static const char *TAG="serial_port";
#define LOGI(fmt, args…) __android_log_print(ANDROID_LOG_INFO, TAG, fmt, ##args)
#define LOGD(fmt, args…) __android_log_print(ANDROID_LOG_DEBUG, TAG, fmt, ##args)
#define LOGE(fmt, args…) __android_log_print(ANDROID_LOG_ERROR, TAG, fmt, ##args)

static speed_t getBaudrate(jint baudrate)
{
switch(baudrate) {
case 0: return B0;
case 50: return B50;
case 75: return B75;
case 110: return B110;
case 134: return B134;
case 150: return B150;
case 200: return B200;
case 300: return B300;
case 600: return B600;
case 1200: return B1200;
case 1800: return B1800;
case 2400: return B2400;
case 4800: return B4800;
case 9600: return B9600;
case 19200: return B19200;
case 38400: return B38400;
case 57600: return B57600;
case 115200: return B115200;
case 230400: return B230400;
case 460800: return B460800;
case 500000: return B500000;
case 576000: return B576000;
case 921600: return B921600;
case 1000000: return B1000000;
case 1152000: return B1152000;
case 1500000: return B1500000;
case 2000000: return B2000000;
case 2500000: return B2500000;
case 3000000: return B3000000;
case 3500000: return B3500000;
case 4000000: return B4000000;
default: return -1;
}
}

/*
* Class: android_serialport_SerialPort
* Method: open
* Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
*/
JNIEXPORT jobject JNICALL Java_com_imlaidian_serialdemo_SerialPort_SerialPort_open
(JNIEnv *env, jclass thiz, jstring path, jint baudrate, jint flags)
{
int fd;
speed_t speed;
jobject mFileDescriptor;

/* Check arguments */
{
    speed = getBaudrate(baudrate);
    if (speed == -1) {
        /* TODO: throw an exception */
        LOGE(&#34;Invalid baudrate&#34;);
        return NULL;
    }
}

/* Opening device */
{
    jboolean iscopy;
    const char *path_utf = (*env)-&gt;GetStringUTFChars(env, path, &amp;iscopy);
    LOGD(&#34;Opening serial port %s with flags 0x%x&#34;, path_utf, O_RDWR | flags);
    fd = open(path_utf, O_RDWR | flags);
    LOGD(&#34;open() fd = %d&#34;, fd);
    (*env)-&gt;ReleaseStringUTFChars(env, path, path_utf);
    if (fd == -1)
    {
        /* Throw an exception */
        LOGE(&#34;Cannot open port&#34;);
        /* TODO: throw an exception */
        return NULL;
    }
}

/* Configure device */
{
    struct termios cfg;
    LOGD(&#34;Configuring serial port&#34;);
    if (tcgetattr(fd, &amp;cfg))
    {
        LOGE(&#34;tcgetattr() failed&#34;);
        close(fd);
        /* TODO: throw an exception */
        return NULL;
    }

    cfmakeraw(&amp;cfg);
    cfsetispeed(&amp;cfg, speed);
    cfsetospeed(&amp;cfg, speed);

    if (tcsetattr(fd, TCSANOW, &amp;cfg))
    {
        LOGE(&#34;tcsetattr() failed&#34;);
        close(fd);
        /* TODO: throw an exception */
        return NULL;
    }
}

/* Create a corresponding file descriptor */
{
    jclass cFileDescriptor = (*env)-&gt;FindClass(env, &#34;java/io/FileDescriptor&#34;);
    jmethodID iFileDescriptor = (*env)-&gt;GetMethodID(env, cFileDescriptor, &#34;&lt;init&gt;&#34;, &#34;()V&#34;);
    jfieldID descriptorID = (*env)-&gt;GetFieldID(env, cFileDescriptor, &#34;descriptor&#34;, &#34;I&#34;);
    mFileDescriptor = (*env)-&gt;NewObject(env, cFileDescriptor, iFileDescriptor);
    (*env)-&gt;SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
}

return mFileDescriptor;

}

/*
* Class: cedric_serial_SerialPort
* Method: close
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_imlaidian_serialdemo_SerialPort_SerialPort_close
(JNIEnv *env, jobject thiz)
{
jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");

jfieldID mFdID = (*env)-&gt;GetFieldID(env, SerialPortClass, &#34;mFd&#34;, &#34;Ljava/io/FileDescriptor;&#34;);
jfieldID descriptorID = (*env)-&gt;GetFieldID(env, FileDescriptorClass, &#34;descriptor&#34;, &#34;I&#34;);

jobject mFd = (*env)-&gt;GetObjectField(env, thiz, mFdID);
jint descriptor = (*env)-&gt;GetIntField(env, mFd, descriptorID);

LOGD(&#34;close(fd = %d)&#34;, descriptor);
close(descriptor);

}


由于api 19 之后的 termios.h 里面的函数有调整,因此调试过程中,出现

java.lang.UnsatisfiedLinkError: dlopen failed: cannot locate symbol "tcgetattr" referenced by "libserialport.so"…

解决方法:

将api 19  的 termios.h 拷贝到 jni 目录下

termios.h

/*
* Copyright © 2008 The Android Open Source Project
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _TERMIOSH
#define _TERMIOSH

#include <sys/cdefs.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <stdint.h>
#include <linux/termios.h>

__BEGIN_DECLS

/* Redefine these to match their ioctl number */
#undef TCSANOW
#define TCSANOW TCSETS

#undef TCSADRAIN
#define TCSADRAIN TCSETSW

#undef TCSAFLUSH
#define TCSAFLUSH TCSETSF

static inline int tcgetattr(int fd, struct termios *s)
{
return ioctl(fd, TCGETS, s);
}

static inline int tcsetattr(int fd, int __opt, const struct termios *s)
{
return ioctl(fd, __opt, (void *)s);
}

static inline int tcflow(int fd, int action)
{
return ioctl(fd, TCXONC, (void *)(intptr_t)action);
}

static inline int tcflush(int fd, int queue)
{
return ioctl(fd, TCFLSH, (void *)(intptr_t)
queue);
}

static inline pid_t tcgetsid(int fd)
{
pid_t _pid;
return ioctl(fd, TIOCGSID, &_pid) ? (pid_t)-1 : _pid;
}

static inline int tcsendbreak(int fd, int duration)
{
return ioctl(fd, TCSBRKP, (void *)(uintptr_t)
duration);
}

static inline speed_t cfgetospeed(const struct termios *s)
{
return (speed_t)(s->c_cflag & CBAUD);
}

static inline int cfsetospeed(struct termios *s, speed_t speed)
{
s->c_cflag = (s->c_cflag & ~CBAUD) | (speed & CBAUD);
return 0;
}

static inline speed_t cfgetispeed(const struct termios *s)
{
return (speed_t)(s->c_cflag & CBAUD);
}

static inline int cfsetispeed(struct termios *s, speed_t speed)
{
s->c_cflag = (s->c_cflag & ~CBAUD) | (speed & CBAUD);
return 0;
}

static inline void cfmakeraw(struct termios *s)
{
s->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
s->c_oflag &= ~OPOST;
s->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
s->c_cflag &= ~(CSIZE|PARENB);
s->c_cflag |= CS8;
}

__END_DECLS

#endif /* _TERMIOSH /


此处参考:

http://jp1017.github.io/2016/06/30/jni-开发错误之-java-lang-UnsatisfiedLinkError-dlopen-failed-cannot-locate-symbol-tcgetattr-referenced-by-libserial-port-so/

http://www.eoeandroid.com/thread-914514-1-1.html



8.创建串口native接口类



SerialPort.java

/
* 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 com.imlaidian.serialportdemo.serialapi;

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 = &#34;SerialPort&#34;;

/*
 * 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(&#34;/system/bin/su&#34;);
            String cmd = &#34;chmod 666 &#34; + device.getAbsolutePath() + &#34;

"
+ "exit
";
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, 8, 1,&#39;n&#39;);
    if (mFd == null) {
        Log.e(TAG, &#34;native open returns null&#34;);
        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 databits,int stopbits,char parity);
public native void close();
static {
    System.loadLibrary(&#34;serialport&#34;); // 调用jni生成的库
}

}



SerialPortFinder.java

/*
* 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 com.imlaidian.serialportdemo.serialapi;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.Iterator;
import java.util.Vector;

import android.util.Log;

public class SerialPortFinder {

public class Driver {
    public Driver(String name, String root) {
        mDriverName = name;
        mDeviceRoot = root;
    }
    private String mDriverName;
    private String mDeviceRoot;
    Vector&lt;File&gt; mDevices = null;
    public Vector&lt;File&gt; getDevices() {
        if (mDevices == null) {
            mDevices = new Vector&lt;File&gt;();
            File dev = new File(&#34;/dev&#34;);
            File[] files = dev.listFiles();
            int i;
            for (i=0; i&lt;files.length; i++) {
                if (files[i].getAbsolutePath().startsWith(mDeviceRoot)) {
                    Log.d(TAG, &#34;Found new device: &#34; + files[i]);
                    mDevices.add(files[i]);
                }
            }
        }
        return mDevices;
    }
    public String getName() {
        return mDriverName;
    }
}

private static final String TAG = &#34;SerialPort&#34;;

private Vector&lt;Driver&gt; mDrivers = null;

Vector&lt;Driver&gt; getDrivers() throws IOException {
    if (mDrivers == null) {
        mDrivers = new Vector&lt;Driver&gt;();
        LineNumberReader r = new LineNumberReader(new FileReader(&#34;/proc/tty/drivers&#34;));
        String l;
        while((l = r.readLine()) != null) {
            // Issue 3:
            // Since driver name may contain spaces, we do not extract driver name with split()
            String drivername = l.substring(0, 0x15).trim();
            String[] w = l.split(&#34; +&#34;);
            if ((w.length &gt;= 5) &amp;&amp; (w[w.length-1].equals(&#34;serial&#34;))) {
                Log.d(TAG, &#34;Found new driver &#34; + drivername + &#34; on &#34; + w[w.length-4]);
                mDrivers.add(new Driver(drivername, w[w.length-4]));
            }
        }
        r.close();
    }
    return mDrivers;
}

public String[] getAllDevices() {
    Vector&lt;String&gt; devices = new Vector&lt;String&gt;();
    // Parse each driver
    Iterator&lt;Driver&gt; itdriv;
    try {
        itdriv = getDrivers().iterator();
        while(itdriv.hasNext()) {
            Driver driver = itdriv.next();
            Iterator&lt;File&gt; itdev = driver.getDevices().iterator();
            while(itdev.hasNext()) {
                String device = itdev.next().getName();
                String value = String.format(&#34;%s (%s)&#34;, device, driver.getName());
                devices.add(value);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return devices.toArray(new String[devices.size()]);
}

public String[] getAllDevicesPath() {
    Vector&lt;String&gt; devices = new Vector&lt;String&gt;();
    // Parse each driver
    Iterator&lt;Driver&gt; itdriv;
    try {
        itdriv = getDrivers().iterator();
        while(itdriv.hasNext()) {
            Driver driver = itdriv.next();
            Iterator&lt;File&gt; itdev = driver.getDevices().iterator();
            while(itdev.hasNext()) {
                String device = itdev.next().getAbsolutePath();
                devices.add(device);
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    }
    return devices.toArray(new String[devices.size()]);
}

}


9. gradle sync同步




10. 生成.so 路径


收藏
暂无回复