Android:网络连接变化监听器

100

我已经有了监听网络连接变化的代码 -

public class NetworkStateReceiver extends BroadcastReceiver
{
  public void onReceive(Context context, Intent intent)
  {
    Log.d("app","Network connectivity change");

    if(intent.getExtras() != null)
    {
      NetworkInfo ni = (NetworkInfo) intent.getExtras().get(ConnectivityManager.EXTRA_NETWORK_INFO);
      if(ni != null && ni.getState() == NetworkInfo.State.CONNECTED)
      {
        Log.i("app", "Network " + ni.getTypeName() + " connected");
      }
    }

    if(intent.getExtras().getBoolean(ConnectivityManager.EXTRA_NO_CONNECTIVITY, Boolean.FALSE))
    {
      Log.d("app", "There's no network connectivity");
    }
  }
}

我使用这段代码检查互联网连接 - Internet Check

但问题是,如果网络突然失去互联网连接而没有任何连接更改,这段代码就无用了。是否有办法创建广播接收器以侦听互联网连接性的更改? 我有一个Web应用程序,突然的互联网连接变化可能会引起问题。


请参考以下链接:https://dev59.com/U2Uo5IYBdhLWcg3w3yqi - lenhuy2106
13个回答

110

试试这个

public class NetworkUtil {
    public static final int TYPE_WIFI = 1;
    public static final int TYPE_MOBILE = 2;
    public static final int TYPE_NOT_CONNECTED = 0;
    public static final int NETWORK_STATUS_NOT_CONNECTED = 0;
    public static final int NETWORK_STATUS_WIFI = 1;
    public static final int NETWORK_STATUS_MOBILE = 2;

    public static int getConnectivityStatus(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        if (null != activeNetwork) {
            if(activeNetwork.getType() == ConnectivityManager.TYPE_WIFI)
                return TYPE_WIFI;

            if(activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE)
                return TYPE_MOBILE;
        } 
        return TYPE_NOT_CONNECTED;
    }

    public static int getConnectivityStatusString(Context context) {
        int conn = NetworkUtil.getConnectivityStatus(context);
        int status = 0;
        if (conn == NetworkUtil.TYPE_WIFI) {
            status = NETWORK_STATUS_WIFI;
        } else if (conn == NetworkUtil.TYPE_MOBILE) {
            status = NETWORK_STATUS_MOBILE;
        } else if (conn == NetworkUtil.TYPE_NOT_CONNECTED) {
            status = NETWORK_STATUS_NOT_CONNECTED;
        }
        return status;
    }
}

并且对于BroadcastReceiver

public class NetworkChangeReceiver extends BroadcastReceiver {

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

        int status = NetworkUtil.getConnectivityStatusString(context);
        Log.e("Sulod sa network reciever", "Sulod sa network reciever");
        if ("android.net.conn.CONNECTIVITY_CHANGE".equals(intent.getAction())) {
            if (status == NetworkUtil.NETWORK_STATUS_NOT_CONNECTED) {
                new ForceExitPause(context).execute();
            } else {
                new ResumeForceExitPause(context).execute();
            }
       }
    }
}

别忘了将此添加到你的AndroidManifest.xml文件中

 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
 <uses-permission android:name="android.permission.INTERNET" />
 <receiver
        android:name="NetworkChangeReceiver"
        android:label="NetworkChangeReceiver" >
        <intent-filter>
            <action android:name="android.net.conn.CONNECTIVITY_CHANGE" />
            <action android:name="android.net.wifi.WIFI_STATE_CHANGED" />
        </intent-filter>
    </receiver>

希望这能对你有所帮助,祝好!


