Android фоновое восстановление сетевых ошибок

Учитывая службу намерений Android, чья работа - выполнять фоновую сетевую связь (например, выполнить вызов REST для синхронизации данных), когда служба намерений ловит IOExceptionЧто такое хорошая практика для восстановления после ошибки?

Давайте предположим, что объем передаваемых данных достаточно мал, чтобы мы могли повторить работу сети с нуля. Если устройство потеряло подключение к сети, мы хотим получить уведомление о восстановлении подключения и повторить попытку. Если мы не потеряли соединение, мы предполагаем, что сервер или его сетевое соединение не работает, и хотим повторить попытку после задержки.

Не критично завершать операцию связи как можно скорее, но более быстрое означает улучшение взаимодействия с пользователем, хотя это необходимо учитывать при использовании полосы пропускания и времени автономной работы.

Надеюсь, это общее требование, и функциональность встроена в Android. Если да, то где, а если нет, как будет выглядеть код для интеллектуального перезапуска службы намерений?

1 ответ

Решение

Я создал вспомогательный класс, который проверяет состояние сети и ожидает, пока сеть станет доступной, или, если она будет доступна, откладывает с использованием экспоненциального отката.

/**
 * Handles error recovery for background network operations.
 * 
 * Recovers from inability to perform background network operations by applying a capped exponential backoff, or if connectivity is lost, retrying after it is
 * restored. The goal is to balance app responsiveness with battery, network, and server resource use.
 * 
 * Methods on this class are expected to be called from the UI thread.
 * */
public final class ConnectivityRetryManager {
    private static final int INITIAL_DELAY_MILLISECONDS = 5 * 1000;
    private static final int MAX_DELAY_MILLISECONDS = 5 * 60 * 1000;

    private int delay;

    public ConnectivityRetryManager() {
        reset();
    }

    /**
     * Called after a network operation succeeds. Resets the delay to the minimum time and unregisters the listener for restoration of network connectivity.
     */
    public void reset() {
        delay = INITIAL_DELAY_MILLISECONDS;
    }

    /**
     * Retries after a delay or when connectivity is restored. Typically called after a network operation fails.
     * 
     * The delay increases (up to a max delay) each time this method is called. The delay resets when {@link reset} is called.
     */
    public void retryLater(final Runnable retryRunnable) {
        // Registers to retry after a delay. If there is no Internet connection, always uses the maximum delay.
        boolean isInternetAvailable = isInternetAvailable();
        delay = isInternetAvailable ? Math.min(delay * 2, MAX_DELAY_MILLISECONDS) : MAX_DELAY_MILLISECONDS;
        new RetryReciever(retryRunnable, isInternetAvailable);
    }

    /**
     * Indicates whether network connectivity exists.
     */
    public boolean isInternetAvailable() {
        NetworkInfo network = ((ConnectivityManager) App.getContext().getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo();
        return network != null && network.isConnected();
    }

    /**
     * Calls a retry runnable after a timeout or when the network is restored, whichever comes first.
     */
    private class RetryReciever extends BroadcastReceiver implements Runnable {
        private final Handler handler = new Handler();
        private final Runnable retryRunnable;
        private boolean isInternetAvailable;

        public RetryReciever(Runnable retryRunnable, boolean isInternetAvailable) {
            this.retryRunnable = retryRunnable;
            this.isInternetAvailable = isInternetAvailable;
            handler.postDelayed(this, delay);
            App.getContext().registerReceiver(this, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION));
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            boolean wasInternetAvailable = isInternetAvailable;
            isInternetAvailable = isInternetAvailable();
            if (isInternetAvailable && !wasInternetAvailable) {
                reset();
                handler.post(this);
            }
        }

        @Override
        public void run() {
            handler.removeCallbacks(this);
            App.getContext().unregisterReceiver(this);
            retryRunnable.run();
        }
    }
}
Другие вопросы по тегам