[Android]Android 下实现点亮 Led

Android 下的实现点亮 led

1 准备驱动

1.1 修改设备树

在设备树中添加节点,在 / 节点下添加 led_test 节点,在 &iomuxc 节点下添加 led_test 的 pinctrl 组。

/* 
 * vendor/nxp-opensource/kernel_imx/arch/arm64/boot/dts/freescale/imx8mp-evk.dts
 */

led_test {
    compatible = "devled";
    pinctrl-names = "default";
    status = "okay";
    pinctrl-0 = <&pinctrl_led_test>;

    led {
        gpios =<&gpio5 11 GPIO_ACTIVE_HIGH>;
        default-state = "off";
    };
};

pinctrl_led_test: led_test_grp {
    fsl,pins = <
        	MX8MP_IOMUXC_ECSPI2_MOSI__GPIO5_IO11	0x17059
        >;
};

1.2 添加驱动代码

添加简单的 led 驱动程序。

/* 
 * vendor/nxp-opensource/kernel_imx/drivers/char/led_drv.c
 */

#include <linux/init.h>                  
#include <linux/module.h>          
#include <linux/errno.h>             
#include <linux/types.h>                  
#include <linux/kernel.h>         
#include <linux/fs.h>                  
#include <linux/cdev.h>           
#include <linux/device.h>               
#include <linux/of_gpio.h>             
#include <linux/platform_device.h>    
#include <linux/gpio.h>
#include <linux/of_device.h>
#include <linux/slab.h>

#define LED_OFF                     0       
#define LED_ON                      1

static int dev_major = 0;

/* 存放led信息的结构体 */
struct gpio_led_data 
{
    char                    name[16];       // 设备名字
    int                     pin;            // gpio编号
    int                     active;         // 控制亮灭的标志
};

/* 存放led的私有属性 */
struct gpio_leds_priv 
{
    struct cdev             cdev;           // cdev结构体
    struct class            *dev_class;     // 自动创建设备节点的类
    int                     num_leds;       // led的数量
    struct gpio_led_data    led;            // 存放led信息的结构体数组
};

/* 为led私有属性开辟存储空间的函数 */
static inline int sizeof_gpio_leds_priv(int num_leds)
{
    return sizeof(struct gpio_leds_priv) + (sizeof(struct gpio_led_data) * num_leds);
}

/* 解析设备树并初始化led属性 */
int parser_dt_init_led(struct platform_device *pdev)
{
    struct device_node *np = pdev->dev.of_node;     // 当前设备节点
    struct device_node *child;                      // 当前设备节点的子节点
    struct gpio_leds_priv *priv;                    // 存放私有属性
    int num_leds, gpio;                             // led数量和gpio编号                

    /* 获取该节点下子节点的数量 */
    num_leds = 1;
    if(num_leds <= 0) 
    {
        dev_err(&pdev->dev, "fail to find child node\n");
        return -EINVAL;
    }
    
    /* devm_kzalloc是内核内存分配函数,跟设备有关的,驱动卸载时,内存会被自动释放
     * void * devm_kzalloc (struct device * dev, size_t size, gfp_t gfp)
     * dev:申请内存的目标设备 size:申请的内存大小 gfp:申请内存的类型标志
     * GFP_KERNEL是分配内核空间的内存时的一个标志位,无内存可用时可引起休眠
     */
    priv = devm_kzalloc(&pdev->dev, sizeof_gpio_leds_priv(num_leds), GFP_KERNEL);
    if (!priv)
    {
        return -ENOMEM;
    }
    
    priv->num_leds = 1;
    
    /* 找到子节点并传给child */
    child = of_get_child_by_name(np, "led");


    /* 解析dts并且获取gpio口,函数返回值就得到gpio号,并且读取gpio现在的标志 */
    gpio = of_get_named_gpio(child, "gpios", 0);
    
    /* 将子节点的名字,传给私有属性结构体中的led信息结构体中的name属性 */
    strncpy(priv->led.name, child->name, sizeof(priv->led.name)); 
    
    /* 将gpio编号和控制亮灭的标志传给结构体
     * active属性,1代表亮,0代表灭,初始属性为灭
     */
    priv->led.active = 0; 
    priv->led.pin = gpio; 
    
    /* 申请gpio口,相较于gpio_request增加了gpio资源获取与释放功能 */
    gpio_request(priv->led.pin, "led");
    
    /* 设置gpio为输出模式,并设置初始状态 */
    gpio_direction_output(priv->led.pin, 1);
    
    /* 将led的私有属性放入platform_device结构体的device结构体中的私有数据中 */
    platform_set_drvdata(pdev, priv);
    
    return 0;

}

static int led_open(struct inode *inode, struct file *file)
{
    struct gpio_leds_priv *priv;

    priv = container_of(inode->i_cdev, struct gpio_leds_priv, cdev);
    file->private_data = priv;
    return 0;

}

static int led_release(struct inode *inode, struct file *file)
{
    return 0;
}

