USB线插拔检测使用UEventObserver检测uevent事件的分析
说实话这玩样儿的代码量真的很少,大家如果能耐得住性子啃一会儿也就能撸懂了。
在这之前研究USB线插拔的时候就知道了有这么个东西,当时也就看了看,但没做什么笔记。最近想用起来,却发现就只有个名字在记忆中了。
好了,又扯了这么多,来回到正题。
首先按照技术博客一贯的作风,得先有个入口点,这里我就先从怎么使用这个UEventObserver开始一步步分析。
首先这玩样儿是java代码,所以你就别想着c++什么用了。
这里我举例了USB线插拔来分析,代码地址如下:
frameworks/base/services/java/com/android/server/usb/UsbDeviceManager.java
/*
* Listens for uevent messages from the kernel to monitor the USB state
*/
private final UEventObserver mUEventObserver = new UEventObserver() {
@Override
public void onUEvent(UEventObserver.UEvent event) {
if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
String state =event.get("USB_STATE");
String accessory = event.get("ACCESSORY");
if (state != null) {
mHandler.updateState(state);
} else if ("START".equals(accessory)) {
if (DEBUG) Slog.d(TAG,"got accessory start");
startAccessoryMode();
}
}
};
这里我们可以看到使用前必须先初始化一个新的UEventObserver类来处理事件,即函数onUEvent,这是一个回调函数,之后我们会看到他是怎么被调用的。这里这个回调函数根据处理的事件消息来判断是否要更新USB的状态,继而触发通知栏显示。
初始化完UEventObserver之后,我们还需要使用这个类,接下来的代码做了这个事。
private static final String USB_STATE_MATCH =
"DEVPATH=/devices/virtual/android_usb/android0";
public UsbHandler(Looper looper) {
super(looper);
// Watch for USB configurationchanges
mUEventObserver.startObserving(USB_STATE_MATCH);
从中我们看出他直接调用了UEventObserver的startObserving函数,并且指定了参数,即一段字符串。
接下来我们看下startObserving到底做了什么事。
public final void startObserving(String match) {
if (match == null || match.isEmpty()) {
throw new IllegalArgumentException("match substring must benon-empty");
}
final UEventThread t = getThread();
t.addObserver(match, this);
}
这个函数首先会判断是否字符串为空,然后调用getThread来获取UEventThread类,我们来看下getThread
private static UEventThread getThread() {
synchronized (UEventObserver.class) {
if (sThread == null) {
sThread = new UEventThread();
sThread.start();
}
return sThread;
}
}
我们可以发现,这个函数告诉我们每个UEventObserver类中只有一个UEventThread类。从名字就能看出这货是个处理线程。因为我们这里是第一次进行获取,所以这里会新建一个UEventThread类,并调用start函数启动它。(有些安卓线程经验的同学,应该知道它最终会调用线程类中run函数,具体为神马的请百度吧,或者等我的博客更新)
private static final class UEventThread extends Thread {
@Override
public void run() {
nativeSetup();
while (true) {
String message =nativeWaitForNextEvent();
if (message != null) {
if (DEBUG) {
Log.d(TAG, message);
}
sendEvent(message);
}
}
}
我们可以从while循环看出他就是一个不停工作的线程。首先我们先看下nativeSetup做了什么,这货一般你看到就应该知道都是native层实现了,麻溜的就去找对应的cpp文件吧。
frameworks/base/core/jni/android_os_UEventObserver.cpp
static void nativeSetup(JNIEnv *env, jclassclazz) {
if (!uevent_init()) {
jniThrowException(env, "java/lang/RuntimeException",
"Unable to open socket forUEventObserver");
}
}
hardware/libhardware_legacy/uevent/uevent.c
int uevent_init()
{
struct sockaddr_nl addr;
int sz = 64*1024;
int s;
memset(&addr, 0, sizeof(addr));
addr.nl_family = AF_NETLINK;
addr.nl_pid = getpid();
addr.nl_groups = 0xffffffff;
s= socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if(s < 0)
return 0;
setsockopt(s, SOL_SOCKET, SO_RCVBUFFORCE, &sz, sizeof(sz));
if(bind(s, (struct sockaddr *) &addr, sizeof(addr)) < 0) {
close(s);
return 0;
}
fd = s;
return (fd > 0);
}
其实么这个函数调了很多层,最终只是调用了uevent实现里的初始化socket来接收UEVENT消息的,注意这里的接收蔟为NETLINK_KOBJECT_UEVENT,这货好像是全局接收各种uevent的,不只是单单某一种。(具体可以百度下socket知识,我也只是知道个皮毛。)
好了建完socket返回fd之后,我们回到UEventThread,他会继续调用native层的nativeWaitForNextEvent来获得一个消息。说实话,以前经常看别人的博客吐槽java层偷懒,时间长了确实感同身受,各种native处理啊。
static jstringnativeWaitForNextEvent(JNIEnv *env, jclass clazz) {
char buffer[1024];
for (;;) {
int length = uevent_next_event(buffer, sizeof(buffer) - 1);
if (length <= 0) {
return NULL;
}
buffer[length] = '\0';
ALOGV("Received uevent message: %s", buffer);
if (isMatch(buffer, length)) {
// Assume the message is ASCII.
jchar message[length];
for (int i = 0; i < length; i++) {
message[i] = buffer[i];
}
return env->NewString(message, length);
}
}
}
我们先看下uevent_next_event,它和uevent_init函数一样,都是调到uevent里的。
int uevent_next_event(char* buffer, intbuffer_length)
{
while (1) {
struct pollfd fds;
int nr;'
nr = poll(&fds, 1, -1);
if(nr > 0 && (fds.revents & POLLIN)) {
int count = recv(fd, buffer, buffer_length, 0);
这个函数其实也很简单,我稍微省略了些内容,它的主要功能就两个,首先调用poll来等socket数据,有数据来的话接收到buffer里,然后返回。
好了,回到nativeWaitForNextEvent函数里。在接收到uevent消息后,我们会调用isMatch函数来检测是否匹配。
static bool isMatch(const char* buffer,size_t length) {
AutoMutex _l(gMatchesMutex);
for (size_t i = 0; i < gMatches.size(); i++) {
const String8& match = gMatches.itemAt(i);
if (strstr(field, match.string())) {
ALOGV("Matched ueventmessage with pattern: %s", match.string());
return true;
}
这里就是判断是否这个返回的uevent消息存在于gMatches里,这里大家可能会问gMatches里什么都没有啊,不可能会存在。这时候我们在看下我们当初分析的时候,我们除了启动一个新线程,我们还做了什么。
final UEventThread t = getThread();
t.addObserver(match, this);
这里我们会调用UEventThread的addObserver函数,参数为传进来的字符串以及新建的UEventObserver,这里我们看下这个函数。
public void addObserver(String match,UEventObserver observer) {
synchronized (mKeysAndObservers) {
mKeysAndObservers.add(match);
mKeysAndObservers.add(observer);
nativeAddMatch(match);
}
}
这个函数首先会把我们传进来的参数添加到mKeysAndObservers里,并且调用native函数nativeAddMatch添加匹配字符串。
static void nativeAddMatch(JNIEnv* env,jclass clazz, jstring matchStr) {
ScopedUtfChars match(env, matchStr);
AutoMutex _l(gMatchesMutex);
gMatches.add(String8(match.c_str()));
}
好吧,这里你看到了gMatches了吧,没错他就是记录我们要查询的字符串的。
之后我们回到判断gMatches的isMatch函数,这时候我们已经有了一个字符串,如果这时候我们接收的uevent消息里包含了这段字符串就会返回true
还记得我们设置的字符串吗
DEVPATH=/devices/virtual/android_usb/android0
一般USB线插拔的时候都会有这段uevent消息来告诉用户所属设配路径。
假设这时候确实插入了USB线,即会返回true
那么我们就可以返回nativeWaitForNextEvent函数来看下接下来的处理。
if (isMatch(buffer, length)) {
// Assume the message is ASCII.
jchar message[length];
for (int i = 0; i < length; i++) {
message[i] = buffer[i];
}
return env->NewString(message, length);
}
之后他会进行保存并返回一个新的字符串,注意这里的字符串类型是jchar,即JAVA中的String类,这样java层才能进行进一步处理。
OK,我们也跟着它返回到UEventThread,这时候我们已经得到了一个uevent匹配的消息,然后调用sentEvent进行处理。
String message = nativeWaitForNextEvent();
if (message != null) {
if (DEBUG) {
Log.d(TAG, message);
}
sendEvent(message);
}
private void sendEvent(String message) {
synchronized (mKeysAndObservers) {
final int N =mKeysAndObservers.size();
for (int i = 0; i < N; i +=2) {
final String key =(String)mKeysAndObservers.get(i);
if (message.contains(key)){
final UEventObserverobserver =
(UEventObserver)mKeysAndObservers.get(i + 1);
mTempObserversToSignal.add(observer);
}
}
}
if (!mTempObserversToSignal.isEmpty()) {
final UEvent event = newUEvent(message);
final int N =mTempObserversToSignal.size();
for (int i = 0; i < N; i++){
final UEventObserverobserver = mTempObserversToSignal.get(i);
observer.onUEvent(event);
}
mTempObserversToSignal.clear();
}
}
这个函数有点长,但是做的事情一目了览,首先它会根据返回的消息在mKeysAndObservers查找UEventObserver,如果有匹配的话就返回下一个类,还记得我们在之前添加的两个参数吗,即查询字符串和UEventObserver类。这里他就会找到UEventObserver,并且添加到mTempObserversToSignal里。当所有匹配的UEventObserver被添加完后,循环调用每个类的onUEvent回调函数。这个函数我们已经在一开始新建UEventObserver的时候实现过了。
/*
* Listens for uevent messages from the kernel to monitor the USB state
*/
private final UEventObserver mUEventObserver = new UEventObserver() {
@Override
public void onUEvent(UEventObserver.UEvent event) {
if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
String state =event.get("USB_STATE");
if (state != null) {
mHandler.updateState(state);
这样我们就把UEventObserver说清楚了,大家觉得是不是很简单呢,但是说实话这货确实是java层检测硬件变化的好帮手,推荐使用。
最后做个总结,我画了张简单的流程图,大家凑合着看就行了。

浙公网安备 33010602011771号