Как получить уведомление, когда одноранговое соединение недоступно в диапазоне Wi-Fi Direct?

Я разрабатываю приложение для Android, основанное на использовании Wifi Direct API. Я зарегистрировался в своем Activity a BroadcastReceiver, чтобы получать уведомления о следующих событиях Wifi Direct: WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION, WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION, WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION и WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.

Я считал, что любое изменение в списке сверстников (включение или исключение однорангового узла в диапазоне Wifi Direct) может вызвать BroadcastReceiver. В моем приложении, когда новый одноранговый узел найден, его имя правильно включено в ListView, но если одноранговый узел покидает беспроводной диапазон (или если отключить его интерфейс Wi-Fi), BroadcastReceiver не вызывается (более конкретно, событие WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION не запускается), а одноранговое имя остается в ListView.

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

Ответ 1

Я полагал, что любое изменение в списке одноранговых узлов (включение или исключение однорангового узла в диапазоне Wifi Direct) может инициировать BroadcastReceiver.

Да, я думаю, это должно произойти.

В моем приложении, когда новый одноранговый узел найден, его имя правильно включено в ListView, но если одноранговый узел покидает беспроводной диапазон (или если отключить его интерфейс Wi-Fi), BroadcastReceiver не вызывается ( более конкретно, событие WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION не запускается), а одноименное имя остается в ListView.

Мы можем попытаться прорвать исходный код и посмотреть, можем ли мы объяснить это поведение.

Примечание. У меня нет предложения. Но следующее исследование может помочь вам лучше понять проблему.

Мы начнем здесь: WifiP2pSettings Ссылка

Это фрагмент, который вы видите, когда вы переходите в Настройки > Wifi > Wifi-Direct. Если вы пройдете через код, вы заметите, что реализация очень похожа на проект WifiDirectDemo - BroadcastReceiver прослушивает те же четыре действия (и еще два - один для обновления пользовательского интерфейса). Причина, по которой мы рассматриваем этот фрагмент, - проверить, не испорчена ли сама демонстрация. Но, похоже, что демонстрация в порядке.

Перемещение - посмотрим, кто вещает действие WIFI_P2P_PEERS_CHANGED_ACTION - в конечном итоге нам интересно узнать, почему это не происходит, как только устройство переходит в автономный режим/выходит за пределы диапазона. Это приведет нас к WifiP2pService Ссылка.

Метод WifiP2pService # sendPeersChangedBroadcast() выдает трансляцию:

private void sendPeersChangedBroadcast() {
    final Intent intent = new Intent(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
    intent.putExtra(WifiP2pManager.EXTRA_P2P_DEVICE_LIST, new WifiP2pDeviceList(mPeers));
    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
}

От взгляда на WifiP2pService вы можете сказать, что sendPeersChangedBroadcast() вызывается в ответ на несколько событий. Нам это интересно: WifiMonitor.P2P_DEVICE_LOST_EVENT Ссылка:

....
case WifiMonitor.P2P_DEVICE_LOST_EVENT:
    device = (WifiP2pDevice) message.obj;
    // Gets current details for the one removed
    device = mPeers.remove(device.deviceAddress);
    if (device != null) {
        sendPeersChangedBroadcast();
    }
    break;
....

WifiMonitor # handleP2pEvents(String) отвечает за отправку сообщения, которое попадает в вышеупомянутый case. Поднимите цепочку, чтобы найти MonitorThread - статический внутренний класс в WifiMonitor. MonitorThread # dispatchEvent(String) вызывает метод handleP2pEvents(String).

Наконец, что-то интересное. Посмотрите на метод run() MonitorThread:

private static class MonitorThread extends Thread {

    ....

    public void run() {
        //noinspection InfiniteLoopStatement
        for (;;) {
            String eventStr = mWifiNative.waitForEvent();
            ....
        }
    }

    ....
}            
  • во-первых, мы находимся внутри бесконечного цикла.
  • second, mWifiNative.waitForEvent() говорит мне, что это, вероятно, блокирующий вызов.

Эти две точки, вместе взятые, указывают мне, что я не собираюсь получать быстрый ответ из этого - ну, определенно ничего "моментально". Метод, который мы достигли, поднявшись по цепочке - MonitorThread # dispatchEvent(String) - вызывается изнутри этого бесконечного цикла.

Позвольте проверить, может ли что-то поддержать наше образованное предположение:

Взгляните на WifiNative класс Ссылка, особенно метод setScanInterval(int). Этот метод вызывается из класса WifiStateMachine Ссылка. Пройдите метод processMessage(Message):

....
case WifiP2pService.P2P_CONNECTION_CHANGED:
    NetworkInfo info = (NetworkInfo) message.obj;
    mP2pConnected.set(info.isConnected());
    if (mP2pConnected.get()) {
        int defaultInterval = mContext.getResources().getInteger(
                            R.integer.config_wifi_scan_interval_p2p_connected);
        long scanIntervalMs = Settings.Global.getLong(mContext.getContentResolver(),
                            Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS,
                            defaultInterval);

        // ====>> Interval defined here
        mWifiNative.setScanInterval((int) scanIntervalMs/1000);
    } else if (mWifiConfigStore.getConfiguredNetworks().size() == 0) {
        if (DBG) log("Turn on scanning after p2p disconnected");
            sendMessageDelayed(obtainMessage(CMD_NO_NETWORKS_PERIODIC_SCAN,
                            ++mPeriodicScanToken, 0), mSupplicantScanIntervalMs);
    }
....

Обратите внимание на defaultInterval в первом if block. Он запрашивает R.integer.config_wifi_scan_interval_p2p_connected, который определен в config.xml Ссылка как:

<!-- Integer indicating wpa_supplicant scan interval when p2p is connected in milliseconds -->
<integer translatable="false" name="config_wifi_scan_interval_p2p_connected">60000</integer>

60000 мс. Это 1 минута. Итак, если Settings.Global.WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS не установлен, сканирование будет разнесено на 1 минуту.

Так как WIFI_SCAN_INTERVAL_WHEN_P2P_CONNECTED_MS находится под глобальными настройками, мы не можем его изменить. На самом деле его даже не читать. Смысл, единственная гарантия, которую мы имеем здесь, состоит в том, что список сверстников будет обновлен через минуту. Интервал может, конечно, варьироваться от бренда к бренду.

Чтобы убедиться в этом, я запускал демонстрацию на планшете asus и планшете dell. Как вы сказали, устройство, поступающее в Интернет, было замечено довольно быстро (на этапе обнаружения). При отключении Wi-Fi я отвел ответ. Автономное устройство было отключено от списка автоматически - с существенной задержкой. Планшет dell приблизился к 60 seconds, чтобы заметить, что asus отключен. С другой стороны, Asus взял 45 seconds.

Для меня это похоже на ограничение, введенное андроидом. Я не могу понять, почему. Я надеюсь, что кто-то здесь может предложить вам решение - возможно, возьмите это исследование и изучите его дальше. Но я не удивлюсь, если решение не существует (в настоящее время).