При подключении к сети Wi-Fi намерение CONNECTIVITY_ACTION получено дважды

В моем приложении есть BroadcastReceiver, который запускается как компонент через тег <receiver>, фильтруя android.net.conn.CONNECTIVITY_CHANGE намерения.

Моя цель - просто узнать, когда было установлено соединение Wi-Fi, поэтому в onReceive() я делаю следующее:

NetworkInfo networkInfo = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI && networkInfo.isConnected()) {
    // Wifi is connected
}

Он работает нормально, но я всегда получаю два одинаковых намерения примерно в течение одной секунды, когда устанавливается соединение Wi-Fi. Я попытался просмотреть любую информацию, которую мог получить из намерения, ConnectivityManager и WifiManager, но не могу найти ничего, что отличает эти два намерения.

В журнале есть по крайней мере еще один BroadcastReceiver, который также принимает два идентичных намерения.

Он работает на HTC Desire с Android 2.2.

Есть идеи, почему я, кажется, получаю «дублированное» намерение при подключении Wi-Fi или какая разница между ними?


person Torsten Römer    schedule 11.03.2011    source источник
comment
youtube.com/playlist?list=PLrnPJCHvNZuBqr_0AS9BPX, хорошо разбираясь в методах реализации недавних методов вещания. приемники   -  person Anubhav Pandey    schedule 22.08.2019


Ответы (14)


ПРИМЕЧАНИЕ. Чтобы получить последний актуальный ответ, см. этот ниже!

После долгих поисков в Google и отладки я считаю, что это правильный способ определить, подключен ли Wi-Fi или отключен.

Метод onReceive() в BroadcastReceiver:

public void onReceive(final Context context, final Intent intent) {

if(intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
    NetworkInfo networkInfo =
        intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
    if(networkInfo.isConnected()) {
        // Wifi is connected
        Log.d("Inetify", "Wifi is connected: " + String.valueOf(networkInfo));
    }
} else if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
    NetworkInfo networkInfo =
        intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
    if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI &&
        ! networkInfo.isConnected()) {
        // Wifi is disconnected
        Log.d("Inetify", "Wifi is disconnected: " + String.valueOf(networkInfo));
    }
}
}

Вместе со следующим элементом-приемником в AndroidManifest.xml

<receiver android:name="ConnectivityActionReceiver"
    android:enabled="true" android:label="ConnectivityActionReceiver">
    <intent-filter>
        <action android:name="android.net.conn.CONNECTIVITY_CHANGE"/>
        <action android:name="android.net.wifi.STATE_CHANGE"/>
    </intent-filter>
</receiver>

Некоторое объяснение:

  • Если рассматривать только ConnectivityManager.CONNECTIVITY_ACTION, я всегда получаю два намерения, содержащие идентичные экземпляры NetworkInfo (оба getType () == TYPE_WIFI и isConnected () == true) при подключении Wi-Fi - проблема, описанная в этом вопросе.

  • Когда используется только WifiManager.NETWORK_STATE_CHANGED_ACTION, при отключении Wi-Fi не транслируется намерение, но есть два намерения, содержащие разные экземпляры NetworkInfo, что позволяет определить одно событие при подключении Wi-Fi.

ПРИМЕЧАНИЕ. Я получил один отчет о сбое (NPE), в котором intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO) вернул значение null. Таким образом, даже если кажется, что это происходит крайне редко, было бы неплохо добавить нулевую проверку.

Ура, Торстен

person Torsten Römer    schedule 04.05.2011
comment
Я хотел бы упомянуть здесь, что, по крайней мере, иногда, намерение WifiManager.NETWORK_STATE_CHANGED_ACTION транслируется за короткий момент до того, как соединение для передачи данных будет полностью установлено (конфигурация DHCP не завершена), и что в этот момент ConnectivityManager.getActiveNetworkInfo() может по-прежнему возвращать NetworkInfo с типом TYPE_WIFI . - person Torsten Römer; 05.08.2011
comment
Могу лично подтвердить все, что написал Торстен. Спасибо за ваше решение. Меня расстраивает то, что нет более чистого и простого решения. Самое удивительное, что такого поведения не происходит при отключении Wi-Fi. Я не говорю, что это обязательно ошибка, но ее можно было бы лучше задокументировать. - person tos; 12.03.2012
comment
Получение NetworkInfo из намерения теперь обесценивается. Однако непонятно, почему они не ожидают, что это останется стабильным. Кто-нибудь обнаруживает нестабильность в получении Networkinfo намеренно? - person Brill Pappin; 30.10.2012
comment
@BrillPappin Получение его из ConnectivityManager устарело, но обсуждение этого ответа сосредоточено на получении его из WifiManager, что все еще в порядке. Однако, как и другие, я все еще получаю странные последовательности уведомлений. - person Tom; 08.02.2013
comment
Спасибо, попросил объединить аккаунты! - person Torsten Römer; 14.11.2013
comment
intent.getParcelableExtra (ConnectivityManager.EXTRA_NETWORK_INFO) устарел. - person filthy_wizard; 03.10.2015
comment
android.net.conn.CONNECTIVITY_CHANGE также устарел в N, просто чтобы будущие люди знали .. - person behelit; 04.08.2016

