读书频道 > 网站 > 网页设计 > 深入理解Android:卷III
3.3.1 WiredAccessoryObserver 设备状态的监控
15-08-21    下载编辑
收藏    我要投稿   

本文所属图书 > 深入理解Android:卷III

本书在逻辑上分为4个部分Part 01(第1-2章):这是本书的基础部分,首先介绍了Android源码环境的搭建、编译和调试;然后讲解了Android进程间通信与任务调度的工具Binder与MessageQueue 这两项基础工作是深入研究立即去当当网订购
1. WiredAccessoryObserver简介
 
这要从WiredAccessoryObserver开始讲起,它是内核通知有线耳机插入事件所到达的第一个环节。
 
WiredAccessoryObserbver继承自UEventObserver。UEventObserver是Android用来接收UEvent的一个工具类。UEventObserver类维护着一个读取UEvent的线程,注意这个线程是UEventObserver的一个静态成员,也就是说,一个进程只有一个。当调用UEventObserver的startObserving()函数开始监听时,会告诉这个线程UEventObserver关心什么样的UEvent,当匹配的事件到来时,监听线程会通过回调UEventObserver的onUEvent函数进行通知。读者可以看一下UEventObserver的源代码以了解其具体实现,这并不复杂。
 
WiredAccessoryObserver接收内核上报的和耳机/HDMI/USB相关的UEvent事件,并将其翻译成设备的状态变化。由于每种外设都有自己的UEvent与状态文件,因此WiredAccessoryObserver定义了一个内部类名为UEventInfo, 并且为自己感兴趣的每一个音频外设创建一个实例,其内部保存了对应外设的名字、UEvent地址及状态文件的地址。每当有合适的UEvent到来时,WiredAccessoryObserver就会查找匹配的UEventInfo实例,并且更新可用设备的状态列表,同时通知AudioService。
 
关于可用外设的状态列表,虽然称为列表,事实上,它只是一个整型的变量,名为mHeadsetState。在可用外设的状态列表中用一个二进制标志位表示某个外设的状态可用与否,这与AudioPolicyManager的mAvailableOutputDevices的用法是一样的。下面是各种外设的标志位的定义:
 
private static final int BIT_HEADSET = (1 << 0);
private static final int BIT_HEADSET_NO_MIC = (1 << 1);
private static final int BIT_USB_HEADSET_ANLG = (1 << 2);
private static final int BIT_USB_HEADSET_DGTL = (1 << 3);
private static final int BIT_HDMI_AUDIO = (1 << 4);
private static final int SUPPORTED_HEADSETS = (BIT_HEADSET|BIT_HEADSET_NO_MIC|
                                  BIT_USB_HEADSET_ANLG|BIT_USB_HEADSET_DGTL|
                                  BIT_HDMI_AUDIO);
private static final int HEADSETS_WITH_MIC = BIT_HEADSET;

 

举个例子,如果mHeadsetState等于0x00000002,也就是BIT_HEADSET_NO_MIC,表示目前手机上插入一个不带麦克风的耳机。而如果mHeadsetState等于0x00000011,也就是HEADSETS_WITH_MIC | BIT_HDMI_AUDIO,则表示目前手机上同时插入一个带有麦克风的耳机及HDMI输出线。
 
WiredAccessoryObserver工作原理就这么简单,我们接下来将以有线耳机为例子对其进行详细讨论。
 
2. 启动与初始化
 
虽然WiredAccessoryObserver不是一个服务,但是它拥有系统服务的待遇—在system_server中同系统服务一起被加载,如下所示:
 
[SystemServer.java-->ServerThread.run()]
try {
    new WiredAccessoryObserver(context);
} catch (Throwable e) {
    reportWtf("starting WiredAccessoryObserver", e);
}

 

 
只有一个构造函数,其实,构造函数中并没有做太多的初始化工作,而是注册了一个BroadcastReceiver,监听ACTION_BOOT_COMPLETE。其真正的初始化工作是在这个BootCompletedReceiver中完成的。
 
