(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"); } ~~~
';