Если вы слушаете WifiManager.NETWORK_STATE_CHANGED_ACTION, вы получите это дважды, потому что в NetworkInfo

  • isConnectedOrConnecting()
  • isConnected()

Первый раз isConnectedOrConnecting() возвращает true и isConnected() false
Второй раз isConnectedOrConnecting() и isConnected() возвращают true

Ваше здоровье

person Dominic    schedule 06.09.2011
comment
оба метода возвращают true для обоих событий для меня :) - person slinden77; 03.12.2013
comment
Этот ответ неверен, isConnected() может возвращать true оба раза. - person Justin Papez; 09.07.2018

Это правильный способ зарегистрироваться для изменения подключения к API 21 и выше. Следующий код можно поместить в базовое действие, и таким образом вы можете ожидать, что каждый экран в вашем приложении (который наследуется от этого действия) получит эти обратные вызовы.

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

@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private val networkCallback: ConnectivityManager.NetworkCallback = object : ConnectivityManager.NetworkCallback() {

    // Implement the callback methods that are relevant to the actions you want to take.
    // I have implemented onAvailable for connecting and onLost for disconnecting.

    override fun onAvailable(network: Network?) {
        super.onAvailable(network)
    }

    override fun onLost(network: Network?) {
        super.onLost(network)
    }
}

Затем зарегистрируйте и отмените регистрацию этого обратного вызова в соответствующих местах.

override fun onResume() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
        cm?.registerNetworkCallback(NetworkRequest.Builder().build(), networkCallback)
    }
}

И отмените регистрацию, когда это необходимо.

override fun onPause() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        val cm = getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager
        cm?.unregisterNetworkCallback(networkCallback)
    }
}

Обратите внимание, что есть проверка на Build.VERSION_CODES.LOLLIPOP. Эта функция доступна только в Lollipop и выше. Убедитесь, что у вас есть план обработки изменений статуса сети в устройствах Pre-Lollipop, если вы поддерживаете в своем приложении менее 21 API.

person Mike    schedule 05.02.2019
comment
Спасибо, @mike, это я искал :) - person Saurabh Dhage; 16.06.2021

Обновлен код Торстена, так что при отключении Wi-Fi действует только одна соответствующая трансляция.

Используется NetworkInfo.getDetailedState () == DetailState.DISCONNECTED для проверки.

public void onReceive(final Context context, final Intent intent) {
    if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
        NetworkInfo networkInfo = intent
            .getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
        if (networkInfo.isConnected()) {
            // Wifi is connected
            Log.d("Inetify","Wifi is connected: " + String.valueOf(networkInfo));
        }
    } else if (intent.getAction().equals(
        ConnectivityManager.CONNECTIVITY_ACTION)) {
        NetworkInfo networkInfo = intent
            .getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
        if (networkInfo.getDetailedState() == DetailedState.DISCONNECTED) {
            // Wifi is disconnected
            Log.d("Inetify","Wifi is disconnected: "+String.valueOf(networkInfo));
        }
    }
}
person Christopher Guray    schedule 25.06.2013
comment
Для тех, кто задается вопросом, изменение: if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI && ! networkInfo.isConnected()) на if (networkInfo.getDetailedState() == DetailedState.DISCONNECTED) - person Mr_and_Mrs_D; 13.11.2013
comment
А как насчет Wi-Fi и сотовых данных? Я считаю, что оба вызовут срабатывание BroadcastReceiver, вызывающее больше вызовов. - person lazypig; 13.12.2014

Если вы зарегистрировали действие как прослушиватель намерений, вы получите одно и то же сообщение дважды. В частности, вам нужно выбрать, хотите ли вы слушать на уровне пакета (XML) или на программном уровне.

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

Надеюсь, это решит вашу проблему.

person JoxTraex    schedule 04.05.2011
comment
Это правильно, вы либо создаете слушателя широковещательной рассылки в классе, либо в манифесте, но не то и другое вместе. - person Eric Novins; 11.06.2011
comment
Наконец, я могу прокомментировать это ... ваш ответ, безусловно, правильный, но я зарегистрировал BroadcastReceiver только в манифесте. - person Torsten Römer; 05.08.2011

