目录
创建Android.mk, Application.mk, touch.cpp这3个文件
前提背景:
1. 底层设备驱动已经实现ioctl相关操作接口
2. 本篇主要讲如何实现JNI层和应用层的ioctl相关内容
3. 这里是基于sda810开发板调试,其他arm开发板搭载安卓系统均可以调试
NDK环境搭建和Demo代码实现
环境搭建
下载NDK包和配置环境变量
1.到官网下载android-ndk-r13b-windows-x86_64.zip(我用的是这个版本,最新版本)
2.解压缩后将目录添加到PATH中,我这里是d:\android-ndk-r13b\
3.测试如下图,表示配置成功
代码实现
创建Android.mk, Application.mk, touch.cpp这3个文件
Note:这3个文件放在同一目录,我这里放在jni目录
1. Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_LDLIBS :=-llog
LOCAL_MODULE := touch
LOCAL_SRC_FILES := touch.cpp
include $(BUILD_SHARED_LIBRARY)
2. Application.mk
APP_ABI := all #编译所有平台
3. touch.cpp (文件名和Android.mk 的 MODULE匹配, JNI里面的语法知识不做介绍)
#include<android/log.h>
#include <jni.h>
#include <fcntl.h>
#define TAG "TOUCH-JNI" // 这个是自定义的LOG的标识
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,TAG ,__VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG ,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#define LOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
#define IOCTL_GET_DRIVER_VERSION 0x80084300
#define IOCTL_GET_DEVICE_TYPE 0x80084301
extern "C"
{
jstring Java_com_morgen_touch_MainActivity_getStringFromJni(JNIEnv* jni, jobject obj) {
return jni->NewStringUTF("Hello jni!");
}
jlong Java_com_morgen_touch_MainActivity_ioctl(JNIEnv* jni, jobject obj, jint code, jlong arg) {
char path[30] = "/proc/icn85xx_tool";
int fd = 0;
LOGE("code:0x%x arg:0x%x", code, arg);
fd = open(path, O_RDWR);
if (fd < 0) {
LOGE("Open file failed");
return -1;
}
switch (code) {
case IOCTL_GET_DRIVER_VERSION:
case IOCTL_GET_DEVICE_TYPE:
ioctl(fd, code, &arg);//注意这里是核心代码
LOGE("arg:0x%x", arg);
break;
default:
LOGE("CTLCODE was not right");
}
close(fd);
LOGE("ioctl successfully");
return arg;
}
}//extern "C"
4.到这里基本的jni层相关代码都配置OK了,接下来编译
a.打开命令窗口进入jni目录,输入ndk-build,
b.如下图,可以看到相关平台的so文件都已经编译成功
c.编译好了之后so文件在jni同级目录的libs目录下 (libtouch.so以生成)
d.到这里就为应用层app调用jni so库文件做好了准备
应用层app demo实现
使用android studio创建默认工程
1.创建好的工程目录如下(怎么创建android demo工程这里就不赘述了,只需要创建一个空壳应用就好):
2.将ndk编译好的libs目录拷贝到touch工程中的main目录下,如上图
引用libtouch.so库文件
1.在app工程目录下的build.gradle文件中添加 ndk和sourceSets.main这两个代码段
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.morgen.touch"
minSdkVersion 15
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk { //1.添加ndk模块名
moduleName "touch"
}
}
sourceSets.main{ //2.添加jni lib目录
jni.srcDirs=[]
jniLibs.srcDir "src/main/libs"
}
}
3.然后在MainActivity中载入touch模块
4.然后在onCreate中调用
5.MainActivity完整代码如下(到这里,引用和调用都以完成):
package com.morgen.touch;
import android.os.Bundle;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.annotation.NonNull;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import java.io.File;
public class MainActivity extends AppCompatActivity {
private TextView mTextMessage;
private Button mButton;
static {
System.loadLibrary("touch");
}
public native long ioctl(int a, long b);
private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
= new BottomNavigationView.OnNavigationItemSelectedListener() {
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case R.id.navigation_home:
mTextMessage.setText(R.string.title_home);
return true;
case R.id.navigation_dashboard:
mTextMessage.setText(R.string.title_dashboard);
return true;
case R.id.navigation_notifications:
mTextMessage.setText(R.string.title_notifications);
return true;
}
return false;
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BottomNavigationView navView = findViewById(R.id.nav_view);
mTextMessage = findViewById(R.id.message);
navView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
mButton = findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
long arg = 0;
Log.e("TOUCH-APP", "0x" + Long.toHexString(ioctl(0x80084300, arg)));
Log.e("TOUCH-APP", "0x" + Long.toHexString(ioctl(0x80084301, arg)));
Log.e("TOUCH-APP", "0x" + Long.toHexString(ioctl(0x80084302, arg)));
Log.e("TOUCH-APP", "0x" + Long.toHexString(ioctl(0x80084303, arg)));
Log.e("TOUCH-APP", "0x" + Long.toHexString(ioctl(0x80084304, arg)));
Log.e("TOUCH-APP", "0x" + Long.toHexString(ioctl(0xc0084310, arg)));
Log.e("TOUCH-APP", "0x" + Long.toHexString(ioctl(0xc0084320, arg)));
Log.e("TOUCH-APP", "0x" + Long.toHexString(ioctl(0xc0084321, arg)));
}
});
}
}
编译运行安装touch应用
1.安装后的应用
2.点击按钮运行,同时抓取logcat log(如下图),这里可以看到app层和jni层的log都有打印出来,但是有没有被底层识别呢,接下来我们再看底层设备驱动的简单实现
130|root@msm8994:/ # logcat | grep TOUCH
E/TOUCH-JNI(14217): code:0x80084300 arg:0x0
E/TOUCH-JNI(14217): arg:0x10310
E/TOUCH-JNI(14217): ioctl successfully
E/TOUCH-APP(14217): 0x10310
E/TOUCH-JNI(14217): code:0x80084301 arg:0x0
E/TOUCH-JNI(14217): arg:0x991110
E/TOUCH-JNI(14217): ioctl successfully
E/TOUCH-APP(14217): 0x991110
E/TOUCH-JNI(14217): code:0x80084302 arg:0x0
E/TOUCH-JNI(14217): arg:0x212
E/TOUCH-JNI(14217): ioctl successfully
E/TOUCH-APP(14217): 0x212
E/TOUCH-JNI(14217): code:0x80084303 arg:0x0
E/TOUCH-JNI(14217): arg:0x61702cf
E/TOUCH-JNI(14217): ioctl successfully
E/TOUCH-APP(14217): 0x61702cf
E/TOUCH-JNI(14217): code:0x80084304 arg:0x0
E/TOUCH-JNI(14217): arg:0x120020
E/TOUCH-JNI(14217): ioctl successfully
E/TOUCH-APP(14217): 0x120020
E/TOUCH-JNI(14217): code:0xc0084310 arg:0x0
E/TOUCH-JNI(14217): arg:0x0
E/TOUCH-JNI(14217): ioctl successfully
E/TOUCH-APP(14217): 0x0
E/TOUCH-JNI(14217): code:0xc0084320 arg:0x0
E/TOUCH-JNI(14217): arg:0x0
E/TOUCH-JNI(14217): ioctl successfully
E/TOUCH-APP(14217): 0x0
E/TOUCH-JNI(14217): code:0xc0084321 arg:0x0
E/TOUCH-JNI(14217): arg:0x0
E/TOUCH-JNI(14217): ioctl successfully
E/TOUCH-APP(14217): 0x0
设备驱动程序ioctl实现关键代码
1.代码如下:
static long tool_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct ts_data *data;
struct device *dev;
pr_info("ioctl, cmd=0x%08x, arg=0x%08lx", cmd, arg);
data = file->private_data;
if (data == NULL) {
pr_err("IOCTL with private data = NULL");
return -EFAULT;
}
switch (cmd) {
case IOCTL_GET_DRIVER_VERSION:
return put_user(CTS_DRIVER_VERSION_CODE,
(unsigned int __user *)arg);
case IOCTL_GET_DEVICE_TYPE:
return put_user(dev->hwdata->hwid,
(unsigned int __user *)arg);
}
return -ENOTSUPP;
}
static int tool_open(struct inode *inode, struct file *file)
{
file->private_data = PDE_DATA(inode);
return 0;
}
static struct file_operations tool_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.open = tool_open,
.unlocked_ioctl = tool_ioctl,
};
2.这时候抓取kernel log如下(cat dev/kmsg):
这里可以看到应用层传下来的cmd,底层设备驱动已经接收到了,这就说明从app-jni-kernel的ioctl功能已经实现
<I>Tool ioctl, cmd=0x80084300, arg=0x7fd4dc4508
<I>Tool ioctl, cmd=0x80084301, arg=0x7fd4dc4508
<I>Tool ioctl, cmd=0x80084302, arg=0x7fd4dc4508
<I>Tool ioctl, cmd=0x80084303, arg=0x7fd4dc4508
<I>Tool ioctl, cmd=0x80084304, arg=0x7fd4dc4508
======== 完 =========
到这里,从应用层到JNI层再到kernel层,功能实现OK,这就是上层应用来控制底层设备的大概逻辑。
以上,如有错误欢迎大家指出,共同进步,谢谢!