9
为什么需要检查 if (!"android.intent.action.BOOT_COMPLETED".equals(intent.getAction()))?这个检查是为了确保当前的Intent不是设备启动完成后发送的广播。在Android设备启动完成时,会发送一个带有“android.intent.action.BOOT_COMPLETED”动作的广播。如果你的应用程序不希望在设备启动时执行某些操作,那么你可以使用这个检查来避免它们被执行。 - Shridutt Kothari
2
它在金立S Plus设备上无法工作......还有其他方法吗? - Himanshu
2
这个代码很好用,除了一个问题。我还想为我的Android电视盒子的LAN /以太网连接添加一个监听器。这对WiFi或移动连接非常有效,但对于LAN连接则不然。我有这段代码来检查LAN连接的状态,但我需要一个监听器来捕获Internet状态的任何更改,而不仅仅是WiFi或移动网络:code NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean isConnected = activeNetwork!= null && activeNetwork.isConnectedOrConnecting(); code - Wayne Johnson
47
在Android 7.0及以上版本中,像这样在清单文件中注册接收网络连接变化事件的方法将不再起作用。请参考以下链接了解更多信息:https://developer.android.com/training/monitoring-device-state/connectivity-monitoring.html#MonitorChanges - w3bshark
2
发现了一个关于网络连接的特殊情况,即显示没有互联网连接,但实际上是有的。原来,在电池电量低且应用程序刚刚切换时更改网络时,getActiveNetworkInfo将始终返回DISCONNECTED/BLOCKED。请查看此帖子:https://dev59.com/nJPea4cB1Zd3GeqP_y96#45231746 - Phil
显示剩余6条评论

70

在api 28+中,ConnectivityAction已被弃用。相反,您可以使用registerDefaultNetworkCallback,只要您支持api 24+。

Kotlin中:

val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityManager?.let {
    it.registerDefaultNetworkCallback(object : ConnectivityManager.NetworkCallback() {
        override fun onAvailable(network: Network) {
            //take action when network connection is gained
        }
        override fun onLost(network: Network?) {
            //take action when network connection is lost
        }
    })
}

4
正确!这是2019年最有效的答案 :) - gunjot singh
6
调用需要API级别24,对于2019-2020年仍然非常重要。 - Vlad
1
@Vlad,你可以使用connectivityManager.registerNetworkCallback - user924
1
还有一个ConnectivityManager.registerNetworkCallback适用于21+。 - user924
3
当我们只有单个连接时,比如移动数据或Wi-Fi,这个功能可以正常工作。但是当我同时使用两者并关闭其中一个时,它仍然会触发onLost(),尽管我仍然有网络连接。你知道为什么会发生这种情况吗?或者我们该如何处理这种情况? - Srushti
@Srushti,你能找出解决方案吗? - krisDrOid

49

以下是使用 registerDefaultNetworkCallback(对于 API < 24,还有 registerNetworkCallback)的 Java 代码:

ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
    @Override
    public void onAvailable(Network network) {
        // network available
    }

    @Override
    public void onLost(Network network) {
        // network unavailable
    }
};

ConnectivityManager connectivityManager =
        (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    connectivityManager.registerDefaultNetworkCallback(networkCallback);
} else {
    NetworkRequest request = new NetworkRequest.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();
    connectivityManager.registerNetworkCallback(request, networkCallback);
}

3
NetworkRequest 需要最低 API 级别为 21。对于 API 级别为 19,我们能做些什么? - Aliton Oliveira
这个需要添加到广播接收器中吗?它确实注册了回调,所以我有点困惑。 - A_rmas
1
除非您有保持支持API 19的理由,否则我真的建议停止支持低于21的API,因为(根据Google)94.1%的Android用户使用API 21及以上版本。此外,API 21是许多新API的一个非常好的起点,您会惊讶地发现可以从代码库中删除多少不必要的向后兼容性代码。 - Heysem Katibi
@algrid 我想确认这个在物理设备上可以运行,但模拟器上无法运行。当模拟器失去连接时,onAvailable 也会被触发。 - I.Step
这段代码在网络变化时运行良好,但是无法检测网络的初始状态。当应用程序启动时,回调函数不会被调用。有什么解决方法吗? - hiddeneyes02

44

更新:

针对Android 7.0(API级别24)及以上版本的应用程序,如果它们在清单文件中声明广播接收器,则不会接收到CONNECTIVITY_ACTION广播。如果应用程序使用Context.registerReceiver()注册其广播接收器且上下文仍然有效,则仍将接收到CONNECTIVITY_ACTION广播。

您需要通过registerReceiver()方法注册接收器:

 IntentFilter intentFilter = new IntentFilter("android.net.conn.CONNECTIVITY_CHANGE");
 mCtx.registerReceiver(new NetworkBroadcastReceiver(), intentFilter);