Я решил дважды вызов с помощью SharedPref with Time.

private static final Long SYNCTIME = 800L;
private static final String LASTTIMESYNC = "DATE";
SharedPreferences sharedPreferences;
private static final String TAG = "Connection";

@Override
public void onReceive(Context context, Intent intent) {
     Log.d(TAG, "Network connectivity change");
     sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);

     final ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
     final NetworkInfo ni = connectivityManager.getActiveNetworkInfo();

        if (ni != null && ni.isConnected()) {   

            if(System.currentTimeMillis()-sharedPreferences.getLong(LASTTIMESYNC, 0)>=SYNCTIME)
            {
                sharedPreferences.edit().putLong(LASTTIMESYNC, System.currentTimeMillis()).commit();
                // Your code Here.
            }
     }
     else if (intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, Boolean.FALSE)) {
            Log.d(TAG, "There's no network connectivity");

    }
}

Потому что есть небольшая задержка между 1. вызовом и 2. вызовом (около 200 миллисекунд). Таким образом, в IF со временем второй вызов прекратится, и только первый будет продолжен.

person Lalson    schedule 14.08.2014
comment
Как бы плохо это ни звучало как решение, оно сработало для меня. Я пробовал семафоры, а также синхронизированные блоки, и ни один из них у меня не работал. - person lazypig; 13.12.2014

Я решил, если в

onCreate()
       intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
       intentFilter.addAction("android.net.wifi.WIFI_STATE_CHANGED");
       intentFilter.addAction("android.net.wifi.STATE_CHANGE");
       ctx.registerReceiver(outgoingReceiver, intentFilter);

in

BroadcastReceiver
 public void onReceive(Context context, Intent intent) {
if(intent.getAction().equals(ConnectivityManager.CONNECTIVITY_ACTION)) {
                NetworkInfo networkInfo =
                        intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
                if(networkInfo.getType() == ConnectivityManager.TYPE_WIFI &&
                        networkInfo.isConnected()) {
                    // Wifi is connected
                    Log.d("Inetify", "Wifi is connected: " + String.valueOf(networkInfo));

                    Log.e("intent action", intent.getAction());
                    if (isNetworkConnected(context)){
                        Log.e("WiFi", "is Connected. Saving...");
                        try {
                            saveFilesToServer("/" + ctx.getString(R.string.app_name).replaceAll(" ", "_") + "/Temp.txt");
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }}


 boolean isNetworkConnected(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo ni = cm.getActiveNetworkInfo();
        if (ni != null) {
            Log.e("NetworkInfo", "!=null");

            try{
                //For 3G check
                boolean is3g = cm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE)
                        .isConnectedOrConnecting();
                //For WiFi Check
                boolean isWifi = cm.getNetworkInfo(ConnectivityManager.TYPE_WIFI)
                        .isConnected();

                Log.e("isWifi", "isWifi="+isWifi);
                Log.e("is3g", "is3g="+is3g);
                if (!isWifi)
                {

                    return false;
                }
                else
                {
                    return true;
                }

            }catch (Exception er){
                return false;
            }

        } else{
            Log.e("NetworkInfo", "==null");
            return false;
        }
    }
person NickUnuchek    schedule 26.08.2015

Я решил эту проблему, воспользовавшись дополнительным намерением для NetworkInfo. В приведенном ниже примере событие onReceive запускается только один раз, если Wi-Fi подключен или мобильный.

if (intent.getAction().equalsIgnoreCase(ConnectivityManager.CONNECTIVITY_ACTION)) {

        NetworkInfo networkInfo = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);

        boolean screenIsOn = false;
        // Prüfen ob Screen on ist
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            screenIsOn = pm.isInteractive();
        } else {
            screenIsOn = pm.isScreenOn();
        }

        if (Helper.isNetworkConnected(context)) {
            if (networkInfo.isConnected() && networkInfo.isAvailable()) {
                Log.v(logTAG + "onReceive", "connected");

                if (networkInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                    Log.v(logTAG + "onReceive", "mobile connected");

                } else if (networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                    Log.v(logTAG + "onReceive", "wifi connected");
                }
            }
        }

и мой помощник:

    public static boolean isNetworkConnected(Context ctx) {
    ConnectivityManager cm = (ConnectivityManager) ctx.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo ni = cm.getActiveNetworkInfo();

    return ni != null;
}
person Scrounger    schedule 11.02.2017

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

 if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) {
                NetworkInfo activeNetwork = intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);
                if (activeNetwork != null) { // connected to the internet
                    if (activeNetwork.isConnected() && !isUpdated) {
                        if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) {
                            // connected to wifi
                        } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) {
                            // connected to the mobile provider's data plan
                        }

                        isUpdated = true;
                    } else {
                        isUpdated = false;
                    }
                }
            }