[WiredAccessoryObserver.java-->BootCompletedReceiver.onReceive()]
public void onReceive(Context context, Intent intent) {
    // 初始化
    init(); 
    // 开始对所有感兴趣的UEvent进行监听
    for (int i = 0; i < uEventInfo.size(); ++i) {
        UEventInfo uei = uEventInfo.get(i);
        startObserving("DEVPATH="+uei.getDevPath());
    }
}
这里的init()函数的作用是为了在开机后对外设的状态进行初始化。
[WiredAccessoryObserver.java-->WiredAccessoryObserver.init()]
private synchronized final void init() {
    char[] buffer = new char[1024];
    mPrevHeadsetState = mHeadsetState;

    for (int i = 0; i < uEventInfo.size(); ++i) {
        UEventInfo uei = uEventInfo.get(i);
        try {

            int curState;

            /* 打开状态文件并从中读取状态信息。状态文件中保存着一个整数,非0则表示设备已插入。
             通过UEventInfo的定义可以知道,有线耳机的状态文件路径为
              /sys/class/switch/h2w/state */
            FileReader file = new FileReader(uei.getSwitchStatePath());
            int len = file.read(buffer, 0, 1024);
            file.close();
            curState = Integer.valueOf((new String(buffer, 0, len)).trim());

            // 如果设备已插入,则更新设备的状态,否则不作处理
            if (curState > 0) {
                updateState(uei.getDevPath(), uei.getDevName(), curState);
            }
        } catch (Exception e) {
            ......
        }
    }
}

 

 
到这里WiredAccessoryObserver已经完成初始化了,已经对第一条UEvent的到来准备就绪。
 
3. 耳机插入或拔出时的处理
 
如果有外设被插入或拔出,WiredAccessoryObserver的onUEvent()函数会被回调。参数event中保存了其详细的信息。
 
[WiredAccessoryObserver.java-->WiredAccessoryObserver.onUEvent()]
public void onUEvent(UEventObserver.UEvent event) {
    try {
        // UEvent事件的路径
        String devPath = event.get("DEVPATH");
        // 这个name其实就是UEventInfo中的mDevName,通过这个变量确定发生状态变化的设备名字
        String name = event.get("SWITCH_NAME");
        // 这个state与保存在状态文件中的数值的意义是一致的
        // 事实上,当这条UEvent上报时,状态文件中的值也被更新成这个值
        int state = Integer.parseInt(event.get("SWITCH_STATE"));
        // 像初始化的init()函数一样,调用updateState()进行状态更新
        updateState(devPath, name, state);
    } catch (NumberFormatException e) {
        ......
    }
}
[WiredAccessoryObserver.java-->WiredAccessoryObserver.updateState()]
private synchronized final void updateState(String devPath, String name, int state)
{
    for (int i = 0; i < uEventInfo.size(); ++i) {
        UEventInfo uei = uEventInfo.get(i);
        if (devPath.equals(uei.getDevPath())) {
            // 找到状态发生变化的外设所对应的UEventInfo并更新状态
            update(name, uei.computeNewHeadsetState(mHeadsetState, state));
            return;
        }
    }
}

 

 
看到这里,读者是否觉得updateState的实现有些笨拙了呢?如果以devName为键,将uEventInfo保存在Hashtable中,无论对代码的整洁还是执行的效率都是有帮助的。
 
注意uei.computeNewHeadsetState()这个函数,它的目的是通过UEvent上报的状态值计算出新的可用外设列表。
 
computeNewHeadsetState()这个函数的扩展性并不是太好,只是目前够用而已,读者可以自行研究。
 
继续前面的脚步,现在到了update()函数。这个函数的目的是对前面传入的newState进行全面检查,防止出现不正确的状态。这个函数的运算稍多些,为了方便分析,仅留下和有线耳机(h2w)相关的代码。
 
[WiredAccessoryObserver.java-->WiredAccessoryObserver.update()]
private synchronized final void update(String newName, int newState) {
    /* 从headsetState中去掉不支持的外设,所以,如果不希望手机支持某种外设,比
       如说USB_HEADSET,不需要从kernel改起,只要将其从SUPPORTED_HEADSETS中去
       掉即可 */
    int headsetState = newState & SUPPORTED_HEADSETS;
    int h2w_headset = headsetState & (BIT_HEADSET | BIT_HEADSET_NO_MIC);
    boolean h2wStateChange = true;
    // 下面这行代码比较有意思,首先我们的目的是判断有线耳机的状态是否发生了变化
    // mHeadsetState == headsetState这种条件很好理解,可是后面那个条件呢
    if (mHeadsetState == headsetState || ((h2w_headset & (h2w_headset - 1)) != 0)) {
        h2wStateChange = false;
    }

    // 如果是不正确的状态转换则直接忽略
    if (!h2wStateChange) {
        return;
    }

    // 更新可用外设列表
    mHeadsetName = newName;
    mPrevHeadsetState = mHeadsetState;
    mHeadsetState = headsetState;

    // 为什么要申请一个电源锁呢
    mWakeLock.acquire();

    // 状态已经更新完毕,发送消息给mHandler,我们可以想象出接下来要做什么了,通知AudioSevicervice
    // 注意mHandler的定义,可以看出它运行在创建WiredAccessoryObserver的ServerThread中
    mHandler.sendMessage(mHandler.obtainMessage(0,
                                        mHeadsetState,
                                        mPrevHeadsetState,
                                        mHeadsetName));
}

 

 
这个函数的意图比较很明显,只是其中一个判断条件让人一时摸不着头脑,(h2w_headset & (h2w_headset - 1)) != 0。按照注释中的说法,此函数不接受同时有两种耳机出现的情况,也就是h2w_headst == BIT_HEADSET | BIT_HEADSET_NO_MIC,直接做这个判断不就可以了吗?仔细琢磨就能发现写这个条件的人的聪明之处。直接判断仅限于只有两种可能的外设时才能起作用,超过两个就很难处理了。而谷歌的这个做法既快捷,又可以应对任意多种可能的外设。读者可以思考一下为什么。
 