static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    struct gpio_leds_priv *priv;
    priv = file->private_data;

    switch (cmd)
    {
        case LED_ON:
            printk("LED_ON:\n");
            printk("gpio%d state: %d\n", priv->led.pin, gpio_get_value(priv->led.pin));
            gpio_set_value(priv->led.pin, 1);
            priv->led.active = 1;
            printk("gpio_pin: %d\n", priv->led.pin);
            break;
    
        case LED_OFF:
            printk("LED_OFF:\n");
            printk("gpio%d state: %d\n", priv->led.pin, gpio_get_value(priv->led.pin));
            gpio_set_value(priv->led.pin, 0);
            priv->led.active = 0;
            printk("gpio_pin: %d\n", priv->led.pin);
            break;
    
        default:
            printk(KERN_ERR "Ioctl command=%d can't be supported\n", cmd);
            break;
    }
    
    return 0;

}

static struct file_operations led_fops = 
{
    .owner = THIS_MODULE,
    .open = led_open,
    .release = led_release,
    .unlocked_ioctl = led_ioctl,
};

static int gpio_led_probe(struct platform_device *pdev)
{
    struct gpio_leds_priv   *priv;          // 临时存放私有属性的结构体
    struct device           *dev;           // 设备结构体
    dev_t                   devno;          // 设备的主次设备号
    int                     i, rv = 0;      

    /* 1)解析设备树并初始化led状态 */
    rv = parser_dt_init_led(pdev);
    if( rv < 0 )
            return rv;
    
    /* 将之前存入的私有属性,放入临时的结构体中 */
    priv = platform_get_drvdata(pdev);
    
    /* 2)分配主次设备号 */
    if (0 != dev_major) 
    {   
        /* 静态分配主次设备号 */
        devno = MKDEV(dev_major, 0); 
        rv = register_chrdev_region(devno, priv->num_leds, "devled"); 
    }   
    else 
    {   
        /* 动态分配主次设备号 */
        rv = alloc_chrdev_region(&devno, 0, priv->num_leds, "devled"); 
        dev_major = MAJOR(devno); 
    }   
    if (rv < 0) 
    {   
        dev_err(&pdev->dev, "major can't be allocated\n"); 
        return rv; 
    }   
    
    /* 3)分配cdev结构体 */
    cdev_init(&priv->cdev, &led_fops);
    priv->cdev.owner  = THIS_MODULE;
    
    rv = cdev_add (&priv->cdev, devno , priv->num_leds); 
    if( rv < 0) 
    {
        dev_err(&pdev->dev, "struture cdev can't be allocated\n");
        goto undo_major;
    }
    
    /* 4)创建类,实现自动创建设备节点 */
    priv->dev_class = class_create(THIS_MODULE, "led");
    if( IS_ERR(priv->dev_class) ) 
    {
        dev_err(&pdev->dev, "fail to create class\n");
        rv = -ENOMEM;
        goto undo_cdev;
    }
    
    /* 5)创建设备 */
    for(i=0; i<priv->num_leds; i++)
    {
        devno = MKDEV(dev_major, i);
        dev = device_create(priv->dev_class, NULL, devno, NULL, "led");
        if( IS_ERR(dev) ) 
        {
            dev_err(&pdev->dev, "fail to create device\n");
            rv = -ENOMEM;
            goto undo_class;
        }
    }
    
    printk("success to install driver[major=%d]!\n", dev_major);
    
    return 0;

undo_class:
    class_destroy(priv->dev_class);

undo_cdev:
    cdev_del(&priv->cdev);

undo_major:
    unregister_chrdev_region(devno, priv->num_leds);

    return rv;

}

static int gpio_led_remove(struct platform_device *pdev)
{
    struct gpio_leds_priv *priv = platform_get_drvdata(pdev);
    int i;
    dev_t devno = MKDEV(dev_major, 0);

    /* 注销设备结构体,class结构体和cdev结构体 */
    for(i=0; i<priv->num_leds; i++)
    {
        devno = MKDEV(dev_major, i);
        device_destroy(priv->dev_class, devno);
    }
    class_destroy(priv->dev_class);
    
    cdev_del(&priv->cdev); 
    unregister_chrdev_region(MKDEV(dev_major, 0), priv->num_leds);
    
    /* 将led的状态设置为灭 */
    for (i = 0; i < priv->num_leds; i++) 
    {
        gpio_set_value(priv->led.pin, ~priv->led.active);
    }   
    
    printk("success to remove driver[major=%d]!\n", dev_major);
    return 0;

} 

/* 匹配列表 */
static const struct of_device_id gpio_led_of_match[] = {
	{ .compatible = "devled" },
	{}
};

MODULE_DEVICE_TABLE(of, gpio_led_of_match);

/* platform驱动结构体 */
static struct platform_driver gpio_led_driver = {
	.driver		= {
		.name	= "devled",			        	// 无设备树时,用于设备和驱动间的匹配
		.of_match_table	= gpio_led_of_match,     // 有设备树后,利用设备树匹配表
	},
	.probe		= gpio_led_probe,
	.remove		= gpio_led_remove,
};

module_platform_driver(gpio_led_driver);

MODULE_LICENSE("GPL");

在同一文件夹的 Makefile 中添加编译支持,之后可以编译成 .ko 文件。

# vendor/nxp-opensource/kernel_imx/drivers/char/Makefile
obj-m += led_drv.o

然后执行如下命令,重新编译 kernel,并重新编译安卓镜像。

android@ubuntu22:~/android/android_build$  source build/envsetup.sh
android@ubuntu22:~/android/android_build$  lunch 62
android@ubuntu22:~/android/android_build$ ./imx-make.sh kernel
android@ubuntu22:~/android/android_build$ ./build.sh -b && sudo ./build.sh -g