person zhengcheng wang    schedule 28.04.2018

При включении WIFI,

  1. При включенных МОБИЛЬНЫХ данных отправляются две широковещательные передачи: широковещательная передача №1: МОБИЛЬНЫЕ данные отключены и широковещательная передача №2: подключен Wi-Fi.
  2. Если МОБИЛЬНЫЕ данные ВЫКЛЮЧЕНЫ, отправляется только одна трансляция: Трансляция №1: WIFI подключен.

Аналогичное поведение можно наблюдать при выключении WIFI в двух вышеуказанных условиях.

Чтобы различать их, следуйте пунктам 2 и 3 ниже:

        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d(TAG, "*** Action: " + intent.getParcelableExtra("networkInfo"));

            NetworkInfo netInfo = intent.getParcelableExtra("networkInfo");

            if(intent.getAction().equalsIgnoreCase("android.net.conn.CONNECTIVITY_CHANGE")) {
                ConnectivityManager connectivityManager
                        = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
                NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo();
                if (activeNetInfo != null) {
                    if (netInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                        if (netInfo.getState().name().contains("DISCONNECTED")
                                && activeNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                            Log.d(TAG, "WIFI disconnect created this broadcast. MOBILE data ON."); // #1
                        } else if (netInfo.getState().name().contains("CONNECTED")
                                && activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                            Log.d(TAG, "WIFI connect created this broadcast."); // #2
                        }
                    } else if (netInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                        if (netInfo.getState().name().contains("DISCONNECTED")
                                && activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI) {
                            Log.d(TAG, "MOBILE data disconnect created this broadcast. WIFI ON."); // #3
                        } else if (netInfo.getState().name().contains("CONNECTED")
                                && activeNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) {
                            Log.d(TAG, "MOBILE data connect created this broadcast."); // #4
                        }
                    }
                } else {
                    Log.d(TAG, "No network available");
                }
            }
        }
person FNordy Tuba68    schedule 14.09.2018

Я поступил так: просто сохранил состояние сети и затем сравнил его, чтобы увидеть, было ли изменение.

public class ConnectivityChangedReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        boolean previouslyConnected = MyApp.getInstance().isNetworkPreviouslyConnected();
        boolean currentlyConnected = MyApp.getInstance().isNetworkConnected();

        if (previouslyConnected != currentlyConnected) {
            // do something and reset
            MyApp.getInstance().resetNetworkPreviouslyConnected();
        }
    }

}

Если вы выбрали именно такой подход, важно сбросить его в onResume фрагмента или действия, чтобы оно сохраняло текущее значение:

@Override
public void onResume() {
    super.onResume();
    MyApp.getInstance().resetNetworkPreviouslyConnected();
}

Я сделал это в своем BaseFragment, родительском для всех фрагментов в моем приложении.

person Oleksiy    schedule 23.03.2015

проверьте networkType из намерения и сравните activeNetworkInfo.getType ()

                 Bundle bundle = intent.getExtras();
                 ConnectivityManager manager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
                 NetworkInfo ni = manager.getActiveNetworkInfo();

                 if(ni != null && ni.getState() == NetworkInfo.State.CONNECTED) {
                     if(bundle.getInt("networkType") == ni.getType()) {
                         // active network intent
                     }
                 }
person Wooyeol Jung    schedule 29.09.2016

Нашел особый случай подключения к сети, говоря, что Интернета нет, но на самом деле он есть. Оказывается, getActiveNetworkInfo всегда будет возвращать значение ОТКЛЮЧЕНО / ЗАБЛОКИРОВАНО в конкретном случае, когда сеть изменена при низком уровне заряда батареи и приложение было только что переключено.

Ознакомьтесь с этим сообщением

person Phil    schedule 21.07.2017

Слушайте только действие "android.net.conn.CONNECTIVITY_CHANGE". Он транслируется всякий раз, когда соединение установлено или прервано.

"android.net.wifi.STATE_CHANGE" будет транслироваться, когда соединение установлено. Итак, у вас есть два триггера.

Наслаждаться!

person Henry Sou    schedule 19.10.2011
comment
Проблема в том, что он отправляет два события для каждого изменения состояния. - person Brill Pappin; 30.10.2012