即使在8.0上使用此代码,我的广播接收器仍未被调用。 - TheRealChx101
@TheRealChx101,如果上下文仍然有效,你应该可以继续。你使用了什么上下文? - Tomer Petel
@TheRealChx101,“android.net.conn.CONNECTIVITY_CHANGE”字符串实际上是CONNECTIVITY_ACTION常量。正如您在https://developer.android.com/reference/android/net/ConnectivityManager#CONNECTIVITY_ACTION中看到的那样,此常量已在API 28中弃用。您的问题可能与此有关吗? - ban-geoengineering
如果我想检测网络类型,例如2G、4G,这个答案可以用吗? - Hoo

32

这应该可以运作:

public class ConnectivityChangeActivity extends Activity {

    private BroadcastReceiver networkChangeReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Log.d("app","Network connectivity change");
        }
    };

    @Override
    protected void onResume() {
        super.onResume();

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
        registerReceiver(networkChangeReceiver, intentFilter);
    }

    @Override
    protected void onPause() {
        super.onPause();

        unregisterReceiver(networkChangeReceiver);
    }
}

3
谢谢!以下是几行简洁的最简单解决方案。 - iFederx
如何检查互联网连接或断开? - pa1.Shetty
1
@pa1.Shetty 我使用了这个答案来确定是否有连接:https://dev59.com/j2855IYBdhLWcg3woVw_#4239019 - Kes Walker
这并不告诉你互联网是否可用。它只告诉你是否有网络连接。 - Ali Kazi
如果我想检测网络类型,例如2G、4G,我可以使用这个答案吗? - Hoo

9

我将这种方法用作连接监听器。适用于Lollipop+,Android JAVA语言。

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
    ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkRequest networkRequest = new NetworkRequest.Builder().build();
    connectivityManager.registerNetworkCallback(networkRequest, new ConnectivityManager.NetworkCallback() {
        @Override
        public void onAvailable(Network network) {
            super.onAvailable(network);
            Log.i("Tag", "active connection");
        }

        @Override
        public void onLost(Network network) {
            super.onLost(network);
            Log.i("Tag", "losing active connection");
            isNetworkConnected();
        }
    });
}

private boolean isNetworkConnected() {
    ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    if (!(cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected())) {
        //Do something
        return false;
    }
    return true;
}

还需要在你的Android Manifest.xml文件中添加这个权限。

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

2
根据评论者的说法:“当我们只有单个连接即移动数据或Wifi时,这很好用。但是当我同时使用两者并关闭其中一个时,它仍会触发onLost(),而我确实有互联网连接。” 我猜测代码的第二部分isNetworConnected可以防止误报,对吗? - Delark
1
是的。有时候第一个方法会出问题。因此我添加了isNetworkConnected()来确保连接不会丢失! - Lakpriya Senevirathna
2
@Delark,在我的情况下,如果手机同时连接两者,只有在关闭wifi时才会调用onLost()。只要wifi开启,移动数据状态改变不会触发回调。像素3a XL,安卓12。 - Sam
2
谢谢@Sam,我的待办事项列表出了一些问题,我认为这可能与流上的某些并发问题有关,但现在我想想,这很可能是问题所在,因为我的wifi有时会断开连接,我认为它也可能出现在仅使用wifi连接的非SIM设备中,因为我已经尝试重新连接到wifi(应该正常工作),但没有反应发生,并且需要进行硬监听器重新连接(取消注册然后注册)。 - Delark
2
@Sam 顺便提醒一下,不要忘记在重新连接变为 true 之前同步布尔输出,因为 200 毫秒回调“分派”一个 false。 - Delark
显示剩余3条评论

5

你好,这里是2022年。

在我的自定义视图模型中,我会像这样观察网络状态的变化:

public class MyViewModel extends AndroidViewModel {
    private final MutableLiveData<Boolean> mConnected = new MutableLiveData<>();