最后可以在 out 目录中找到编译好的 led_drv.ko 文件,用 sz 命令将 .ko 文件放到本地,之后使用 adb 传输到开发板上。

android@ubuntu22:~/android/android_build/out/target/product/evk_8mp/obj/KERNEL_OBJ/drivers/char$ sz led_drv.ko 

1.3 将 .ko 文件传入开发板

首先,需要在 windows 下安装 adb,安装过程不再赘述,安装完成后打开 cmd,执行如下命令。

C:\Users\Yaqlng>adb push C:\Users\Yaqlng\Desktop\imx8mp\led_drv.ko /sdcard/test

然后,就可以在开发板上 insmod led_drv.ko 文件,并且设置 /dev/led 设备节点的权限为 777。

evk_8mp:/sdcard/test $ ls
led_drv.ko
evk_8mp:/sdcard/test $ su
evk_8mp:/sdcard/test $ insmod led_drv.ko
[  893.765281][ T2055] success to install driver[major=503]!
evk_8mp:/sdcard/test $ chmod 777 /dev/led

2 Android 中使用驱动

2.1 用 java 直接调用 c 库函数

在这里插入图片描述

在 Android 中,位于顶层的应用程序一般都用 Java 语言进行开发的,想要使用驱动则需要调用 C/C++ 语言,也称作 native 语言。这时,需要引入 JNI 全称为 Java Native Interface,它提供了若干的 API 实现了 Java 和 native 语言的通信,JNI 可以做到使 Java 程序中的函数调用 native 语言编写的函数,也可以使 native 中的函数调用 Java 层的函数。划分 native 和 Java 两部分,其原因是 native 语言编写的库具有更好的性能,并且在 Java 诞生时,许多的程序和库都是 native 语言编写的,JNI 的引入则可以重复利用这些 native 代码,接下来提供一个在 Android Studio 实现 JNI 的简单例子。

2.1.1 创建 Android Studio 工程

点击 File,New Project,然后先选择一个空白的 Activity。

在这里插入图片描述

然后输入工程名,一定要选择语言为 Java。

在这里插入图片描述

2.1.2 为工程添加 native-lib 并新建 Java 类

先添加 C++ 文件,如下图所示。

在这里插入图片描述

添加后会出现下如下文件夹,JNI 函数之后会生成到在合格 native-lib.cpp 中。

在这里插入图片描述

然后,添加名为 HardControl 的 Java 类。

在这里插入图片描述

在 HardConctrl.java 中添加如下内容。

package com.example.app_02_jni_demo;

public class HardControl {
    public static native int ledCtrl(int which, int status);
    public static native int ledOpen();
	
    /* 用  System.loadLibrary 函数,加载 C++ 库,也就是 native-lib 编译生成的库 */
    static {
        System.loadLibrary("app_02_jni_demo");
    }
}

选中函数,然后利用 Android Studio 可以自动为这个 native 函数创建对应的 JNI 函数。

在这里插入图片描述

然后,我们就可以在 native-lib 中看到自动添加的函数,并修改其中的内容如下。

#include <jni.h>
#include <string>
#include <android/log.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <sys/select.h>

int fd;

/* 这种 JNI 函数是使用绝对路径的方式来使 native-lib 中的 JNI 函数和 HardConctrl.java 中的函数对应 
 * 因此,函数名显得很冗长,暂时先使用这种注册方式
 */

/* ledOpen 中主要就是 open 设备节点
 * ledCtrl 的作用就是控制 led 亮灭
 */
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_app_102_1jni_1demo_HardControl_ledOpen(JNIEnv *env, jclass clazz) {
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledOpen");
    fd = open("/dev/led", O_RDWR, 0755);
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "fd = %d", fd);
    return 0;
}

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_app_102_1jni_1demo_HardControl_ledCtrl(JNIEnv *env, jclass clazz, jint which, jint status) {
    __android_log_print(ANDROID_LOG_DEBUG, "LEDDemo", "native ledCtrl : %d, %d", which, status);
    ioctl(fd, status);
    return 0;
}
2.1.3 修改 MainActivity.java 并在 activity_main.xml 添加按钮和标签
package com.example.app_02_jni_demo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.View;
import android.widget.Button;

import com.example.app_02_jni_demo.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    private boolean ledTag = false;
    private Button ledButton = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
	    
        /* 按下按钮后,新建一个 HardControl 类的对象,然后利用这个对象调用其中声明的 native 函数
         * 然后,通过这个 native 函数调到 native-lib 中声明的 JNI 函数
         */
        ledButton = (Button) findViewById(R.id.BUTTON);

        ledButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                HardControl hardControl = new HardControl();
                hardControl.ledOpen();

                ledTag = !ledTag;
                if(ledTag)
                {
                    ledButton.setText("OFF");
                    hardControl.ledCtrl(1, 1);
                }
                else
                {
                    ledButton.setText("ON");
                    hardControl.ledCtrl(1, 0);
                }
            }
        });
    }
}

xml 文件中,主要就是添加了这个按钮,还添加了一个标签。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="led demo"
        />
    <Button
        android:id="@+id/BUTTON"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ON"
        />

</LinearLayout>
2.1.4 连接开发板,运行 APP

