(18)-设备恢复
最后更新于:2022-04-01 06:55:00
# 设备恢复
## 类关系
![](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-01-09_56911ddd1b454.jpg)
## 理解
4个分类中,AbortRecovery和StubDeviceRecovery2个类都是直接继承方法,直接做报错处理。但是报错的信息体现了他们的不同点,一个是放弃恢复,一个是不能恢复。还有客观世界中的区别:好比在说,我能上清华,只是我不上而已。
那么就剩下另外2个类。ReconnectingRecovery和WaitDeviceRecovery。一个是重连,一个实等待设备恢复。2者有什么区别,去具体看方法里的定义吧。
## 代码
AbortRecovery:放弃恢复。就是我不恢复
~~~
private static class AbortRecovery implements IDeviceRecovery {
/**
* {@inheritDoc}
*/
@Override
public void recoverDevice(IDeviceStateMonitor monitor, boolean recoverUntilOnline) throws DeviceNotAvailableException {
throw new DeviceNotAvailableException("aborted test session");
}
/**
* {@inheritDoc}
*/
@Override
public void recoverDeviceBootloader(IDeviceStateMonitor monitor) throws DeviceNotAvailableException {
throw new DeviceNotAvailableException("aborted test session");
}
/**
* {@inheritDoc}
*/
@Override
public void recoverDeviceRecovery(IDeviceStateMonitor monitor) throws DeviceNotAvailableException {
throw new DeviceNotAvailableException("aborted test session");
}
}
~~~
StubDeviceRecovery:为虚拟设备定义的,意思是:我不能恢复。
~~~
public class StubDeviceRecovery implements IDeviceRecovery {
/**
* {@inheritDoc}
*/
@Override
public void recoverDevice(IDeviceStateMonitor monitor, boolean recoverUntilOnline)
throws DeviceNotAvailableException {
throw new DeviceNotAvailableException("device recovery not implemented");
}
/**
* {@inheritDoc}
*/
@Override
public void recoverDeviceBootloader(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
throw new DeviceNotAvailableException("device recovery not implemented");
}
/**
* {@inheritDoc}
*/
@Override
public void recoverDeviceRecovery(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
throw new DeviceNotAvailableException("device recovery not implemented");
}
}
~~~
ReconnectingRecovery:重新连接设备。用于wifi连接设备的时候,如果短线了,调用adb connect的命令来连接设备。
~~~
public class ReconnectingRecovery implements IDeviceRecovery {
private static final int ADB_TIMEOUT = 2 * 60 * 1000;
private static final int CONNECTION_ATTEMPTS = 5;
/**
* {@inheritDoc}
*/
@Override
public void recoverDevice(IDeviceStateMonitor monitor, boolean recoverUntilOnline)
throws DeviceNotAvailableException {
String serial = monitor.getSerialNumber();
// disconnect - many versions of adb client have stale TCP connection
// status
getRunUtil().runTimedCmd(ADB_TIMEOUT, "adb", "disconnect", serial);
// try to reconnect
int attempt = 1;
do {
CLog.i("Trying to reconnect with device " + serial + " / attempt " + attempt);
getRunUtil().runTimedCmd(ADB_TIMEOUT, "adb", "connect", serial);
} while (monitor.waitForDeviceOnline() == null && ++attempt <= CONNECTION_ATTEMPTS);
String errMsg = "Could not recover device " + serial + " after " + --attempt + " attempts";
// occasionally device is erroneously reported as online - double check
// that we can shell into device
if (!monitor.waitForDeviceShell(10 * 1000)) {
throw new DeviceUnresponsiveException(errMsg);
}
if (!recoverUntilOnline) {
if (monitor.waitForDeviceAvailable() == null) {
throw new DeviceUnresponsiveException(errMsg);
}
}
CLog.v("Successfully reconnected with device " + serial);
}
/**
* {@inheritDoc}
*/
@Override
public void recoverDeviceBootloader(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
throw new java.lang.UnsupportedOperationException(
"This implementation can't recover a device in bootloader mode.");
}
/**
* {@inheritDoc}
* <p>
* This implementation assumes devices in recovery mode can't be talked to
* at all, so it will try to recover a device and leave it in fully booted
* mode.
*/
@Override
public void recoverDeviceRecovery(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
recoverDevice(monitor, false);
}
/**
* Get the {@link RunUtil} instance to use.
* <p/>
* Exposed for unit testing.
*/
IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
}
~~~
WaitDeviceRecovery,这才是真正要去理解的类。一般的测试主要是用的该类来恢复设备。
~~~
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.tradefed.device;
import com.android.ddmlib.AdbCommandRejectedException;
import com.android.ddmlib.IDevice;
import com.android.ddmlib.Log;
import com.android.ddmlib.TimeoutException;
import com.android.tradefed.config.Option;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import java.io.IOException;
/**
* A simple implementation of a {@link IDeviceRecovery} that waits for device to be online and
* respond to simple commands.
*/
public class WaitDeviceRecovery implements IDeviceRecovery {
private static final String LOG_TAG = "WaitDeviceRecovery";
/** the time in ms to wait before beginning recovery attempts */
protected static final long INITIAL_PAUSE_TIME = 5 * 1000;
/**
* The number of attempts to check if device is in bootloader.
* <p/>
* Exposed for unit testing
*/
public static final int BOOTLOADER_POLL_ATTEMPTS = 3;
// TODO: add a separate configurable timeout per operation
@Option(name="device-wait-time",
description="maximum time in ms to wait for a single device recovery command.")
protected long mWaitTime = 4 * 60 * 1000;
@Option(name="bootloader-wait-time",
description="maximum time in ms to wait for device to be in fastboot.")
protected long mBootloaderWaitTime = 30 * 1000;
@Option(name="shell-wait-time",
description="maximum time in ms to wait for device shell to be responsive.")
protected long mShellWaitTime = 30 * 1000;
@Option(name = "disable-unresponsive-reboot",
description = "If this is set, we will not attempt to reboot an unresponsive device" +
"that is in userspace. Note that this will have no effect if the device is in " +
"fastboot or is expected to be in fastboot.")
protected boolean mDisableUnresponsiveReboot = false;
/**
* Get the {@link RunUtil} instance to use.
* <p/>
* Exposed for unit testing.
*/
protected IRunUtil getRunUtil() {
return RunUtil.getDefault();
}
/**
* Sets the maximum time in ms to wait for a single device recovery command.
*/
void setWaitTime(long waitTime) {
mWaitTime = waitTime;
}
/**
* {@inheritDoc}
*/
@Override
public void recoverDevice(IDeviceStateMonitor monitor, boolean recoverUntilOnline)
throws DeviceNotAvailableException {
// device may have just gone offline
// sleep a small amount to give ddms state a chance to settle
// TODO - see if there is better way to handle this
Log.i(LOG_TAG, String.format("Pausing for %d for %s to recover",
INITIAL_PAUSE_TIME, monitor.getSerialNumber()));
getRunUtil().sleep(INITIAL_PAUSE_TIME);
// ensure bootloader state is updated,最新获取过fastboot devices信息
monitor.waitForDeviceBootloaderStateUpdate();
if (monitor.getDeviceState().equals(TestDeviceState.FASTBOOT)) {
Log.i(LOG_TAG, String.format(
"Found device %s in fastboot but expected online. Rebooting...",
monitor.getSerialNumber()));
// TODO: retry if failed
getRunUtil().runTimedCmd(20*1000, "fastboot", "-s", monitor.getSerialNumber(),
"reboot");
}
// wait for device online
IDevice device = monitor.waitForDeviceOnline();
if (device == null) {
handleDeviceNotAvailable(monitor, recoverUntilOnline);
return;
}
// occasionally device is erroneously reported as online - double check that we can shell
// into device
if (!monitor.waitForDeviceShell(mShellWaitTime)) {
// treat this as a not available device
handleDeviceNotAvailable(monitor, recoverUntilOnline);
return;
}
if (!recoverUntilOnline) {
if (monitor.waitForDeviceAvailable(mWaitTime) == null) {
// device is online but not responsive
handleDeviceUnresponsive(device, monitor);
}
}
}
/**
* Handle situation where device is online but unresponsive.
* @param monitor
* @throws DeviceNotAvailableException
*/
protected void handleDeviceUnresponsive(IDevice device, IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
if (!mDisableUnresponsiveReboot) {
rebootDevice(device);
}
IDevice newdevice = monitor.waitForDeviceOnline();
if (newdevice == null) {
handleDeviceNotAvailable(monitor, false);
return;
}
if (monitor.waitForDeviceAvailable(mWaitTime) == null) {
throw new DeviceUnresponsiveException(String.format(
"Device %s is online but unresponsive", monitor.getSerialNumber()));
}
}
/**
* Handle situation where device is not available.
*
* @param monitor the {@link IDeviceStateMonitor}
* @param recoverTillOnline if true this method should return if device is online, and not
* check for responsiveness
* @throws DeviceNotAvailableException
*/
protected void handleDeviceNotAvailable(IDeviceStateMonitor monitor, boolean recoverTillOnline)
throws DeviceNotAvailableException {
throw new DeviceNotAvailableException(String.format("Could not find device %s",
monitor.getSerialNumber()));
}
/**
* {@inheritDoc}
*/
@Override
public void recoverDeviceBootloader(final IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
// device may have just gone offline
// wait a small amount to give device state a chance to settle
// TODO - see if there is better way to handle this
Log.i(LOG_TAG, String.format("Pausing for %d for %s to recover",
INITIAL_PAUSE_TIME, monitor.getSerialNumber()));
getRunUtil().sleep(INITIAL_PAUSE_TIME);
// poll and wait for device to return to valid state
long pollTime = mBootloaderWaitTime / BOOTLOADER_POLL_ATTEMPTS;
for (int i=0; i < BOOTLOADER_POLL_ATTEMPTS; i++) {
if (monitor.waitForDeviceBootloader(pollTime)) {
handleDeviceBootloaderUnresponsive(monitor);
// passed above check, abort
return;
} else if (monitor.getDeviceState() == TestDeviceState.ONLINE) {
handleDeviceOnlineExpectedBootloader(monitor);
return;
}
}
handleDeviceBootloaderNotAvailable(monitor);
}
/**
* Handle condition where device is online, but should be in bootloader state.
* <p/>
* If this method
* @param monitor
* @throws DeviceNotAvailableException
*/
protected void handleDeviceOnlineExpectedBootloader(final IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
Log.i(LOG_TAG, String.format("Found device %s online but expected fastboot.",
monitor.getSerialNumber()));
// call waitForDeviceOnline to get handle to IDevice
IDevice device = monitor.waitForDeviceOnline();
if (device == null) {
handleDeviceBootloaderNotAvailable(monitor);
return;
}
rebootDeviceIntoBootloader(device);
if (!monitor.waitForDeviceBootloader(mBootloaderWaitTime)) {
throw new DeviceNotAvailableException(String.format(
"Device %s not in bootloader after reboot", monitor.getSerialNumber()));
}
}
/**
* @param monitor
* @throws DeviceNotAvailableException
*/
protected void handleDeviceBootloaderUnresponsive(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
CLog.i("Found device %s in fastboot but potentially unresponsive.",
monitor.getSerialNumber());
// TODO: retry reboot
getRunUtil().runTimedCmd(20*1000, "fastboot", "-s", monitor.getSerialNumber(),
"reboot-bootloader");
// wait for device to reboot
monitor.waitForDeviceNotAvailable(20*1000);
if (!monitor.waitForDeviceBootloader(mBootloaderWaitTime)) {
throw new DeviceNotAvailableException(String.format(
"Device %s not in bootloader after reboot", monitor.getSerialNumber()));
}
}
/**
* Reboot device into bootloader.
*
* @param device the {@link IDevice} to reboot.
*/
protected void rebootDeviceIntoBootloader(IDevice device) {
try {
device.reboot("bootloader");
} catch (IOException e) {
Log.w(LOG_TAG, String.format("failed to reboot %s: %s", device.getSerialNumber(),
e.getMessage()));
} catch (TimeoutException e) {
Log.w(LOG_TAG, String.format("failed to reboot %s: timeout", device.getSerialNumber()));
} catch (AdbCommandRejectedException e) {
Log.w(LOG_TAG, String.format("failed to reboot %s: %s", device.getSerialNumber(),
e.getMessage()));
}
}
/**
* Reboot device into bootloader.
*
* @param device the {@link IDevice} to reboot.
*/
protected void rebootDevice(IDevice device) {
try {
device.reboot(null);
} catch (IOException e) {
Log.w(LOG_TAG, String.format("failed to reboot %s: %s", device.getSerialNumber(),
e.getMessage()));
} catch (TimeoutException e) {
Log.w(LOG_TAG, String.format("failed to reboot %s: timeout", device.getSerialNumber()));
} catch (AdbCommandRejectedException e) {
Log.w(LOG_TAG, String.format("failed to reboot %s: %s", device.getSerialNumber(),
e.getMessage()));
}
}
/**
* Handle situation where device is not available when expected to be in bootloader.
*
* @param monitor the {@link IDeviceStateMonitor}
* @throws DeviceNotAvailableException
*/
protected void handleDeviceBootloaderNotAvailable(final IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
throw new DeviceNotAvailableException(String.format(
"Could not find device %s in bootloader", monitor.getSerialNumber()));
}
/**
* {@inheritDoc}
*/
@Override
public void recoverDeviceRecovery(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
throw new DeviceNotAvailableException("device recovery not implemented");
}
}
~~~
## 重点
现在着重来理解WaitDeviceRecovery中的方法。
### recoverDevice方法
~~~
/**
* {@inheritDoc}
*/
@Override
public void recoverDevice(IDeviceStateMonitor monitor, boolean recoverUntilOnline)
throws DeviceNotAvailableException {
// device may have just gone offline
// sleep a small amount to give ddms state a chance to settle
// TODO - see if there is better way to handle this
Log.i(LOG_TAG, String.format("Pausing for %d for %s to recover",
INITIAL_PAUSE_TIME, monitor.getSerialNumber()));
getRunUtil().sleep(INITIAL_PAUSE_TIME);
// ensure bootloader state is updated,最新获取过fastboot devices信息
monitor.waitForDeviceBootloaderStateUpdate();
if (monitor.getDeviceState().equals(TestDeviceState.FASTBOOT)) {
Log.i(LOG_TAG, String.format(
"Found device %s in fastboot but expected online. Rebooting...",
monitor.getSerialNumber()));
// TODO: retry if failed
getRunUtil().runTimedCmd(20*1000, "fastboot", "-s", monitor.getSerialNumber(),
"reboot");
}
// wait for device online
IDevice device = monitor.waitForDeviceOnline();
if (device == null) {
handleDeviceNotAvailable(monitor, recoverUntilOnline);
return;
}
// occasionally device is erroneously reported as online - double check that we can shell
// into device
if (!monitor.waitForDeviceShell(mShellWaitTime)) {
// treat this as a not available device
handleDeviceNotAvailable(monitor, recoverUntilOnline);
return;
}
if (!recoverUntilOnline) {
if (monitor.waitForDeviceAvailable(mWaitTime) == null) {
// device is online but not responsive
handleDeviceUnresponsive(device, monitor);
}
}
}
~~~
1.首先等待5秒钟,是为了给adb足够的时间先自己处理一下设备断线问题。(adb有自动恢复功能)。
2.我们要等待最新一次的fastboot监听器的执行结果,这个在上一篇文章讲过fastboot监听器是做什么用的。
3.判断目前设备断线的原因是否是因为进入到了fastboot模式下。如果是的话,执行fastboot reboot操作重启设备。
4.等待设备恢复到在线状态(具体怎么等待的,我会在DeviceMonitor中讲解)。
5.如果等待过后,设备没有恢复到在线状态,则调用handleDeviceNotAvailable方法,直接抛出错误。
6.如果设备正常恢复到online状态。再次通过adb命令来检测其正常性,双重验证。如果验证不通过也需要抛出错误。
7.如果第二次验证通过,是否需要等待设备处于有效状态,有效状态和在线状态的区别在于:在线不等于就可以用于测试。可以用于测试的测试需要经过很多中检测。这个也会在以后讲解。
8.如果做了等待设备直到处于有效状态的操作后,设备没有反馈正确的结果,说明设备没有处于有效状态,这个时候我们就需要调用handleDeviceUnresponsive()
~~~
protected void handleDeviceUnresponsive(IDevice device, IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
if (!mDisableUnresponsiveReboot) {
rebootDevice(device);
}
IDevice newdevice = monitor.waitForDeviceOnline();
if (newdevice == null) {
handleDeviceNotAvailable(monitor, false);
return;
}
if (monitor.waitForDeviceAvailable(mWaitTime) == null) {
throw new DeviceUnresponsiveException(String.format(
"Device %s is online but unresponsive", monitor.getSerialNumber()));
}
}
~~~
9.在上面的方法中,会先判断是否可以重启,如果可以的话,先重启一下设备。
10.然后重复一遍4~7的步骤
11.如果还没成功,就直接报错了。
上面的步骤中在执行设备恢复到设备处于在线状态的的过程中,执行了2次,称为retry的动作。再试一次,可以使成功的概率大一点。
### recoverDeviceBootloader方法
~~~
@Override
public void recoverDeviceBootloader(final IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
// device may have just gone offline
// wait a small amount to give device state a chance to settle
// TODO - see if there is better way to handle this
Log.i(LOG_TAG, String.format("Pausing for %d for %s to recover",
INITIAL_PAUSE_TIME, monitor.getSerialNumber()));
getRunUtil().sleep(INITIAL_PAUSE_TIME);
// poll and wait for device to return to valid state
long pollTime = mBootloaderWaitTime / BOOTLOADER_POLL_ATTEMPTS;
for (int i=0; i < BOOTLOADER_POLL_ATTEMPTS; i++) {
if (monitor.waitForDeviceBootloader(pollTime)) {
handleDeviceBootloaderUnresponsive(monitor);
// passed above check, abort
return;
} else if (monitor.getDeviceState() == TestDeviceState.ONLINE) {
handleDeviceOnlineExpectedBootloader(monitor);
return;
}
}
handleDeviceBootloaderNotAvailable(monitor);
}
~~~
1.第一步和上面的意义,等待一段时间,让adb先自己处理。
2.计算重复执行的次数,用总时间/执行的次数,等待每次执行所用的时间。
3.执行等待设备处于BootLoader模式,也就是fastboot模式。
4.如果成功进入fastboot模式,则直接返回,如果设备处于online模式,则调用handleDeviceOnlineExpectedBootloader方法
~~~
protected void handleDeviceOnlineExpectedBootloader(final IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
Log.i(LOG_TAG, String.format("Found device %s online but expected fastboot.",
monitor.getSerialNumber()));
// call waitForDeviceOnline to get handle to IDevice
IDevice device = monitor.waitForDeviceOnline();
if (device == null) {
handleDeviceBootloaderNotAvailable(monitor);
return;
}
rebootDeviceIntoBootloader(device);
if (!monitor.waitForDeviceBootloader(mBootloaderWaitTime)) {
throw new DeviceNotAvailableException(String.format(
"Device %s not in bootloader after reboot", monitor.getSerialNumber()));
}
}
~~~
5.进入该方法后,首先通过直接monitor.waitForDeviceOnline()来得到该设备,因为该设备就已经处于online,这里调用只是为了得到IDevice.
6.如果这个时候得到的设备为null,直接抛出错误。
7.然后重启设备,进入fastboot模式。
8.然后再次等待设备处于fastboot模式,如果成功,方法结束,回到recoverDeviceBootloader中,然后recoverDeviceBootloader也结束,返回。如果失败,直接抛出异常。
9.重复3~8的过程,如果循环结束后,还无法进入fastboot模式,直接抛出异常。
### recoverDeviceRecovery方法
因为不可能执行卡刷,所以该方法直接抛出异常。
~~~
public void recoverDeviceRecovery(IDeviceStateMonitor monitor)
throws DeviceNotAvailableException {
throw new DeviceNotAvailableException("device recovery not implemented");
}
~~~