AndroidNDK开发运用Cmake编译生成so文件

最近做串口开发需求编译不同的so文件,所以查找了各种资料,学习了一下so编译.

1.增加Cmake文件:

cmake_minimum_required(VERSION 3.4.1) //cmake版本include_directories(src/main/jni/serial_port)  //引进的jni文件途径
aux_source_directory(src/main/jni SRC_FILE)     //src文件途径add_library(serial_port SHARED ${SRC_FILE})
find_library(log-lib log)
​
target_link_libraries(serial_port ${log-lib})

AndroidNDK开发使用Cmake编译生成so文件

2.增加Cmake依靠:

externalNativeBuild {
  cmake {
    cppFlags ""
​
    abiFilters "arm64-v8a","armeabi-v7a"//生成的so渠道类型
​
   }
}
externalNativeBuild {
  cmake {
    path 'CMakeLists.txt'//Cmake地址
   }
}

AndroidNDK开发使用Cmake编译生成so文件

3.jni文件如下:

AndroidNDK开发使用Cmake编译生成so文件

4.Android.mk文件:

#
# 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. 
#
​
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)
​
TARGET_PLATFORM := android-3
LOCAL_MODULE   := serial_port //模块称号
LOCAL_SRC_FILES := SerialPort.c
LOCAL_LDLIBS   := -llog
​
include $(BUILD_SHARED_LIBRARY)

5.Application.mk文件

APP_ABI := arm64-v8a,armeabi-v7a//装备abi渠道

6.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 <termios.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <jni.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_android_1serialport_1api_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("Invalid baudrate");
     return NULL;
    }
  }
​
  /* Opening device */
  {
   jboolean iscopy;
   const char *path_utf = (*env)->GetStringUTFChars(env, path, &iscopy);
   LOGD("Opening serial port %s with flags 0x%x", path_utf, O_RDWR | flags);
   fd = open(path_utf, O_RDWR | flags);
   LOGD("open() fd = %d", fd);
    (*env)->ReleaseStringUTFChars(env, path, path_utf);
   if (fd == -1)
    {
     /* Throw an exception */
     LOGE("Cannot open port");
     /* TODO: throw an exception */
     return NULL;
    }
  }
​
  /* Configure device */
  {
   struct termios cfg;
   LOGD("Configuring serial port");
   if (tcgetattr(fd, &cfg))
    {
     LOGE("tcgetattr() failed");
     close(fd);
     /* TODO: throw an exception */
     return NULL;
    }
​
   cfmakeraw(&cfg);
   cfsetispeed(&cfg, speed);
   cfsetospeed(&cfg, speed);
​
   if (tcsetattr(fd, TCSANOW, &cfg))
    {
     LOGE("tcsetattr() failed");
     close(fd);
     /* TODO: throw an exception */
     return NULL;
    }
  }
​
  /* Create a corresponding file descriptor */
  {
   jclass cFileDescriptor = (*env)->FindClass(env, "java/io/FileDescriptor");
   jmethodID iFileDescriptor = (*env)->GetMethodID(env, cFileDescriptor, "<init>", "()V");
   jfieldID descriptorID = (*env)->GetFieldID(env, cFileDescriptor, "descriptor", "I");
   mFileDescriptor = (*env)->NewObject(env, cFileDescriptor, iFileDescriptor);
    (*env)->SetIntField(env, mFileDescriptor, descriptorID, (jint)fd);
  }
​
  return mFileDescriptor;
}
​
/*
 * Class:   cedric_serial_SerialPort
 * Method:   close
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close
  (JNIEnv *env, jobject thiz)
{
  jclass SerialPortClass = (*env)->GetObjectClass(env, thiz);
  jclass FileDescriptorClass = (*env)->FindClass(env, "java/io/FileDescriptor");
​
  jfieldID mFdID = (*env)->GetFieldID(env, SerialPortClass, "mFd", "Ljava/io/FileDescriptor;");
  jfieldID descriptorID = (*env)->GetFieldID(env, FileDescriptorClass, "descriptor", "I");
​
  jobject mFd = (*env)->GetObjectField(env, thiz, mFdID);
  jint descriptor = (*env)->GetIntField(env, mFd, descriptorID);
​
  LOGD("close(fd = %d)", descriptor);
  close(descriptor);
}
​
jstring Java_com_example_compilesodemo_MainActivity_sayHello(JNIEnv* env,jobject jobj){
  char* text="I am from C";
  return (*env)->NewStringUTF(env,text);
}

7.SerialPort.h文件:

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class android_serialport_api_SerialPort */#ifndef _Included_android_serialport_api_SerialPort
#define _Included_android_serialport_api_SerialPort
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:   android_serialport_api_SerialPort
 * Method:   open
 * Signature: (Ljava/lang/String;II)Ljava/io/FileDescriptor;
 */
JNIEXPORT jobject JNICALL Java_android_1serialport_1api_SerialPort_open
  (JNIEnv *, jclass, jstring, jint, jint);
​
/*
 * Class:   android_serialport_api_SerialPort
 * Method:   close
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_android_1serialport_1api_SerialPort_close
  (JNIEnv *, jobject);
​
​
#ifdef __cplusplus
}
#endif
#endif

8.运转项目:

直接跑项目或许运用gradle构建so生成的so文件如下:这儿大家依据自己的需求装备生成不同的so文件

AndroidNDK开发使用Cmake编译生成so文件

9.项目中引进so文件:

如果不引进so文件初始化和调用时会报错,提示找不到so文件,

AndroidNDK开发使用Cmake编译生成so文件

AndroidNDK开发使用Cmake编译生成so文件

10.Java调用so的测验代码如下:

public class MainActivity extends AppCompatActivity {
  private static final String TAG = MainActivity.class.getName();
​
   {
    System.loadLibrary("serial_port");//加载so库
   }
​
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    initView();
   }
​
  private void initView() {
    TextView textView =  ((TextView)findViewById(R.id.tv_test));
    textView.setText(sayHello());
    Log.d(TAG,"-----传入的数据为:------"+ textView.getText().toString());
   }
​
  public native String sayHello();//调用jni方法
}

11.运转效果如下:

AndroidNDK开发使用Cmake编译生成so文件

12.打印日志如下:

AndroidNDK开发使用Cmake编译生成so文件

13.总结:

能够看到项目demo成功运转,期前也遇到不少问题,so的加载和调用,jni初始化,数据传递等等,如果有兴趣的同学能够自己去试试,学到手的东西才是自己的。本文是以串口通信的so为例,所以里面的头文件及数据调用的方法都是在加载串口数据,不过我为了测验所以暂时只写了一个简单的,由于测验串口需求设备和串口通信帮手,这儿先不讲,后面有空会逐个讲解串口通信流程和实战demo.

14.项目源码如下:

gitee.com/jackning_ad…