另外,这段代码在执行mHandler.sendMessage()的调用之前先申请了一个电源锁。这是一个很细节但很重要的做法。当发送消息给一个Handler时,必须考虑设备有可能在Handler得以处理消息之前进入深睡眠状态的极端情况(对延时消息来说,可能就是常见情况了)。在这种情况下,CPU将会进入休眠状态,从而使得消息无法得到及时处理,影响程序执行的正确性。
 
可用外设列表更新完毕后发送了一条消息给mHandler。当消息生效时,直接调用setDevicesState()函数,它会遍历所有SUPPORTED_HEADSET,然后对每个外设调用setDeviceState()。注意,这两个函数是devices与device的区别。setDeviceState()的目的就是要把指定外设的状态汇报给AudioService,我们看一下它的实现:
 
[WiredAccessoryObserver.java-->WiredAccessoryObserver.setDeviceState()]
private final void setDeviceState(int headset,
                           int headsetState,
                           int prevHeadsetState,
                           String headsetName) {
    if ((headsetState & headset) != (prevHeadsetState & headset)) {
        // 只有当这个外设的接入状态发生变化时才会继续
        int device;
        int state;

        // 1表示可用,0表示不可用
        if ((headsetState & headset) != 0) {
            state = 1;
        } else {
            state = 0;
        }

        // 翻译可用外设列表中的外设为Audio系统的设备号
        if (headset == BIT_HEADSET) {
            device = AudioManager.DEVICE_OUT_WIRED_HEADSET;
        } else if (headset == BIT_HEADSET_NO_MIC){
            device = AudioManager.DEVICE_OUT_WIRED_HEADPHONE;
        } else if (headset == BIT_USB_HEADSET_ANLG) {
            device = AudioManager.DEVICE_OUT_ANLG_DOCK_HEADSET;
        } else if (headset == BIT_USB_HEADSET_DGTL) {
            device = AudioManager.DEVICE_OUT_DGTL_DOCK_HEADSET;
        } else if (headset == BIT_HDMI_AUDIO) {
            device = AudioManager.DEVICE_OUT_AUX_DIGITAL;
        } else {
            Slog.e(TAG, "setDeviceState() invalid headset type: "+headset);
            return;
        }

        // 通知AudioService
        mAudioManager.setWiredDeviceConnectionState(device, state, headsetName);
    }
}

 

 
之后,程序的流程将会离开WiredHeadsetObserver,再次前往AudioService。
 
4. 总结一下WiredAccessoryObserver
 
对WiredHeadsetObserver的分析就先告一段落,这里再简单回顾一下关于它的知识。
 
它是站在最前方的一个哨兵,时刻监听着和音频外设拔插相关的UEvent事件。
 
它接收到UEvent事件后,会翻译事件的内容为外设可用状态的变化。
 
它是为AudioService服务的,一旦有变化就立刻通知AudioService。
 
它虽然不是一个服务,但是它却运行在system_server中。
 
它不是唯一的音频外设状态监听者,它只负责监控有线连接的音频外设。其他的,如蓝牙耳机,在其他相关模块中维护。但是它们的本质是类似的,最终都要通知给AudioServic。有兴趣的读者可以自行研究。
点击复制链接 与好友分享!回本站首页
分享到: 更多
您对本文章有什么意见或着疑问吗?请到论坛讨论您的关注和建议是我们前行的参考和动力  
上一篇:1.3 功能
下一篇:1.5 小结
相关文章
图文推荐
JavaScript网页动画设
1.9 响应式
1.8 登陆页式
1.7 主题式
排行
热门
文章
下载
读书

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训
版权所有: 红黑联盟--致力于做最好的IT技术学习网站