[上一篇文章](http://testerhome.com/topics/1858)已經講了cts如何自動檢測到設備,效果就是無需我們再去調用`ADB`的`getIDevice()`得到設備,利用的是ADB中提供的觀察者模式做到了這一點,那么得到設備后我們如何對這些設備進行管理的呢?
# 設備分類
cts中將設備分為3種狀態:處于驗證中的設備,可用設備,執行任務的設備。這三種狀態的設備分別用3個集合保存:
~~~
//處于驗證中的設備集合
private Map<String, IDeviceStateMonitor> mCheckDeviceMap;
//可用設備的集合
private ConditionPriorityBlockingQueue<IDevice> mAvailableDeviceQueue;
//執行任務的設備集合
private Map<String, IManagedTestDevice> mAllocatedDeviceMap;
~~~
## 1.處于驗證中的設備集合
`mCheckDeviceMap`是一個`Map`,`key`值表示設備的`SN`號,`value`值表示當前設備的狀態監聽器對象`IDeviceStateMonitor`(以后會講到)。當一個新設備被檢測的時候會首先放到該Map中,然后會調用`IDeviceStateMonitor`.`waitForDeviceShell(final long waitTime)`來對設備進行一個掃描,掃描通過以后,就會將設備添加到可用設備集合`mAvailableDeviceQueue`中。但是不管掃描成不成功,經過檢測步驟后,都會從`mCheckDeviceMap`設備集合中刪除該設備。所以可以說該容器這是臨時存放設備用的,為的是對設備進行驗證是否可用,就像機場里過安檢一樣。
## 2.可用的設備集合
`mAvailableDeviceQueue`是一個[優先隊列](http://baike.baidu.com/view/1267829.htm),且是線程安全的,這是`cts`自定義的一個隊列數據結構。你可以傳入條件選擇設備,比如按設備號選擇,按平臺號選擇都可以。返回的是一個`IDevice`對象,這是原生的`ADB`中定義的接口類。該集合是一個中間集合,它從`mCheckDeviceMap`中得到集合,然后等到用戶使用設備后,就將集合中的某個元素"送給"了執行任務的設備集合`mAllocatedDeviceMap`。
## 3.執行任務的設備集合
`mAllocatedDeviceMap`集合是一個Map,`key`值表示設備的`SN`號,`value`值表示的是cts自己定義的設備對象的接口`IManagedTestDevice`。當用戶選擇一個可用設備后,是從可用設備集合`mAvailableDeviceQueue`中得到了一個`IDevice`,然后`cts`使用外觀模式,將其封裝到了繼承自`IManagedTestDevice`接口的對象`TestDevice`(這個類很重要,以后會單獨講)中,里面有很多關于設備的方法可以被調用,那么用戶實際能操作就是這個`TestDevice`類。等待任務完成后,該設備將“送還”到可用設備集合中,這只是一個借用的過程,用完了就還給了它,這樣的話這個設備還可以繼續執行其他任務。
## 4.總結
通過上面的介紹,我們用一幅圖來描述一下3個集合之間的關系。

# 檢測設備后分類
終于到了揭曉廬山真面目的時候,以上的種種解釋,包括第一篇文章的鋪墊,都是為了下面的內容鋪墊的,講代碼的東西就是這么的麻煩。先上代碼:
~~~
private class ManagedDeviceListener implements IDeviceChangeListener {
/**
* {@inheritDoc}
*/
@Override
public void deviceChanged(IDevice device, int changeMask) {
IManagedTestDevice testDevice = mAllocatedDeviceMap.get(device.getSerialNumber());
if ((changeMask & IDevice.CHANGE_STATE) != 0) {
if (testDevice != null) {
TestDeviceState newState = TestDeviceState.getStateByDdms(device.getState());
testDevice.setDeviceState(newState);
} else if (mCheckDeviceMap.containsKey(device.getSerialNumber())) {
IDeviceStateMonitor monitor = mCheckDeviceMap.get(device.getSerialNumber());
monitor.setState(TestDeviceState.getStateByDdms(device.getState()));
} else if (!mAvailableDeviceQueue.contains(device) && device.getState() ==IDevice.DeviceState.ONLINE) {
checkAndAddAvailableDevice(device);
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void deviceConnected(IDevice device) {
CLog.d("Detected device connect %s, id %d", device.getSerialNumber(), device.hashCode());
IManagedTestDevice testDevice = mAllocatedDeviceMap.get(device.getSerialNumber());
if (testDevice == null) {
if (isValidDeviceSerial(device.getSerialNumber()) && device.getState() == IDevice.DeviceState.ONLINE) {
checkAndAddAvailableDevice(device);
} else if (mCheckDeviceMap.containsKey(device.getSerialNumber())) {
IDeviceStateMonitor monitor = mCheckDeviceMap.get(device.getSerialNumber());
monitor.setState(TestDeviceState.getStateByDdms(device.getState()));
}
} else {
// this device is known already. However DDMS will allocate a
// new IDevice, so need
// to update the TestDevice record with the new device
CLog.d("Updating IDevice for device %s", device.getSerialNumber());
testDevice.setIDevice(device);
TestDeviceState newState = TestDeviceState.getStateByDdms(device.getState());
testDevice.setDeviceState(newState);
}
}
private boolean isValidDeviceSerial(String serial) {
return serial.length() > 1 && !serial.contains("?");
}
/**
* {@inheritDoc}
*/
@Override
public void deviceDisconnected(IDevice disconnectedDevice) {
if (mAvailableDeviceQueue.remove(disconnectedDevice)) {
CLog.i("Removed disconnected device %s from available queue",disconnectedDevice.getSerialNumber());
}
IManagedTestDevice testDevice = mAllocatedDeviceMap.get(disconnectedDevice.getSerialNumber());
if (testDevice != null) {
testDevice.setDeviceState(TestDeviceState.NOT_AVAILABLE);
} else if (mCheckDeviceMap.containsKey(disconnectedDevice.getSerialNumber())) {
IDeviceStateMonitor monitor = mCheckDeviceMap.get(disconnectedDevice.getSerialNumber());
monitor.setState(TestDeviceState.NOT_AVAILABLE);
}
updateDeviceMonitor();
}
}
~~~
上面的代碼我在第一篇文章中有涉及,只是那個時候我把三個監聽方法里的具體實現給隱藏了,現在終于把它展現出來了。
## deviceChanged方法
該方法會在設備連接的時候調用,但是我們做了個判斷,就是設備狀態的改變,那就說明該設備連接前`ADB`已經知道了該設備的存在,只是它與之前的狀態發生了變化,所以在設備第一次連接的時候,該方法里的代碼塊是不會被調用的。那么再具體說說操作步驟:
`1`.首先判斷設備的改變是否是狀態的改變,因為設備的變化很有多種,我們需要關心的設備于PC連接狀態的改變,所以需要做判斷。
`2`.然后從`mAllocatedDeviceMap`集合中嘗試獲得發生改變的設備,這一步是為了檢查是否會影響到執行任務的設備。因為在任務執行過程中,不是每時每刻都能去檢測狀態,所以cts采用的是被動判斷,如果狀態發生改變,需要通知正在執行任務的設備監聽器,由監聽器去做相應的處理。
`3`.如果確定狀態發生改變的設備是正在執行任務的設備,就需要將其狀態設置為新狀態。
`4`.如果不是正在執行任務的設備,那么再去驗證是否屬于另外2個集合中的設備。
`5`.如果是檢測集合`mCheckDeviceMap`中的元素,那么也要重新設置設備狀態,不過這個時候是從檢測設備集合中得到設備再改變它的狀態。
`6`.如果改變的設備既不是執行任務的設備,也不是檢測中的設備,這個時候我們要判斷是否是新設備,這個時候可能有人會有疑問?為什么不判斷是否存在于可分配設備中呢?這得從`deviceChanged`的調用機制來說,`deviceChanged`只在設備連接進來的時候會調用,設備掉線的時候該方法不會被調用,那么自然這個里面無需判斷是否是可分配設備中。只需要判斷傳進來的`IDevice`的狀態是否在線且不在可分配設備列表中,這個時候就要按新設備來處理啦。另外判斷是否在該集合中是需要在`deviceDisconnected`中判斷的。
## deviceConnected方法
這個方法會在有設備連接的時候調用,不管是新設備還是舊設備。它的處理步驟如下:
`1`.首先判斷該設備是否正在執行任務,如果正在執行任務,`ok`!我們需要更新該設備的狀態。如果不是進入第二步:
`2`.如果在線且通過判斷其`SN`號是可用的,如果條件不通過,跳到第三步,如果通過的話,這個時候需要進行操作判斷是否可以放到可用設備集合中。
`3`.是否存在于檢測設備集合中,如果存在就將其設備更新下。
## deviceDisconnected方法
這個方法會在設備離線的時候調用,
`1`.首先試著從可用集合中刪除該設備,然后進入第二步。
`2`.剩下的處理方式和上面的`deviceConnected`一樣。
# 總結
設備管理的復雜性較之我所講的,我所能說的也只是皮毛,希望對此有興趣研究研究源碼。提示一下,在研究源碼之前先了解一下23種設計模式中常用模式,對你的理解會很有幫助。
- 前言
- (1)-windows下cts配置
- (2)-cts調試環境的搭建
- (3)-基礎庫tradefederation配置
- (4)-任務的添加
- (5)-9大組件配置
- (6)-任務的執行
- (7)-任務執行的調度室
- (8)-IBuildProvider
- (9)-IDeviceRecovery
- (10)-TestDeviceOptions
- (11)-ICommandOptions
- (12)-ITargetPreparer
- (13)-任務執行過程
- (14)-任務執行過程
- (15)-任務執行完
- (16)-logcat信息收集系統
- (17)-fastboot狀態監聽器
- (18)-設備恢復
- (19)-設備狀態的分類以及恢復模式的分類
- (20)-cts自身log系統
- (21)-測試結果收集系統
- (22)-自動檢測設備
- (23)-設備分類
- (24)-case的組織