检测设备的网络连接
一个设备有各种类型的网络连接。本篇只关注wifi和移动网络连接。
WIFI通常情况下很快,而移动数据通常按量收费,还很昂贵。APP的通常使用策略是在WIFI网络可用的情况下才去获取大量的数据。
在执行网络操作之前,最好是检查一下网络的连接状态。执行网络状态检查,通常会使用到下面的类:
· ConnectivityManager:可以获取当前网络的连接状况,还可以在网络连接状况发生变化时通知应用程序。
· NetworkInfo:描述了指定类型的网络接口状态。
例如:
ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_WIFI);
boolean isWifiConn = networkInfo.isConnected();
networkInfo = connMgr.getNetworkInfo(ConnectivityManager.TYPE_MOBILE);
boolean isMobileConn = networkInfo.isConnected();
Log.d(DEBUG_TAG,"Wifi connnected:"+isWifiConn);
Log.d(DEBUG_TAG,"Mobile connected:"+isMobileConn);
注意:不应该关注网络是否可用,而是应该在每次执行网络操作之前检查isConnected(),因为isConnected()会处理这些状态:移动网络信号不好、飞行模式或者受限的后台数据。
有一个更简明的方法查看网络是否可用。getActiveNetworkInfo()方法会返回一个NetworkInfo的实例,这个对象代表了所能搜索到的第一个已连接的网络接口,如果没有搜索到任何网络连接则会返回null,null代表了互联网络连接不可用。
public boolean isOnline() {
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
管理网络使用
你可以通过实现一个参数设置的Activity来让用户控制网络资源的使用。
· 你可能只允许用户在WIFI网络状态下才可以上传视频资源。
· 你可能要允许用户设置在指定的条件下才去同步数据,比如:网络可用状态下,或者隔多长时间等等。
·为了使应用可以支持网络的访问和网络管理,你的manifest必须有以下权限和intent过滤器。
- android.permission.INTERNET—允许应用打开网络sockets。
- android.permission.ACCESS_NETWORK_STATE—允许应用访问网络信息。
· 你可以通过声明ACTION_MANAGE_NETWORK_USAGE的Intetn过滤器来指明当前的Activity提供了控制数据使用策略的功能。当应用中含有允许用户管理网络数据使用策略的Activity时,应当声明该Intent过滤器。在这里的示例程序中,这个行为被SettingsActivity所处理,这个Activity允许用户决定什么时候开始下载。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.networkusage"
...>
<uses-sdk android:minSdkVersion="4"
android:targetSdkVersion="14" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
...>
...
<activity android:label="SettingsActivity" android:name=".SettingsActivity">
<intent-filter>
<action android:name="android.intent.action.MANAGE_NETWORK_USAGE" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>
实现一个偏好参数配置activity
它实现了OnSharedPreferenceChangeListener接口。每当用户更改了参数,系统会调用onSharedPreferenceChanged()方法。
public class SettingsActivity extends PreferenceActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//加载一个preferences文件
addPreferencesFromResource(R.xml.preferences);
}
@Override
protected void onResume() {
super.onResume();
//注册一个监听,监听key改变
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
//注销在onResume()方法里注册的监听
//这是一个好习惯,当你的应用不使用它们取消
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
//当用户改变了首选项,onSharedPreferenceChanged()
//会重新开始。为了显示设置refreshDisplay为true
//这个主activiity应该更新显示
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
//设置refreshDisplay为true为了当使用者回到mainactivity使,
//新的设置能刷新显示
NetworkActivity.refreshDisplay = true;
}
}
响应参数的变更
当用户更改了参数时,这个行为会使APP的习性也跟着发生了变化。在下面的代码段中,APP会在onStart()方法中检查参数配置,如果在设备的当前连接状态与设置之间有相匹配的,那么APP将会下载信息,并刷新界面。
public class NetworkActivity extends AppCompatActivity{
public static final String WIFI = "Wi-Fi";
public static final String ANY = "Any";
private static final String URL = "";
//wifi是否连接
private static boolean wifiConnected = false;
//移动数据是否连接
private static boolean mobileConnected = false;
//是否应该更新显示
public static boolean refreshDisplay;
//BroadcastReceiver监听网络连接的变化
private NetworkReceiver receiver;
public static String sPref;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
receiver = new NetworkReceiver();
this.registerReceiver(receiver,filter);
}
@Override
protected void onDestroy() {
super.onDestroy();
//注销BroadcastReceiver
if(receiver!=null){
this.unregisterReceiver(receiver);
}
}
//如果网络连接pref设置允许他刷新显示
@Override
protected void onStart() {
super.onStart();
//获取使用者的首选项设置
SharedPreferences sharePrefs = PreferenceManager
.getDefaultSharedPreferences(this);
//检索首选项的字符串值。如果未找到偏好值,
// 则使用第二个参数为默认值
sPref = sharePrefs.getString("listPref","Wi-Fi");
updateConnectedFlags();
if(refreshDisplay){
loadPage();
}
}
//检查网络连接并且设置相对应的wifiConnected和mobileConnected
public void updateConnectedFlags() {
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeInfo = connMgr.getActiveNetworkInfo();
if (activeInfo != null && activeInfo.isConnected()) {
wifiConnected = activeInfo.getType() == ConnectivityManager.TYPE_WIFI;
mobileConnected = activeInfo.getType() == ConnectivityManager.TYPE_MOBILE;
} else {
wifiConnected = false;
mobileConnected = false;
}
}
//使用异步处理数据
private void loadPage() {
if (((sPref.equals(ANY)) && (wifiConnected || mobileConnected))
|| ((sPref.equals(WIFI)) && (wifiConnected))) {
//处理数据
} else {
//展示错误
}
}
}
监听连接变化
最后一个问题就是BroadcastReceiver的子类NetworkReceiver。当设备的网络连接发生变化时,NetworkReceiver会拦截CONNECTIVITY_ACTION的行为,这个行为用于检查当前是哪种网络连接状态,并会相应的将wifiConnected和mobileConnected设置为true或者false。那么在NetworkActivity.refreshDisplay设置为true时,那么APP会只下载最近一次的资源。
设置的广播监听器需要在系统不需要的情况下解除注册。示例应用中在onCreate()方法中将NetworkReceiver注册到系统,在onDestroy()方法中将其注销。这比在清单文件中注册更为轻量。当在清单文件中声明了广播接收器,系统会在任何时候调用该接收器,甚至是很久都没有启动过。在Activity中注册与注销广播接收器,可以确保用户在离开APP后系统不会再调用广播接收器。如果在清单文件中注册了广播接收器,那么你必须清楚在什么地方需要它,你可以适当的使用setComponentEnabledSetting()方法来开启或者关闭它。
public class NetworkReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager conn = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = conn.getActiveNetworkInfo();
//检查使用者的prefs 并且进行网络连接。
// 根据结果决定是否更新显示
//如果如果userpref只有Wi-Fi,检查是否有Wi-Fi连接的设备
if(WIFI.equals(sPref) && networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI){
refreshDisplay = true;
Toast.makeText(context,"wifi已连接",Toast.LENGTH_SHORT).show();
} else if(ANY.equals(sPref)&&networkInfo!=null){
refreshDisplay = true;
} else {
refreshDisplay = false;
Toast.makeText(context, "无网络连接", Toast.LENGTH_SHORT).show();
}
}
}