连接开发板后,可以在 Android Studio 右上角看到我们所连接的真实设备,点击运行即可将 APP 加载到开发板上。

在这里插入图片描述

在这里插入图片描述

APP 运行如上图所示,点击按钮可以看到 led 灯的亮灭。

2.2 硬件访问服务

通过 JNI 访问操作简单,但是这只适用于单个 APP 访问硬件的情况,如果是多个 APP 访问,会造成许多麻烦,例如多个 APP 同时对一个设备节点进行 open 或者 write 会造成数据混乱,因此建议采用通过 service manager 访问,APP 只需要给 service manager 发送请求,service manager 对硬件的访问进行统一管理。

除此之外,是关于 system server,system server 是系统用来启动 service 的入口,它会启动我们在系统中所需要的一切 service,因此我们之后会将我们所写的服务注册到 system server 中,这样 system server 启动后,我们的服务也会启动。

总体流程是 APP 向 service manager 查询获取一个服务,然后 service manager 再让 system server 去使用服务,并调用 Java 以下的 native 语言所编写的内容,接下来提供一个安卓服务的添加流程。

2.1.1 添加 AIDL

AIDL 是 Android 接口定义语言,可以使用它定义客户端与服务器端通信的接口,在 Android 中,进程之间无法共享内存,不同进程之间的通信一般使用 AIDL 来处理,因此我们可以把我们要添加的服务看作 server,我们在 Android 上编写的 APP 看作 client,这样 APP 就可以调用服务中实现的函数。

在 AIDL 中只添加一个接口也就是 ledCtrl 用于控制 led 亮灭即可,先创建 ledmanager 目录,然后在里面添加 AIDL。

/*
 * frameworks/base/core/java/android/app/ledmanager/ILedManager.aidl
 */

// ILedManager.aidl
package android.app.ledmanager;

// Declare any non-default types here with import statements

