Цель CONNECTIVITY_ACTION получена дважды при подключении Wi-Fi
В моем приложении у меня есть 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
Любая идея, почему я, похоже, получаю "дублированное" намерение, когда Wifi подключается или какая разница между этими двумя?
Ответы
Ответ 1
ПРИМЕЧАНИЕ. Для получения последнего актуального ответа см. Этот ответ ниже!
После многих поисков и отладки я считаю, что это правильный способ определить, подключен или отключен 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
, при отключении Wifi не передается намерение, а два намерения, содержащие разные экземпляры NetworkInfo, позволяющие определить одно событие при подключении Wifi.
ПРИМЕЧАНИЕ. Я получил один отчет о intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)
NPE), в котором intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)
возвратил intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO)
. Таким образом, даже если это случается крайне редко, было бы неплохо добавить нулевую проверку.
Ура, Торстен
Ответ 2
Если вы слушаете WifiManager.NETWORK_STATE_CHANGED_ACTION
, вы получите это дважды, потому что есть два метода в NetworkInfo
-
isConnectedOrConnecting()
-
isConnected()
Первый раз isConnectedOrConnecting()
возвращает true
и isConnected()
false
Второй раз isConnectedOrConnecting()
и isConnected()
return true
Приветствия
Ответ 3
Обновлен код Torsten, так что когда WIFI отключается, действует только одна соответствующая широковещательная рассылка.
Для проверки используется NetworkInfo.getDetailedState() == DetailedState.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));
}
}
}
Ответ 4
Если вы зарегистрировали активность как приемник намерений, вы получите одно и то же сообщение дважды. В частности, вам нужно выбрать, хотите ли вы слушать уровень пакета (XML) или программный уровень.
Если вы настроили класс для приемника широковещательной передачи и подключили его к прослушиванию И вы присоедините фильтр намерения к активности, тогда сообщение будет реплицировано дважды.
Надеюсь, это решит вашу проблему.
Ответ 5
Я решил дважды позвонить, используя SharedPref со временем.
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 no network connectivity");
}
}
Потому что есть небольшая задержка между 1.call и 2.call(около 200 milisec).
Таким образом, в IF со временем второй вызов остановится и только сначала будет продолжен.
Ответ 6
Я решил, что с
в
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);
в
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;
}
}
Ответ 7
Это правильный способ регистрации изменений подключения в 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.
Ответ 8
Я решил эту проблему, используя дополнительные намерения для 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;
}
Ответ 9
Если вы хотите получить его только один раз, вы можете просто управлять им через переменные.
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 data plan
}
isUpdated = true;
} else {
isUpdated = false;
}
}
}
Ответ 10
То, как я справлялся с этим, было просто сохранение состояния сети, а затем сравнение его, чтобы увидеть, произошло ли изменение.
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();
}
}
}
Если это ваш подход, важно reset его в onResume
вашего фрагмента или действия, чтобы он сохранял текущее значение:
@Override
public void onResume() {
super.onResume();
MyApp.getInstance().resetNetworkPreviouslyConnected();
}
Я сделал это в моем BaseFragment
, родителе всех фрагментов в моем приложении.
Ответ 11
проверить 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
}
}
Ответ 12
Нашел специальный случай для подключения к сети, говорящий, что нет Интернета, но на самом деле есть. Оказывается, getActiveNetworkInfo
всегда будет возвращать вам DISCONNECTED/BLOCKED в конкретном случае, когда сеть изменяется, когда уровень заряда батареи низкий, и приложение было просто переключено.
Отметьте этот пост
Ответ 13
При включении WIFI,
- При включенных данных MOBILE отправляются две трансляции: трансляция № 1: данные MOBILE отключены, и трансляция № 2: подключение WIFI
- С МОБИЛЬНЫМИ данными ВЫКЛ, только одна трансляция отправлена: Трансляция # 1: WIFI подключен
Аналогичное поведение можно наблюдать при выключении Wi-Fi в двух вышеупомянутых условиях.
Чтобы провести различие между ними, следуйте № 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");
}
}
}
Ответ 14
Слушайте только действие "android.net.conn.CONNECTIVITY_CHANGE". Он транслируется всякий раз, когда соединение установлено или уничтожается.
"android.net.wifi.STATE_CHANGE" будет транслироваться при установлении соединения. Таким образом, вы получаете два триггера.
Наслаждайтесь!