Android系统源码开发系列教学视频链接:
Android 10.0 AOSP源码编译:https://edu.csdn.net/course/detail/35479
Android 10.0 根文件系统和编译系统:https://edu.csdn.net/course/detail/35480
前言
jni的写法有好几种,在这里先介绍在android源码包+linux环境下的jni编程方式,随后会介绍ndk中的jni的编程方式。
一, JNI简介
jni, java native interface, java的本地接口,如果java代码想去调用native方法(实际就是c/c++函数),就需要用jni技术,在传统的java中jni技术就已经存在,但是由于传统的java代码中,不太去写架构或者平台相关的代码,所以以前jni不是太惹人注意,直到android出现后,android本身是一个linux操作系统, linux中的应用编程中就使用c/c++代码去调用驱动,假如android的应用apk想去调用底层的设备驱动,那么就必须要去调用c/c++代码,所以此时jni就显得很重要,它起着承上启下的作用.
二,JNI控制Led框架
编程之前首先需要给大家介绍一个大体的框架,我们需要在apk中去点一个led灯,此时就像做饭一样,介绍几个主料:
1, apk,实现button点灯
2, jni代码,调用linux的系统调用,去控制驱动
3, 驱动,完成控制led硬件
三, apk中使用jni中的接口
接口1: 调用动态库
static{
System.loadLibrary("led_jni"); // /system/lib/libled_jni.so
}
接口2: 声明本地方法
public native int OpenDev();
public native int DevOn();
public native int DevOff();
public native int CloseDev();
接口3: 直接调用本地方方法
OpenDev();
APP调用JNI代码非常简单
package fs.hq;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
public class LedActivity extends Activity {
Button ledBtnOn;
Button ledBtnOff;
private void init()
{
ledBtnOn =(Button)this.findViewById(R.id.btnOn);
ledBtnOn.setOnClickListener(onClickListener);
ledBtnOff =(Button)this.findViewById(R.id.btnOff);
ledBtnOff.setOnClickListener(onClickListener); }
OnClickListener onClickListener = new OnClickListener() {
@Override public void onClick(View v) {
switch (v.getId()) {
case R.id.btnOn:
Toast.makeText(LedActivity.this, "led on", 2000).show();
DevOn();//接口三: 调用native方法
break;
case R.id.btnOff:
Toast.makeText(LedActivity.this, "led off", 2000).show();
DevOff();//接口三: 调用native方法
// break;
default: break;
}
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
init();
//接口三: 调用native方法
OpenDev();
}
@Override
protected void onDestroy(){
// TODO Auto-generated method stub
super.onDestroy();
DevClose();// 接口三: 调用native方法
this.finish();
}
//接口一:静态区域代码,完整加载动态库,并且会完成映射表的注册
static{
System.loadLibrary("led_jni");
//该动态库的名字叫libled_jni.so,所在路径/system/lib/
}
//接口二: 通过native声明本地方法,只有声明,而没有定义
private native int OpenDev();
private native int DevOn();
private native int DevOff();
private native int DevClose();
}
四,jni代码的编写
1, 原理
java中调用c代码的基本原理是通过查看一个映射表来完成的,比如java中调用OpenDev方法时,实际是有一个c/c++函数与它一一对应,所以需要一个映射表,并且将其注册给dvm,当dvm解析java中的native方法是,会去查看这个映射表,并执行对应的c/c++函数,同时jni代码所在的文件是需要编译成动态库,
2, 代码编写
jni代码可以写成c文件或者是cpp文件,但是建议大家写成cpp文件,在此,以下代码是写成一个文件led_jni.cpp
#define LOG_TAG "MyLed_jni"
#include <utils/Log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include "jni.h"
static int fd = -1;
jint open_led(JNIEnv *env, jobject thiz)
{
LOGD("----^_^ %s------\n", __FUNCTION__);
fd = open("/dev/led1", O_RDWR);
if(fd < 0)
{
LOGE("open : %s\n", strerror(errno));
return -1;
}
return 0;
}
jint led_on(JNIEnv *env, jobject thiz)
{
LOGD("----^_^ %s------\n", __FUNCTION__);
jint val = 1;
jint ret = -1;
ret = write(fd, &val, 4);
if(ret < 0)
{
LOGE("write : %s\n", strerror(errno));
return -1;
}
return 0;
}
jint led_off(JNIEnv *env, jobject thiz)
{
LOGD("----^_^ %s------\n", __FUNCTION__);
jint val = 0;
jint ret = -1;
ret = write(fd, &val, 4);
if(ret < 0)
{
LOGE("write : %s\n", strerror(errno));
return -1;
}
return 0;
}
jint close_led(JNIEnv *env, jobject thiz)
{
LOGD("----^_^ %s------\n", __FUNCTION__);
if(fd > 0)
close(fd);
return 0;
}
static JNINativeMethod myMethods[] ={
{"openDev", "()I", (void *)open_led},
{"devOn", "()I", (void *)led_on},
{"devOff", "()I", (void *)led_off},
{"closeDev", "()I", (void *)close_led},
};
jint JNI_OnLoad(JavaVM * vm,void * reserved)
{
JNIEnv *env = NULL;
jint ret ;
ret = vm->GetEnv((void * * )&env, JNI_VERSION_1_4);
if(ret != 0)
{
LOGE("vm->GetEnv error\n");
return -1;
}
jclass myclz = env->FindClass("com/example/ledjnitest/LedActivity");
if(myclz == NULL)
{
LOGE("env->FindClass error\n");
return -1;
}
ret = env->RegisterNatives(myclz, myMethods, sizeof(myMethods)/sizeof(myMethods[0]));
if(ret < 0)
{
LOGE("env->RegisterNatives error\n");
return -1;
}
return JNI_VERSION_1_4;
}
3, jni代码重点介绍
a, 动态库中的接口:
a, 获取dvm的环境变量对象,并测试jni的版本(1.4)
vm->GetEnv((void **)&env, JNI_VERSION_1_4)
该方法是dvm给我们提供的方法,用于获取环境变量对象JNIEnv* env,同时会检测jni的版本是否是1.4, 该方法正常返回JNI_OK
b, 映射表的注册,会调用env->RegisterNatives()方法
env->RegisterNatives(jclass clazz, const JNINativeMethod* methods,jint nMethods)
第一个参数: 将java中native修饰的方法所在的类转换成jni的class类型(jclass)
jclass mycls = env->FindClass("com/hq/JniLedTestActivity");
env->FindClass中的参数是java中的native方法所在包和类的路径,这个是必须要注意的,路径是用Linux常用的路径表示方式,而不是用windows,也不是用java中的点分方式,同时路径必须要指定正确,否则会出错
第二个参数: jni的映射表, 实际是一个结构体数组, 结构体如下所示:
typedef struct {
const char* name; // java的native修饰的方法名
const char* signature; // 方法的返回值和参数的描述,比如(I)I,表示返回值为int,带有一个int型的参 数
void* fnPtr; //c/c++中的函数名
} JNINativeMethod;
第三个参数: 映射表表的成员个数sizeof(myMethods)/sizeof(myMethods[0])
返回: JNI_OnLoad正常 返回JNI_VERSION_1_4, 错误返回一个负数
c, 接下来就是c/c++函数的实现了
static int fd = -1;
jint open_led(JNIEnv *env, jobject thiz)
{
LOGD("----^_^ %s------\n", __FUNCTION__);
fd = open("/dev/led1", O_RDWR);
if(fd < 0)
{
LOGE("open : %s\n", strerror(errno));
return -1;
}
return 0;
}
五,驱动部分
对于不同平台,led的驱动的写法是不一样的,但是基本的写法就是按照字符设备的方式去写,这个算是一个字符设备驱动的简单模板,基于s5pv210, 可以参考一下看看
/*1. 头文件*/
#include<linux/init.h>
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/device.h>
#include<linux/slab.h>
#include<linux/cdev.h>
#include<asm/uaccess.h>
#include<asm/io.h>
static unsigned int led_major=0;
volatile unsigned long *gpc0con = NULL;
volatile unsigned long *gpc0dat = NULL;
struct led_device{
struct class *led_class ; //表示一类设备, 存储某些信息
struct device *led_device ; //表示一个设备
struct cdev *led_cdev;
unsigned int val;
};
struct led_device *s5pv_led_dev;
static ssize_t led_read(struct file *file, char __user *buf, size_t count, loff_t *opps)
{
int ret;
ret = copy_to_user(buf, &s5pv_led_dev->val, count);
if(ret>0)
{
printk(KERN_ERR "copy to user failed!\n");
return ret;
}
printk(KERN_INFO "val=%d\n", s5pv_led_dev->val);
return ret?0:count;
}
static ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *opps)
{
int ret;
/*拷贝成功,返回0; 拷贝失败, 返回没有被拷贝的字节数*/
ret = copy_from_user(&s5pv_led_dev->val, buf, count);
if(ret>0)
{
printk(KERN_ERR "copy from user failed!\n");
return ret;
}
if(s5pv_led_dev->val)
{
/*点亮LED*/
*gpc0dat |= ((0x1<<3)|(0x1<<4));
}
else
{
/*熄灭LED*/
*gpc0dat &= ~((0x1<<3)|(0x1<<4));
}
return ret?0:count;
}
static int led_open(struct inode *inode, struct file *file)
{
/*1. 将物理地址映射为虚拟地址*/
gpc0con = ioremap(0xE0200060, 8);
gpc0dat = gpc0con +1;
/*2. 初始化GPC0_3,4引脚功能为输出*/
*gpc0con &= ~((0xf<<12)|(0xf<<16));
*gpc0con |= ((0x1<<12)|(0x1<<16));
return 0;
}
static int led_close(struct inode *inode, struct file *file)
{
*gpc0con &= ~((0xf<<12)|(0xf<<16));
iounmap(gpc0con);
}
/*硬件操作方法*/
struct file_operations led_fops={
.owner = THIS_MODULE, //当前模块所用
.open = led_open,
.write = led_write,
.read = led_read,
.release = led_close,
};
static void setup_led_cdev(void)
{
/*1. 为cdev结构体分配空间*/
s5pv_led_dev->led_cdev = cdev_alloc();
/*2. 初始化cdev结构体*/
cdev_init(s5pv_led_dev->led_cdev, &led_fops);
/*3. 注册cdev,加载到内核哈希表中*/
cdev_add(s5pv_led_dev->led_cdev, MKDEV(led_major, 0), 1);
}
/*2. 实现模块加载函数*/
static int __init led_init(void)
{
dev_t devno;
int ret;
/*1. 新的申请主设备号的方法*/
if(led_major)
{
/*静态申请*/
devno = MKDEV(led_major, 0);
register_chrdev_region(devno, 1, "led");
}
else
{
/*动态申请*/
alloc_chrdev_region(&devno, 0, 1, "led");
led_major = MAJOR(devno);
}
/*2. 为本地结构体分配空间*/
/*
** param1: 大小
** param2: 标号: GFP_KERNEL--->表示如果分配不成功,则休眠
*/
s5pv_led_dev = kmalloc(sizeof(struct led_device), GFP_KERNEL);
if (!s5pv_led_dev)
{
printk(KERN_ERR "NO memory for malloc!\n");
ret = -ENOMEM;
goto out_err_1;
}
/*3. 构建struct cdev结构体*/
setup_led_cdev();
/*4. 创建设备文件*/
/*
** param1: struct class
** param2: 父类, 一般为NULL
** param3: dev_t ---> 表示一个设备号, 是一个无符号32位整形
** 其中高12位为主设备号, 低20为次设备号
** 如何操作设备号: MKDEV(major, minor)根据主设备号和次设备号组成一个设备号
*/
s5pv_led_dev->led_class = class_create(THIS_MODULE, "led_class");
if (IS_ERR(s5pv_led_dev->led_class)) {
printk(KERN_ERR "class_create() failed for led_class\n");
ret = -EINVAL;
goto out_err_2;
}
s5pv_led_dev->led_device = device_create(s5pv_led_dev->led_class, NULL, MKDEV(led_major, 0), NULL, "led1"); // 创建设备文件/dev/led1
if (IS_ERR(s5pv_led_dev->led_device)) {
printk(KERN_ERR "device_create failed for led_device\n");
ret = -ENODEV;
goto out_err_3;
}
return 0;
out_err_3:
class_destroy(s5pv_led_dev->led_class);
out_err_2:
cdev_del(s5pv_led_dev->led_cdev);
kfree(s5pv_led_dev);
out_err_1:
unregister_chrdev_region(MKDEV(led_major, 0), 1);
return ret;
}
/*3. 实现模块卸载函数*/
static void __exit led_exit(void)
{
unregister_chrdev_region(MKDEV(led_major, 0), 1);
device_destroy(s5pv_led_dev->led_class, MKDEV(led_major, 0));
class_destroy(s5pv_led_dev->led_class);
cdev_del(s5pv_led_dev->led_cdev);
kfree(s5pv_led_dev);
}
/*4. 模块许可声明*/
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");