interface ILedManager {
	/**
   	 * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
     int ledCtrl(int which, int status);
}

然后重新编译系统,会生成一个和 AIDL 同名的 ILedManager.java 文件,里面实现了 Stub 和 Proxy 类,也就是实现进程通信所需要的类。

首先,AIDL 的具体实现是典型的 C/S 模型,模型实例如下图所示。

在这里插入图片描述

其中,Stub 就是服务的实体,Proxy 是服务的代理,因此我们写的服务需要继承 Stub,而在 APP 一端也就是 client 一端则需要通过一些函数获取到 Proxy 实体,这样 APP 才能使用服务中的函数,暂时对于 AIDL 只需要知道这一点,AIDL 实现的内部机制和数据传输过程之后再做详细的分析,暂时只需要知道 APP 和 服务之间的进程间通信依靠 Stub 和 Proxy 的通信,Stub 在服务一段,Proxy 在 APP 一端。

2.2.2 在 context.java 中添加服务名

这个服务名的添加将相当于一个跨文件的全局变量,之后想要用到这一个名字就可以直接使用 Context.LEDMANAGER_SERVICE。

/*
 * frameworks/base/core/java/android/content/Context.java
 */

public static final String LEDMANAGER_SERVICE = "ledmanager";

@StringDef(suffix = { "_SERVICE" }, value = {
	...
	LEDMANAGER_SERVICE,
	...
}
2.2.3 新建 LedManagerService.java
package com.android.server;

import android.app.ledmanager.ILedManager;
import android.os.RemoteException;
import android.util.Log;

public class LedManagerService extends ILedManager.Stub {
    
    @Override
    public int ledCtrl(int which, int status) throws RemoteException {
        int ret = native_ledOpen();
        if(ret != 0){
            return ret;
        }
        
        return native_ledCtrl(which, status);
    }
    
    public static native int native_ledOpen();
    private static native int native_ledCtrl(int which, int status); 

}

这个文件中主要就是实现之前 AIDL 定义的接口,并且我们创建的 LedManagerService 需要继承之前 AIDL 中生成的 Java 文件中的 Stub,可以看到 ledCtrl 函数中主要调用了 native_ledOpen 和 native_ledCtrl 两个函数,这两个函数在这里暂时声明,之后会关联到 JNI 中注册的函数,实现的就是打开设备节点,然后控制 led 的亮灭。

2.2.4 新建 LedManager.java

由于之后我们需要将我们所写的服务注册到 system server 中,因此这个文件就是用于为这个注册过程提供函数,这个函数是仿造源码中的其他服务而写,具体实现过程还没有研究。

package android.app.ledmanager;

import android.annotation.SystemService;
import android.compat.annotation.UnsupportedAppUsage;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ledmanager.ILedManager;
import android.content.Context;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.util.Singleton;
import android.util.Log;

@SystemService(Context.LEDMANAGER_SERVICE)
public class LedManager {
	private static String TAG = "LedManager";
    private ILedManager mService;
	private static LedManager sInstance;

	/**
	*@hide
	*/
	public  LedManager(ILedManager service){
		mService=service;
	}
	
	/**
	*@hide
	*/
	@NonNull
	@UnsupportedAppUsage
	public static LedManager getInstance() {
		synchronized (LedManager.class) {
	    if (sInstance == null) {
	
	        try {
	            IBinder b = ServiceManager.getServiceOrThrow(Context.LEDMANAGER_SERVICE);
	            sInstance = new LedManager(ILedManager.Stub.asInterface(ServiceManager.getServiceOrThrow(Context.LEDMANAGER_SERVICE)));
	        } catch (ServiceNotFoundException e) {
	            throw new IllegalStateException(e);
	        }
	
	    }
	    return sInstance;
	}
	}
	
	public int ledCtrl(@Nullable int which, int status){
		try{
		   return mService.ledCtrl(which, status);
		} catch (RemoteException e){
		   throw e.rethrowFromSystemServer();
		}
	}

}     
2.2.5 将服务添加到 service manager

调用 addService 方法,将服务添加到 service manager 中,之后 APP 可以调用 getService 获取服务。

t.traceBegin("StartLedManagerService");
try {
	ServiceManager.addService(Context.LEDMANAGER_SERVICE, new LedManagerService());
} catch (Throwable e) {
	Slog.e(TAG, "Failure starting LedManagerService", e);
}
t.traceEnd();
2.2.6 将服务注册到 system server 中

先引入服务类,仿造其他服务添加如下内容。

import android.app.ledmanager.LedManager;

registerService(Context.LEDMANAGER_SERVICE, LedManager.class,
                new CachedServiceFetcher<LedManager>() {
            @Override
            public LedManager createService(ContextImpl ctx) throws ServiceNotFoundException {
                return LedManager.getInstance();
            }});
2.2.7 添加 selinux 权限

selinux 并非 Andriod 驱动开发的重点,因此这里不再赘述。

2.2.8 查看服务

重新编译安卓镜像,并将镜像烧录到开发板上,执行以下命令可以看到我们添加的服务。

evk_8mp:/ $ service list | grep led                                            
100     ledmanager: [android.app.ledmanager.ILedManager]
2.2.9 编写应用程序

界面和之前的 APP 程序相同,但是需要添加获取服务的部分。

我们想要使用已经编译到镜像中的类,有两种方法,第一种是在编译好的源码中找到 Framework 的 jar 包,并将 jar 包导入到我们在 Android Studio 中的工程中,第二种方法是利用 Android 的反射机制来获取类和方法。

这里的例子,我们使用的是反射的方法来获取类和方法,下一个例子使用 jar 包的方式。

2.2.9.1 反射机制

反射是指在运行状态时,对于任何一个类,都能够知道它的所有属性和方法,对于任何一个对象都能调用它的任何一个方法和属性。放射是一种动态机制,运用反射可以在运行时加载,使用编译期间完全未知的类,也就是说 Java 程序可以加载在运行时才得知名称的类,获取其完整的构造方法,并生成其对象实体,对其属性设值或者使用其成员方法。

反射机制涉及到了动态编译和静态编译的概念,静态编译就是在编译时确定类型,绑定对象,而动态编译是指运行时确定类型,绑定对象。反射机制则数据动态编译,其优点在于最大限度发挥了 Java 的灵活性,降低了类之间的耦合性。

2.2.9.2 应用实例
package com.example.app_01_led_demo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.View;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.Toast;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class MainActivity extends AppCompatActivity {
    private boolean ledTag = false;
    private Button ledButton = null;
	
    /* 通过反射机制获取 getService 方法,根据类的路径和方法名
     * 这个方法属于 ServiceManger 这个类
     * 我们之前调用了 addService,将我们所写的服务纳入了 service manager 的管理
     * 这里 client 端也就是 APP 通过 getService 获取服务
     */
    Method getService;
    {
        try {
            getService = Class.forName("android.os.ServiceManager").getMethod("getService", String.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    /* 调用 getService,invoke 就是使用这个函数
     * 获取到的就是我们所需要的 led 服务的代理类
     * 通过代理类与服务实体 Stub 之间的通信,完成 APP 对服务的调用
     */
    Object ledManagerService;
    {
        try {
            ledManagerService = getService.invoke(null, "ledmanager");
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    
    /* 获取 asInterface 方法
     * 这个方法用于将服务的代理类转化为接口类接口类
     * 也就是利用将 BinderProxy 类转化为 ILedManager 类
     * 这样就可以直接使用接口了
     */
    Method asInterface;
    {
        try {
            asInterface = Class.forName("android.app.ledmanager.ILedManager$Stub").getMethod("asInterface", IBinder.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    /* 通过 asInterface 这个方法构造接口类 */
    Object proxy;
    {
        try {
            proxy = asInterface.invoke(null, ledManagerService);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
    
    /* 通过 Proxy 这个类获取到服务中的 ledCtrl 方法 */
    Method ledCtrl;
    {
        try {
            ledCtrl = Class.forName("android.app.ledmanager.ILedManager$Stub$Proxy").getMethod("ledCtrl", int.class, int.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    
        ledButton = (Button) findViewById(R.id.BUTTON);
    	
        /* 通过按钮实现 led 的亮灭 */
        ledButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
    
                ledTag = !ledTag;
                if(ledTag)
                {
                    ledButton.setText(R.string.off);
                    
                    try {
                     	ledCtrl.invoke(proxy, 1, 1);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
                else
                {
                    ledButton.setText(R.string.on);
                    
                    try {
                    	ledCtrl.invoke(proxy, 1, 0);
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}

运行 APP,可以得到与之前相同的实验现象。

2.3 利用 AIDL 实现 HAL 层

在之前的硬件访问服务中,是使用 JNI 直接调用 C 函数从而使用驱动,但规范的流程应该是对底层的 open,read,write,ioctl 等函数进行封装,封装成硬件抽象层也就是 HAL 层,再用 JNI 调用 HAL 中提供的接口,从而调用底层的 open,read,write,ioctl。

但经过查阅资料,HAL 层的封装已经不是简单的将 open,read,write,ioctl 用 C 语言直接封装成 HAL,而是引入了新的服务 HIDL,将对硬件的操作放入这个 HIDL 服务中,这样我们所写的 led 服务可以通过 IPC 也就是进程间通信来获取 HIDL 中对硬件操作的函数。

HIDL 是 Android 8 之后加入的新特性,设计目的是将 Framework 层和 HAL 层分离,进行更新时直接 Framework 层覆盖更新,而不需要对 HAL 进行编译,HAL 的部分放在 /vendor 分区中,由 SOC 制造商或者设备供应商来构建。

但是,在 Android 12 中,我们添加自己的 HIDL 后,重新编译镜像会发现如下警告信息。

No more HIDL interfaces can be added to Android. Please use AIDL.

经过查阅资料,发现 HIDL 也不再推荐使用,从 Android 11 开始,HAL 应该使用 AIDL 来实现,AIDL 实现的 HAL 性质与 HIDL 类似,也将 Framework 层和 HAL 层相互分离。

2.3.1 引入 AIDL for HALs

Google 在 Android 11 引入了 AIDL for HALs,旨在替代原来的 HIDL 的作用,其原因在于,AIDL 具有稳定性支持,可以用单一的 IPC 方式从 HAL 到 Framework 进程或者是应用进程。

AIDL 更成熟,使用更广泛,HAL 层也使用 AIDL 方式,那么就可以直接从应用进程调用到 HAL 进程,以前的 HIDL 实现应用进程访问 HAL 服务需要 system server 作为中介,其区别如下。

在这里插入图片描述

2.3.2 AIDL for HALs 实现流程

首先需要创建的文件和文件夹总目录如下。

android@ubuntu22:~/android/android_build/hardware/interfaces$ tree led
led
└── aidl
    ├── aidl_api
    │   └── android.hardware.led
    │       └── current
    │           └── android
    │               └── hardware
    │                   └── led
    │                       └── ILed.aidl
    ├── android
    │   └── hardware
    │       └── led
    │           └── ILed.aidl
    ├── Android.bp
    └── default
        ├── Android.bp
        ├── android.hardware.led-service.rc
        ├── android.hardware.led-service.xml
        ├── Led.cpp
        ├── Led.h
        └── main.cpp

其中 aidl_api 文件夹是编译生成的,除了这个文件夹的其他内容都需要自己添加。

2.3.2.1 创建 ILed.aidl 和 Android.bp
/* 
 * hardware/interfaces/led/aidl/android/hardware/led/ILed.aidl
 */

package android.hardware.led;

@VintfStability
interface ILed {
    int ledCtrl(int which, int status);
}

其中,VintfStability 是因为 HAL 层的 AIDL 要求是 Stable AIDL,和 Framework 层的 AIDL 不同。

/*
 * hardware/interfaces/led/aidl/Android.bp
 */
package {
    default_applicable_licenses: ["hardware_interfaces_license"],
}

/* 用 AIDL 实现的 HAL 要求,每一个类定义需要带 @VintfStability
 * aidl_interface 声明需要包含 stability: "vintf"
 * 此外,为最大限度提供代码可移植性官方建议停用 C++ 后端
 * backend 中设置 java 的 sdk_version,这样可以编译出该服务的 jar 包以供 APP 使用
 */

aidl_interface {
   name: "android.hardware.led",
   vendor: true,
   srcs: ["android/hardware/led/*.aidl"],
   stability: "vintf",
   owner: "linke",
   backend: {
        cpp: {
            enabled: false,
        },
        java: {
            sdk_version: "module_current",
        },
   },
}

添加了 Android.bp 后编译这个 AIDL。

android@ubuntu22:~/android/android_build$  m android.hardware.led-update-api
android@ubuntu22:~/android/android_build$  mmm /hardware/interface/led

找到生成的文件如下。

android@ubuntu22:~/android/android_build/out/soong/.intermediates/hardware/interfaces/led/aidl/android.hardware.led-V1-ndk_platform-source/gen/include/aidl/android/hardware/led$ ls
BnLed.h  BpLed.h  ILed.h

这其中有三个文件,均由 C++ 编写,这也是使用 AIDL 的原因,AIDL 已经支持编译生成 C++ 文件,直接可以调用 C 函数。其中 BnLed 类似于之前 Java 中的 Stub 服务实体类,BpLed 类似于之前 Java 中的 Proxy 类,ILed 就是所定义的接口,打开 ILed.h 找到我们需要实现的接口,也就是 ledCtrl。

/*
 * out/soong/.intermediates/hardware/interfaces/led/aidl/
 * android.hardware.led-V1-ndk_platform- source/gen/include/aidl/android/hardware/led/ILed.h
 */

virtual ::ndk::ScopedAStatus ledCtrl(int32_t in_which, int32_t in_status, int32_t* _aidl_return) = 0;
2.3.2.2 创建 Led.cpp 和 main.cpp

其中,Led.cpp 用于实现 AIDL 中声明的接口,main.cpp 用于添加服务到 service manager。

和 Java 实现服务类似,我们添加的服务类需要继承 BnLed 也就是服务实体类,并重载它的接口函数。

/*
 * hardware/interfaces/led/aidl/default/Led.h
 */

#pragma once

#include <aidl/android/hardware/led/BnLed.h>

namespace aidl {
namespace android {
namespace hardware {
namespace led {

class Led : public BnLed {
    public:
        ndk::ScopedAStatus ledCtrl(int32_t in_which, int32_t in_status, int32_t* _aidl_return) override;
};

}  // namespace led
}  // namespace hardware
}  // namespace android
}  // namespace aidl
/*
 * hardware/interfaces/led/aidl/default/Led.cpp
 */

#include <utils/Log.h>
#include <iostream>
#include "Led.h"

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>

namespace aidl {
namespace android {
namespace hardware {
namespace led {

ndk::ScopedAStatus Led::ledCtrl(int32_t in_which, int32_t in_status, int32_t* _aidl_return)
{
    int32_t fd, ret;
    in_which = 0;

    fd = open("/dev/led", O_RDWR);
    if (fd < 0)
    {
    	*(_aidl_return) = -1;
    }
    
    ret = ioctl(fd, in_status);
    if (ret < 0)
    {
        *(_aidl_return) = -2;
    }
    
    return ndk::ScopedAStatus::ok();
}

}  // namespace led
}  // namespace hardware
}  // namespace android
}  // namespace aidl
/*
 * hardware/interfaces/led/aidl/default/main.cpp
 */
#include <android-base/logging.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
#include "Led.h"

using ::aidl::android::hardware::led::Led;
using std::string_literals::operator""s;

int main() {
    /* Enable vndbinder to allow vendor-to-venfor binder call
     * 使用vnbinder的配置
     */ 
    android::ProcessState::initWithDriver("/dev/vndbinder");

    /* vnbinder的线程池独立,需要单独配置 */  
    ABinderProcess_setThreadPoolMaxThreadCount(0); 
    ABinderProcess_startThreadPool();
    
    std::shared_ptr<Led> led = ndk::SharedRefBase::make<Led>();
    const std::string desc = Led::descriptor + "/default"s;
    
    if (led != nullptr) {
        if(AServiceManager_addService(led->asBinder().get(), desc.c_str()) != STATUS_OK) {
            ALOGE("Failed to register ILed service");
            return -1;
        }
    } else {
        ALOGE("Failed to get ILed instance");
        return -1;
    }
    
    ALOGD("ILed service starts to join service pool");
    ABinderProcess_joinThreadPool();
    
    return EXIT_FAILURE;  // should not reached

}
2.3.2.3 添加启动文件

服务的 .rc 文件语法大致如下

/* 其中 <name> 是指服务的名称,<pathname> 表示服务的执行文件路径
 * <option> 选项,表示对这个服务的一些配置
 */
service <name> <pathname> [ <argument> ]*
   <option>
   <option>
   ...

前面的 Android.bp 中已经指定了启动文件,这里实现这个启动文件也就是 android.hardware.led-service.rc。

/*
 * hardware/interfaces/led/aidl/default/android.hardware.led-service.rc
 */

/* 因此该文件中,服务名为 android.hardware.led-service
 * 执行路径为 /vendor/bin/hw/android.hardware.led-service
 * class hal,表示所属 class 是 hal,相当于一个归类
 * user 和 group 就是启动服务的用户和组
 */
service android.hardware.led-service /vendor/bin/hw/android.hardware.led-service
    interface aidl android.hardware.led.ILed/default
    class hal
    user system
    group system
2.3.2.4 添加 xml 文件
/*
 * hardware/interfaces/led/aidl/default/android.hardware.led-service.xml
 */

<manifest version="1.0" type="device">
    <hal format="aidl">
        <name>android.hardware.led</name>
        <fqname>ILed/default</fqname>
    </hal>
</manifest>

我们在先前的 Android.bp 中指定了该文件。

vintf_fragments: ["android.hardware.led-service.xml"]

首先,我们要明确的是 AIDL for HALs 以及上一个版本的 HIDL 都有相同的特性,就是解除了 Framework 和 vendor 之间的耦合,也就是 Framework 不依赖于 vendor 的代码。为了实现 Framework 和 vendor 解耦,Android 引入了 VINTF object,全称为 Vendor Interface Object。这个 VINTF object 用于 Framework 告知 vendor 其想要什么,vendor 告知 framework 它有什么。

在 VINTF object 中,牵扯到两个概念,分别是 DM,Device Manifest 和 FCM,Framework Compatibility Matrixes,分别叫做设备清单和 Framework 兼容矩阵。

一个 VINTF object 的数据就来源于设备清单和 Framework 兼容矩阵所匹配的信息,设备清单描述了设备为 Framework 提供的静态组件,Framework 兼容矩阵描述了 Android Framework 希望从给定设备获取的内容,匹配后,系统会检查 VINTF object,如果不匹配则会导致使用 HAL 出错。

上述的 android.hardware.led-service.xml 也就是设备清单,其中简要描述了该 HAL 服务实现形式是 AIDL,并包含一些名称信息。

2.3.2.5 将模块添加到兼容矩阵中

Framework 兼容矩阵,兼容矩阵描述了 Android Framework 希望从给定设备获取的内容,之后会和设备清单匹配,匹配成功后 Framework 才能使用到 HAL 服务,添加内容如下。

/*
 * hardware/interfaces/compatibility_matrices/compatibility_matrix.current.xml
 * hardware/interfaces/compatibility_matrices/compatibility_matrix.6.xml
 * 两个文件中均要添加
 */

<hal format="aidl" optional="true">
    <name>android.hardware.led</name>
    <interface>
        <name>ILed</name>
        <instance>default</instance>
    </interface>
</hal>
2.3.2.6 添加服务构建脚本
/*
 * hardware/interfaces/led/aidl/default/Android.bp
 */

package {
    default_applicable_licenses: ["hardware_interfaces_license"],
}

/* cc_binary,表示该构建目标的产出是一个可执行程序 
 * vendor: true,表示编译出来的 target 放在 vendor 目录下
 * relative_install_path: "hw",表示我们的可执行文件会安装到 vendor/bin/hw 目录下
 * init_rc,也就是指定启动服务的 rc 文件
 * vintf_fragments,指定 device manifest 信息,这个之后再做解释
 * srcs,也就是源文件
 * cflags,编译选项,-Wall 编译阶段显示所有警告,-Werror 所有警告当作 error 处理
 * shared_libs,编译过程中依赖的动态库
 */

cc_binary {
    name: "android.hardware.led-service",
    vendor: true,
    relative_install_path: "hw",
    init_rc: ["android.hardware.led-service.rc"],
    vintf_fragments: ["android.hardware.led-service.xml"],

    srcs: [
        "Led.cpp",
        "main.cpp",
    ],

    cflags: [
        "-Wall",
        "-Werror",
    ],

    shared_libs: [
        "libbase",
        "liblog",
        "libhardware",
        "libbinder_ndk",
        "libbinder",
        "libutils",
        "android.hardware.led-V1-ndk_platform",
    ],
}

在之后制作的镜像中也可以在 vendor 目录下看到我们编译好的可执行程序。

evk_8mp:/vendor/bin/hw $ ls | grep led
android.hardware.led-service
2.3.2.7 将模块加入系统中
# Base modules and settings for the vendor partition.

PRODUCT_PACKAGES += \
    android.hardware.led \
    android.hardware.led-service \
2.3.2.8 添加 selinux 权限

selinux 并非 Andriod 驱动开发的重点,因此这里不再赘述。

2.3.2.9 查看服务

重新编译安卓镜像,并烧录到开发板,执行命令可以看到我们添加的服务。

evk_8mp:/ $ logcat | grep ledmanager 
01-01 00:00:23.988   246   246 I servicemanager: Found android.hardware.led.ILed/default in device VINTF manifest.
01-01 00:00:23.988   439   439 D android.hardware.led-service: ILed service starts to join service pool
evk_8mp:/ $ service list | grep led                                            
100     ledmanager: [android.app.ledmanager.ILedManager]
2.3.3 编写应用程序

之前使用的反射的方式获取服务,这里换一种方式,也就是添加 jar 包的方式,一般这个 jar 包会放在如下路径。

android@ubuntu22:~/android/android_build/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates$ ls
classes-header.jar  classes.jar

如果无法这个目录中没有 classes.jar 则使用如下命令,则可以重新编译出 classes.jar。

android@ubuntu22:~/android/android_build$  source build/envsetup.sh
android@ubuntu22:~/android/android_build$  lunch 62
android@ubuntu22:~/android/android_build$  make java-check-framework

除此之外,还要取出我们编译 AIDL 生成的 jar 包。

android@ubuntu22:~/android/android_build/out/soong/.intermediates/hardware/interfaces/led/aidl/android.hardware.led-V1-java/android_common/javac$ ls
android.hardware.led-V1-java.jar 

然后,将这个 class.jar 和 android.hardware.led-V1-java.jar 发送到本地,然后在 Android Studio 中引入 jar 包,如下。

在这里插入图片描述

先把 jar 包复制到这个 libs 文件夹下,然后右键 jar 包,点击 Add to Library 即可导入 jar 包。

应用程序界面和之前相同,导入 jar 包后可以直接使用 getService 函数获取服务。

package com.example.app_01_led_demo;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;

import android.os.RemoteException;
import android.view.View;
import android.widget.Button;

import android.os.ServiceManager;
import android.hardware.led.ILed;


public class MainActivity extends AppCompatActivity {
    private boolean ledTag = false;
    private Button ledButton = null;
    private ILed mLedManager = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ledButton = (Button) findViewById(R.id.BUTTON);
		
        /* 先获取到服务的代理类对象,然后根据代理类构造接口类 */
        mLedManager = ILed.Stub.asInterface(ServiceManager.getService("android.hardware.led.ILed/default"));

        ledButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {

                ledTag = !ledTag;
                if(ledTag)
                {
                    ledButton.setText(R.string.off);
                    try {
                        /* 通过接口类调用方法 */
                        mLedManager.ledCtrl(1, 1);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
                else
                {
                    ledButton.setText(R.string.on);
                    try {
                        /* 通过接口类调用方法 */
                        mLedManager.ledCtrl(1, 0);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }
}

最后运行 APP,可以看到和之前相同的实验现象。

  • 4
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值