开发应用过程中,我们往往需要获取/监听手机的网络状态,而RN提供的NetInfo api随着sdk的升级往往会出现许多的问题。所以本文将介绍如何按需自行编写获取手机网络状态的原生代码。
对于网络相关的功能,往往涉及到以下几个部分:
1. 主动获取手机的连接状态(是否联网,连接的是移动蜂窝网还是WiFi);
2. 监听网络变化(蜂窝网、WiFi、网络断开连接等),执行相应的操作(例如提示用户);
3. 获取SSID(手机连接的WiFi名称)以及用户ip地址等;
一. 主动获取手机的连接状态
在Android中获取手机的连接状态,需要通过ConnectivityManager实例以及NetworkInfo实例获取:
ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connectivityManager.getActivityNetworkInfo();
boolean isConnected = networkInfo != null && netwrokInfo.isConnected(); // 是否联网
boolean isConnectWifi = networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI; // 是否连接的WiFi
boolean isConnectedMobile = networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_MOBILE;; // 是否连接的移动蜂窝网
二. 监听网络变化
监听网络变化一般都是通过在MainActivity中通过监听广播实现:
private IntentFilter connectiveIntentFilter;
private ConnectiveBroadcastReceiver connectiveBroadcastReceiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 监听网络状态变化的广播
connectiveIntentFilter = new IntentFilter();
connectiveIntentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
connectiveBroadcastReceiver = new ConnectiveBroadcastReceiver();
registerReceiver(connectiveBroadcastReceiver, connectiveIntentFilter);
}
class ConnectiveBroadcastReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// 网络发生变化执行回调
}
}
接着可以在onReceive方法中通过NetInfo api获取变化后的网络属性。但是监听ConnectivityManager.CONNECTIVITY_ACTION这个广播有一个问题,就是当用户网络状态由移动网络切换到WiFi时,实际上会监听到两次广播:第一次是网络关闭,第二次则是网络切换到WiFi。这在App中需要实时提示用户网络连接状态的时候会非常恶心。
后来经过查阅官方文档,发现NetworkRequest api可以很好的解决这个问题,直接上代码,在自定义的NetUtils.java文件中:
/**
* 监听网络变化(移动蜂窝网和WiFi)
*/
public void registerNetworkChangeListener(Context context, NetworkChangeListener listener) {
ConnectivityManager connectivityManager = getConnectivityManager(context);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest request = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build();
connectivityManager.requestNetwork(request, new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
if (isWifiConnected((context))) {
Log.d(TAG, "Wifi connected!");
if (listener != null) listener.onWiFiAvailable();
} else if (isMobileConnected(context)) {
Log.d(TAG, "Cellular connected!");
if (listener != null) listener.onCellularAvailable();
} else {
Log.d(TAG, "Not connected!");
}
}
});
}
public interface NetworkChangeListener {
void onWiFiAvailable();
void onCellularAvailable();
}
NetworkChangeListener接口定义了两个方法签名,onWiFiAvailable是当监听到网络切换到WiFi时需要执行的代码。onCellularAvailable则是监听到网络切换到移动网络时需要执行的代码。
使用如下, MainActivity中:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
NetworkUtils.getInstance().registerNetworkChangeListener(this, new NetworkUtils.NetworkChangeListener() {
@Override
public void onWiFiAvailable() {
sendNetChange("WIFI");
}
@Override
public void onCellularAvailable() {
sendNetChange("WWAN");
}
});
}
private void sendNetChange(String netState) {
try {
getReactInstanceManager().getCurrentReactContext().getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("netChange", netState);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
在RN端,在入口组件(例如Splash.js,APP启动时加载,组件中可以根据登录状态动态加载不同的路由栈)中获取原生层发送过来的事件:
const networkChangeFunc = (netState) => {
LogUtil("netChange: ", netState);
changeNetType(actionCreator.networkChange(netState));
}
useEffect(() => {
// 应用初始加载,主动获取一次当前网络状态
getNetwork().then(netState => {
networkChangeFunc(netState);
});
// 应用运行期间监听网络变化
if (isAndroid) {
DeviceEventEmitter.addListener("netChange", (netState) => {
networkChangeFunc(netState);
});
} else {
const eventEmitter = new NativeEventEmitter(NativeModules.ToolModule);
eventEmitter.addListener("netChange", (netState) => {
networkChangeFunc(netState);
});
}
}, []);
我这里是将原生层监听到的网络变化保存到redux中。这样应用中任何子视图组件如果想要获取/监听当前的网络状态,直接到redux拿即可。不过需要注意的是,如果使用了redux-persist,记得把全局状态对象中保存网络状态的键添加到黑名单中。
3. 获取SSID(手机连接的WiFi名称)以及用户ip地址
Android中获取SSID需要通过WiFiManager实例和WiFiInfo实例完成,也很简单,唯一需要注意的是,在Android 9.0以上的系统中,获取SSID之前必须向用户动态申请位置权限(官方文档的解释是,开发人员可能会通过用户手机的WiFi信息获取到用户的位置等隐私信息,既然如此,不如就限制开发人员在获取WiFi相关的信息前必须向用户请求获取地理位置权限,从而达到告知用户风险,并由用户决定是否授予权限,保护用户隐私的目的) 。
AndroidManifest.xml中添加权限:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
获取SSID:
WifiManager wifiManager = (WifiManager)context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
String ssid = wifiInfo.getSSID().replace("\"", "");
一定要记得在使用上面代码前确保用户已经授予地理位置权限。至于动态权限检查以及申请,使用RN提供的PermissionsAndroid或者是安卓原生的api,还是使用第三方库Easy Permission都可以。
另外,当我们需要用户连接特定的WiFi时,如果监听到用户打开的是移动网络,可以在App内添加一个按钮,帮助用户快速拉起系统设置WiFi的Activity。代码如下:
/**
* 跳转设置WiFi界面
*/
@ReactMethod
public void jumpToWiFiSettings() {
Intent intent = new Intent(Settings.ACTION_WIFI_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
mContext.startActivityForResult(intent, MainActivity.REQUEST_SETTING_CODE, null);
}
如果想要拉起设置应用的其他Activity,只需要修改对应intent的Action即可。
以上就是本文的全部内容,最后贴一下我目前在项目中用到的网络相关的工具类NetUtils.java,基本能够满足我开发中的需求,如果有问题的话,欢迎小伙伴们提出来哦~
public class NetworkUtils {
private final String TAG = "NetworkUtils";
private NetworkUtils() {}
public static NetworkUtils getInstance() {
return NetUtilsBuilder.instance;
}
private static class NetUtilsBuilder {
private static final NetworkUtils instance = new NetworkUtils();
}
/**
* 获取ConnectivityManager实例
*/
private ConnectivityManager getConnectivityManager(Context context) {
return (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);
}
/**
* 获取NetworkInfo实例
*/
private NetworkInfo getNetworkInfo(Context context) {
return getConnectivityManager(context).getActiveNetworkInfo();
}
/**
* 获取WifiManager实例
*/
private WifiManager getWifiManager(Context context) {
return (WifiManager)context.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
}
/**
* 获取WifiInfo实例
*/
private WifiInfo getWifiInfo(Context context) {
return getWifiManager(context).getConnectionInfo();
}
/**
* 网络是否可用
*/
public boolean isConnected(Context context) {
NetworkInfo networkInfo = getNetworkInfo(context);
return networkInfo != null && networkInfo.isConnected();
}
/**
* 判断当前是否连接的wifi
*/
public boolean isWifi(Context context) {
NetworkInfo networkInfo = getNetworkInfo(context);
return networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI;
}
/**
* 判断当前是否连接的移动网络
*/
public boolean isMobile(Context context) {
NetworkInfo networkInfo = getNetworkInfo(context);
return networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_MOBILE;
}
/**
* 是否连接的wifi且网络可用
*/
public boolean isWifiConnected(Context context) {
return isConnected(context) && isWifi(context);
}
/**
* 是否连接的移动数据且网络可用
*/
public boolean isMobileConnected(Context context) {
return isConnected(context) && isMobile(context);
}
/**
* 获取网络类型
*/
public String getNetworkType(Context context) {
if (isWifiConnected(context)) return "WIFI";
if (isMobileConnected(context)) return "WWAN";
return "NONE";
}
/**
* 获取WiFi名称
* tips: in Android 8.0 or higher needs the permission of ACCESS_FINE_LOCATION, or you'll get unknown ssid
*/
public String getSSID(Context context) {
return getWifiInfo(context).getSSID().replace("\"", "");
}
/**
* 获取本机ip地址
*/
public String getLocalIp(Context context) {
return ipFormatter(getWifiInfo(context).getIpAddress());
}
private String ipFormatter(int ip) {
return (ip & 0xFF) + "." + ((ip >> 8) & 0xFF) + "." + ((ip >> 16) & 0xFF) + "." + ((ip >> 24) & 0xFF);
}
/**
* 监听网络变化(移动蜂窝网和WiFi)
*/
public void registerNetworkChangeListener(Context context, NetworkChangeListener listener) {
ConnectivityManager connectivityManager = getConnectivityManager(context);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
NetworkRequest request = builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build();
connectivityManager.requestNetwork(request, new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
super.onAvailable(network);
if (isWifiConnected((context))) {
Log.d(TAG, "Wifi connected!");
if (listener != null) listener.onWiFiAvailable();
} else if (isMobileConnected(context)) {
Log.d(TAG, "Cellular connected!");
if (listener != null) listener.onCellularAvailable();
}
}
@Override
public void onUnavailable() {
super.onUnavailable();
Log.d(TAG, "onUnavailable");
}
@Override
public void onLosing(Network network, int maxMsToLive) {
super.onLosing(network, maxMsToLive);
Log.d(TAG, "onLosing");
}
@Override
public void onLost(Network network) {
super.onLost(network);
Log.d(TAG, "onLost");
}
});
}
public interface NetworkChangeListener {
void onWiFiAvailable();
void onCellularAvailable();
}
}