android uevent

Uevent是内核通知android有状态变化的一种方法,比如USB线插入、拔出,电池电量变化等等。其本质是内核发送(可以通过socket)一个字符串,应用层(android)接收并解释该字符串,获取相应信息。

一、Kernel侧

UEVENT的发起在Kernel端,主要是通过函数
int kobject_uevent_env(struct kobject *kobj, enum kobject_action action,
char *envp_ext[])
该函数的主要功能是根据参数组合一个字符串并发送。一个典型的字符串如下:change@/devices/platform/msm-battery/power_supply/usb纮ACTION=change纮DEVPATH=/devices/platform/msm-battery/power_supply/usb纮SUBSYSTEM=power_supply纮POWER_SUPPLY_NAME=usb纮POWER_SUPPLY_ONLINE=0纮SEQNUM=1486纮
首先,准备各个字符串:


1.准备字符串

1)获取action字符串
*action_string = kobject_actions[action];
Action为KOBJ_ADD等,kobject_actions的定义如下:
static const char *kobject_actions[] = {
[KOBJ_ADD] = “add”,
[KOBJ_REMOVE] = “remove”,
[KOBJ_CHANGE] = “change”,
[KOBJ_MOVE] = “move”,
[KOBJ_ONLINE] = “online”,
[KOBJ_OFFLINE] = “offline”,
};

2)获取subsystem字符串
subsystem = kobject_name(&kset->kobj);
static inline const char *kobject_name(const struct kobject *kobj)
{
return kobj->name;
}
这里主要获取kobj的名字。
以“power_supply”为例,在power_supply_core.c中注册class power_supply:
power_supply_class = class_create(THIS_MODULE, “power_supply”);
将调用以下函数class_createà __class_createà __class_registerà kobject_set_name:
error = kobject_set_name(&cp->subsys.kobj, “%s”, cls->name);
其中的cls->name就是“power_class”最终在kobject_set_name_vargs中赋值给kobject->name
3)devpath字符串,是改变了的uevent所在的sysfs中的位置
devpath = kobject_get_path(kobj, GFP_KERNEL);


2.填充字符串

然后分配一个env空间存储字符串,
env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);将上面这些字符串填充到其中去,
retval = add_uevent_var(env, “ACTION=%s”, action_string);
retval = add_uevent_var(env, “DEVPATH=%s”, devpath);
retval = add_uevent_var(env, “SUBSYSTEM=%s”, subsystem);
接着加入不同class的附加的字符串
retval = add_uevent_var(env, “%s”, envp_ext[i]);
retval = uevent_ops->uevent(kset, kobj, env);
然后加上该Uenvent的序号,该序号是不断递增的。
add_uevent_var(env, “SEQNUM=%llu”, (unsigned long long)seq)。
3.发送
字符串准备完毕,就要准备发送了,由于Android的CONFIG_NET选项是选上的,因此可以通过socket发送:
首先分配一个skb用于存储网络发送的数据
scratch = skb_put(skb, len);
sprintf(scratch, “%s@%s”, action_string, devpath);
此时scratch中就增加了change@/devices/platform/msm-battery/power_supply/usb的字符,然后将之前准备好的各个字符传加在后面
for (i = 0; i < env->envp_idx; i++) {
len = strlen(env->envp[i]) + 1;
scratch = skb_put(skb, len);
strcpy(scratch, env->envp[i]);
}
最后发送调用retval = netlink_broadcast_filtered发送就OK了。


二 、hardware Socket接口

在hardware/libhardware_legacy/uevent/vim uevent.c中,

// 该函数监听socket,并将socket收到的数据保存到buffer中
uevent_next_event(char* buffer, int buffer_length)
nr = poll(&fds, 1, -1);

