如果你的程序要执行大量的网络操作,你应该提供用户设置来允许用户控制你应用程序数据的习惯。 比如你的程序多久会进行数据同步,是否只有在拥有Wi-Fi的情况下才执行上传和下载操作,是否使用数据漫游等等。将这些控制变量提供给他们,当用户接近自己的(网络流量)限制时,就不太可能禁用你应用程序对后台数据的访问通道。因为他们可以精确的控制你的应用程序使用多少数据。
所以应用程序的一般策略是只有在Wi-Fi可用的情况下才会获取大量数据
检查网络连接一般使用以下类:
- ConnectivityManager: 响应网络连接状态查询,同时能在网络连接状态发生改变时通知程序。
- NetworkInfo:描述给定类型的网络接口状态(无论是移动网络还是Wi-Fi)
以下代码片段测试了Wi-Fi和移动网络连接,它决定了这些网络接口是否可用(即网络连接是否存在) 和/或 是否已经连接网络(即网络连接存在以及是否有可能建立socket通道并传输数据):
private static final String DEBUG_TAG = "NetworkStatusExample";
...
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 connected: " + isWifiConn);
Log.d(DEBUG_TAG, "Mobile connected: " + isMobileConn);
需要在执行网络操作之前检查isConnectioned() (看该方法返回是否已经网络连接成功了),在连接成功了之后再执行操作(因为有可能你的移动网络不够稳定,手机处于飞行模式,限制后台数据等状态;译者注:在这种情况下,你可能会得到当前网络可用,但是无法连接的情况;)
更简洁的检查网络接口是否可用的方式如下: getActiveNetworkInfo()方法返回一个NetworkInfo实例,这个实例表示的是系统能够找到并连接的第一个网络接口。如果网络接 口是可以连接的,但是getNetworkInfo返回的是null(这表示当前网络连接无效)
public boolean isOnline() {
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
permission权限:
android.permission.INTERNET 允许用户打开网络通道。
android.permission.ACCESS_NETWORK_STATE允许程序访问网络相关信息。
intent filter:
可以为ACTION_MANAGE_NETWORK_USAGE action声明一个intent filter(在Android4.0中有介绍)表明你的应用程序定义了一个提供控制(网络)数据运用选项的activity。 ACTION_MANAGE_NETWORK_USAGE显示了管理特定程序的网络数据运行的设置。当你的应用程序有了一个“设置选项”的activity,用以允许用户控制网络使用,你应该为这个activity定义一个intent filter。在这个简单的例子中,这个action是由SettingActivity类控制的,这个activity显示了一个“设置选项”的UI给用户,让用户决定什么时候下载数据源。
<?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>
这里的SettingActivity,注意它实现了OnSharedPreferenceChangeListener,当用户改变了设置,就会触发java.lang.String) onSharedPreferenceChanged()方法,并在这方法中设置refresDisplay为true。这样导致当用户返回主activity时显示会刷新。
public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 加载XML设定文件(XML preferences file)
addPreferencesFromResource(R.xml.preferences);
}
@Override
protected void onResume() {
super.onResume();
// 每当键发生变化注册一个监听事件
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
// 注销onResume()方法中设置的监听.
// 注销监听最好是当你的程序不使用它们以减少系统不必要的开销, 你可以在onPause()方法中做这件事
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
// 当用户改变设置的选项,onSharedPreferenceChanged()重新启动主activity作为一个新任务,
// 设置refreshDisplay为true以表明主activity应该更新它的显示, 主activity查询PreferenceManager得到最新的设置.
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// 设置refreshDisplay为true,这样当用户返回主activity,显示会根据新设置进行刷新.
NetworkActivity.refreshDisplay = true;
}
}
当用户在设置界面修改了选项时,通常都会对应用程序的行为产生影响。在这节代码片段中,应用在onStart()方法中检查设置选项,如果设置和设备的网络连接能够匹配(比如设置中是“Wi-Fi”并且设备也拥有Wi-Fi连接),应用就下载数据源并刷新显示。
public class NetworkActivity extends Activity {
public static final String WIFI = "Wi-Fi";
public static final String ANY = "Any";
private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest";
// 是否有一个Wi-Fi连接.
private static boolean wifiConnected = false;
// 是否有一个手机移动网络连接.
private static boolean mobileConnected = false;
// 显示是否需要刷新.
public static boolean refreshDisplay = true;
// 用户当前网络优先设置.
public static String sPref = null;
// BroadcastReceiver追踪网络连接变化.
private NetworkReceiver receiver = new NetworkReceiver();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 注册BroadcastReceiver追踪网络连接变化.
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
receiver = new NetworkReceiver();
this.registerReceiver(receiver, filter);
}
@Override
public void onDestroy() {
super.onDestroy();
// 当应用销毁,注销BroadcastReceiver.
if (receiver != null) {
this.unregisterReceiver(receiver);
}
}
// 如果网络连接和优先设置允许,刷新显示。
@Override
public void onStart () {
super.onStart();
// 得到用户网络设置
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
// 获取设置的字符串,第二个参数为当获取不到设置的值时默认使用的值.
sPref = sharedPrefs.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;
}
}
// 使用AsyncTask子类从stackoverflow.com下载XML数据源.
public void loadPage() {
if (((sPref.equals(ANY)) && (wifiConnected || mobileConnected))
|| ((sPref.equals(WIFI)) && (wifiConnected))) {
// AsyncTask的子类
new DownloadXmlTask().execute(URL);
} else {
showErrorPage();
}
}
...
}
简单的应用在onCreate方法中注册了BroadcastReceiver NetworkReceiver,并在onDestroy方法中将其注销。这比在manifest文件中声明<receiver>标签更轻量级
如果你在manifest中声明了一个<receiver>,并且确切的知道在哪里你需要用到它,你可以用int, int) 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();
// 检查用户首选项和网络连接.基于这个结果,决定刷新界面或保持当前显示.
// 如果用户偏好Wi-Fi, 检查设备是否拥有Wi-Fi连接.
if (WIFI.equals(sPref) && networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
// 如果设备有Wi-Fi连接,设置refreshDisplay为true
// 这是因为当用户返回到应用时,显示应该被刷新.
refreshDisplay = true;
Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show();
// 如果设置为任何网络并且这里有一个网络连接,设置refreshDisplay为true.
} else if (ANY.equals(sPref) && networkInfo != null) {
refreshDisplay = true;
// 否则, 应用无法下载内容--可能是因为没有网络连接(mobile或Wi-Fi),
// 或者是应为用户首选项是Wi-Fi,但没有Wi-Fi连接.设置refreshDisplay为false。
} else {
refreshDisplay = false;
Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show();