android jni的编写, 控制led灯

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");








  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
### 回答1: 在Android上通过JNI打开LED,我们可以按照以下步骤进行操作: 1. 首先,我们需要编写一个JNI接口,将其与Java代码连接起来。在JNI接口中,我们可以调用底层C代码来完成对LED控制。 2. 在C代码中,我们可以使用Linux的文件系统接口来控制LED。例如,对于某些设备,LED控制文件路径可能是"/sys/class/leds/led1/brightness"。我们可以通过打开该文件,然后将相应的值写入文件来控制LED的开关状态。 3. 在JNI接口中,我们可以定义一个函数来接收Java代码传递的控制命令,并根据命令调用相应的C函数来操作LED。 4. 在Java代码中,我们需要加载JNI库,并调用JNI接口来控制LED。我们可以使用System.loadLibrary()方法加载JNI库,并通过JNI接口调用相应的函数来控制LED的开关状态。 需要注意的是,具体的实现方法可能因不同的设备而异。特定设备的LED控制方式可能不同,因此需要根据实际情况进行调整。 总结起来,通过JNI打开LED需要编写JNI接口将Java代码与底层C代码连接起来,使用Linux的文件系统接口来控制LED的开关状态,并在Java代码中加载JNI库和调用JNI接口来控制LED的开关状态。 ### 回答2: 要在Android上通过JNI打开LED,需要遵循以下步骤: 1. 创建一个C/C++的JNI接口函数。这个函数将会调用底层的硬件接口以控制LED的打开和关闭。这个函数需要与Java层的代码进行绑定。 2. 在Java层,创建一个Native方法,该方法与JNI接口函数进行绑定。可以使用`native`关键字来定义这个方法。 3. 使用Java Native Interface(JNI)工具将Java层的代码与C/C++层的代码进行连接。创建一个JNI头文件,并将C/C++代码实现功能。 4. 在C/C++层的代码中,使用底层硬件接口控制LED的打开和关闭。这里需要根据具体的硬件和设备进行不同的实现。 5. 在Android项目中,将C/C++代码编译成共享库。可以使用Android的NDK工具来进行编译。编译成功后,可以将.so文件复制到Android项目的相应目录中。 6. 在Java层的代码中,加载编译好的共享库,并调用Native方法来打开LED。可以使用`System.loadLibrary()`方法进行加载。 7. 最后,在Android设备上运行应用程序,调用对应的Java方法,即可通过JNI打开LED。 总体来说,上述步骤涵盖了在Android上通过JNI打开LED所需的基本操作。但是由于涉及到底层硬件接口的控制,具体的实现方式还需要根据所用的硬件设备和操作系统进行调整和细化。 ### 回答3: 要在Android中使用JNI打开LED,需要以下步骤: 1. 创建一个Java类来管理JNI的调用。在这个类中,添加native方法声明。例如,创建一个名为LedControl的类,添加一个native方法openLed(),声明如下: ```java public class LedControl { public native void openLed(); } ``` 2. 生成.h头文件。使用命令行工具在项目的JNI目录下运行以下命令: ```shell javah -jni com.example.LedControl ``` 这将为LedControl类生成一个.h头文件,用于在C/C++代码中引用该类。 3. 编写C/C++代码。在C/C++代码中,实现openLed()方法以打开LED。这需要根据所使用的硬件平台和设备进行编码。示例代码如下: ```c #include <jni.h> JNIEXPORT void JNICALL Java_com_example_LedControl_openLed(JNIEnv *env, jobject obj) { // 在这里编写打开LED的逻辑代码 // 例如,使用GPIO库函数控制GPIO引脚输出为高电平以打开LED // ... } ``` 4. 在Android.mk文件中添加C/C++源文件。在jni目录下的Android.mk文件中,将C/C++源文件添加到LOCAL_SRC_FILES列表中: ```makefile LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := ledcontrol LOCAL_SRC_FILES := LedControl.c include $(BUILD_SHARED_LIBRARY) ``` 5. 编译和构建项目。在项目的根目录下运行以下命令,编译并构建项目: ```shell ndk-build ``` 这将编译C/C++代码并生成共享库。 6. 调用JNI方法。在Java代码中调用native方法,实现与C/C++代码的交互。例如,在Activity中调用openLed()方法: ```java LedControl ledControl = new LedControl(); ledControl.openLed(); ``` 这将通过JNI调用C/C++代码中的openLed()方法,从而控制LED的打开。 需要注意的是,以上提供的只是一个简单的示例,具体的实现方式取决于硬件平台、设备和使用的库函数。在实际应用中,需要根据相关文档或资料进行详细的配置和编码。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旗浩QH

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值