if(nr > 0 && fds.revents == POLLIN) {
    int count = recv(fd, buffer, buffer_length, 0);
if (count > 0) {
………………………………..
}

该socket是在int uevent_init()中创建的

s = socket(PF_NETLINK, SOCK_DGRAM,NETLINK_KOBJECT_UEVENT);


三、Android侧


1.启动监视

private UEventObserver mPowerSupplyObserver = new UEventObserver()
{
   @Override
public void onUEvent(UEventObserver.UEvent event) {
            update();
        }
}

申明一个observer对象,然后调用startObserving启动对该对象的监视。

mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply");

最终会调用到UEventObserver的addObserver:

private ArrayListmObservers = new ArrayList();

public void addObserver(String match, UEventObserver observer) {
   synchronized(mObservers) {
       mObservers.add(match);
      mObservers.add(observer);
   }
}

该函数最终会将”SUBSYSTEM=power_supply”增加到匹配序列中,当kernel发送具有该字符串的数据时,就返回匹配成功,然后调用mPowerSupplyObserver的onUEvent函数;

public void run() {
            ………………….
            while (true) {
                len = next_event(buffer);
                if (len > 0) {
                    String bufferStr = new String(buffer, 0, len);  // easier to search a String
                    synchronized (mObservers) {
                        for (int i = 0; i < mObservers.size(); i += 2) {
                            if (bufferStr.indexOf((String)mObservers.get(i)) != -1) {
                                ((UEventObserver)mObservers.get(i+1))
                                        .onUEvent(new UEvent(bufferStr));
                            }
                        }
                    }
                }
            }
        }

next_event(buffer)从底层接收数据,然后在for循环中比较,如果符合,则调用onUevent。之所以for循环时要加2,是因为一次startObserving是调用了两次mObservers.add,其中第一次的是匹配字符串。


2.JNI函数

其中next_event是一个JNI函数

//android_os_UEventObserver.c
private static native int next_event(byte[] buffer);
static JNINativeMethod gMethods[] = {
    {"native_setup", "()V",   (void *)android_os_UEventObserver_native_setup},
    {"next_event",   "([B)I", (void *)android_os_UEventObserver_next_event},
};

android_os_UEventObserver_next_event会调用到uevent_next_event,

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android系统中,可以使用uevent来检测U盘的挂载地址。具体步骤如下: 1. 在Android系统中,U盘的插入和拔出都会发送uevent消息,可以通过注册一个uevent监听器来接收这些消息。 2. 在uevent消息中,会包含有U盘的一些信息,比如设备名称、挂载路径等。 3. 在接收到U盘插入消息时,可以解析uevent消息中的挂载路径信息,即可获得U盘的挂载地址。 以下是一个使用uevent检测U盘挂载地址的示例代码: ``` import android.os.Handler; import android.os.Looper; import android.os.MessageQueue; import android.util.Log; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; public class UdiskMonitor { private static final String TAG = "UdiskMonitor"; private static final String UDISK_MOUNT_PATH = "/sys/class/android_usb/android0/f_mass_storage/lun/file"; private OnUdiskMountedListener mListener; public void startMonitor(final OnUdiskMountedListener listener) { mListener = listener; // 在主线程中监听uevent消息 new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { Looper.myQueue().addIdleHandler(new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { // 开始监听uevent消息 startUeventMonitor(); return false; } }); } }); } private void startUeventMonitor() { BufferedReader reader = null; try { // 打开uevent监听文件 reader = new BufferedReader(new FileReader(new File("/proc/net/netlink"))); String line; while ((line = reader.readLine()) != null) { // 解析uevent消息 String[] parts = line.split(" "); if (parts.length >= 6 && parts[0].equals("1")) { String action = parts[1]; String devPath = parts[4]; if (action.equals("add") && devPath.equals(UDISK_MOUNT_PATH)) { // U盘被插入,获取挂载路径并回调监听器 String mountPath = getUdiskMountPath(); if (mountPath != null) { if (mListener != null) { mListener.onUdiskMounted(mountPath); } } } } } } catch (IOException e) { Log.e(TAG, "startUeventMonitor: " + e.getMessage()); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { Log.e(TAG, "startUeventMonitor: " + e.getMessage()); } } } } private String getUdiskMountPath() { String mountPath = null; try { // 读取U盘挂载路径 BufferedReader reader = new BufferedReader(new FileReader(new File(UDISK_MOUNT_PATH))); mountPath = reader.readLine().trim(); reader.close(); } catch (IOException e) { Log.e(TAG, "getUdiskMountPath: " + e.getMessage()); } return mountPath; } public interface OnUdiskMountedListener { void onUdiskMounted(String mountPath); } } ``` 在上述代码中,我们通过监听uevent消息来检测U盘的插入和拔出事件。当接收到U盘插入事件时,我们通过读取U盘的挂载路径来获取U盘的挂载地址,并通过回调监听器来通知外部应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值