    public MyViewModel(Application app) {
        super(app);

        ConnectivityManager manager = (ConnectivityManager)app.getSystemService(Context.CONNECTIVITY_SERVICE);

        if (manager == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
            mConnected.setValue(true);
            return;
        }

        NetworkRequest networkRequest = new NetworkRequest.Builder()
            .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
            .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
            .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
            .build();

        manager.registerNetworkCallback(networkRequest, new ConnectivityManager.NetworkCallback() {
            Set<Network> availableNetworks = new HashSet<>();

            public void onAvailable(@NonNull Network network) {
                availableNetworks.add(network);
                mConnected.postValue(!availableNetworks.isEmpty());
            }

            public void onLost(@NonNull Network network) {
                availableNetworks.remove(network);
                mConnected.postValue(!availableNetworks.isEmpty());
            }

            public void onUnavailable() {
                availableNetworks.clear();
                mConnected.postValue(!availableNetworks.isEmpty());
            }
        });
    }

    @NonNull
    public MutableLiveData<Boolean> getConnected() {
        return mConnected;
    }
}

在我的 Activity 或 Fragment 中,通过观察,我可以改变用户界面:

@Override
protected void onCreate(Bundle savedInstanceState) {
    MyViewModel vm = new ViewModelProvider(this).get(MyViewModel.class);

    vm.getConnected().observe(this, connected -> {
        // TODO change GUI depending on the connected value
    });
}

1
这个答案很好,只需在activity/fragment中实现它以监听网络变化,并在值为true/false时执行某些操作。 - Liew Syet Chau
1
当调用onLost时,假设没有连接是不正确的。如果有Wifi和蜂窝网络,onAvailable会被调用两次。当你失去Wifi时,会调用onLost,但你仍然有蜂窝网络。查看Xid的答案以了解如何正确处理这个问题。它完美地工作。 - Ridcully

4
  1. 首先在你的代码中添加依赖项:implementation 'com.treebo:internetavailabilitychecker:1.0.4'
  2. 让你的类实现 InternetConnectivityListener 接口。
public class MainActivity extends AppCompatActivity implements InternetConnectivityListener {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        InternetAvailabilityChecker.init(this);
        mInternetAvailabilityChecker = InternetAvailabilityChecker.getInstance();
        mInternetAvailabilityChecker.addInternetConnectivityListener(this);
    }

    @Override
    public void onInternetConnectivityChanged(boolean isConnected) {
        if (isConnected) {
            alertDialog = new AlertDialog.Builder(this).create();
            alertDialog.setTitle(" internet is connected or not");
            alertDialog.setMessage("connected");
            alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    });
            alertDialog.show();

    }
    else {
            alertDialog = new AlertDialog.Builder(this).create();
            alertDialog.setTitle("internet is connected or not");
            alertDialog.setMessage("not connected");
            alertDialog.setButton(AlertDialog.BUTTON_NEUTRAL, "OK",
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    });
            alertDialog.show();

        }
    }
}

3
我注意到没有人提到更好并且支持大多数Android设备的WorkManager解决方案。
你应该有一个带有网络约束条件的Worker,只有在网络可用时才会触发,例如:
val constraints = Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()
val worker = OneTimeWorkRequestBuilder<MyWorker>().setConstraints(constraints).build()

在 worker 中,一旦连接恢复,您可以随意执行任何操作,您可以定期触发 worker。

例如:

dowork() 回调函数中:

notifierLiveData.postValue(info)

3

根据官方文档

  1. 定义网络请求
private val networkRequest = NetworkRequest.Builder().apply {
    // To check wifi and cellular networks for internet availability
    addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
    addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
    addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Capabilities can be verified starting Android 6.0.
        // For a network with NET_CAPABILITY_INTERNET, 
        // it means that Internet connectivity was successfully detected
        addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
    }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
        // Indicates that this network is available for use by apps,
        // and not a network that is being kept up in the background 
        // to facilitate fast network switching.
        addCapability(NetworkCapabilities.NET_CAPABILITY_FOREGROUND)
    }
}.build()
  1. 配置网络回调函数
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
    private val networks = mutableListOf<Network>()

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

        networks.add(network)
        Log.d("Has network --->", networks.any().toString())
    }

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

        networks.remove(network)
        Log.d("Has network --->", networks.any().toString())
    }
}
  1. 注册网络更新
val connectivityService =
    applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
connectivityService.registerNetworkCallback(networkRequest, networkCallback)

1
网络实现了hashCode和equals,因此在onLost()中,一个简单的networks.remove(network)就足够了。 - Ridcully

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接