本文译自:http://developer.android.com/training/basics/network-ops/managing.html
本文介绍如何编写细粒度的应用程序,以便控制网络资源的使用。如果你的应用程序执行很多网络操作,那么你应该给用户提供设置功能,允许用户来控制应用程序的数据处理方式,如应用程序是否经常同步数据;是否是在只有Wi-Fi时才上传或下载数据;手机漫游时,是否使用数据等等。把这些控制提供给用户,用户很少会在流量达到极限时禁止你的应用程序访问后台的数据,因为他们不能够精确的计算你的应用程序对数据流量的使用情况。
对于如何编写最小化的消耗电池电量的下载和网络连接的应用程序,请看“优化电池消耗”和“不消耗电池电量的数据传输”
检查设备的网络连接
一个设备可以有各种类型的网络连接。本文关注Wi-Fi和移动网络的连接。完整的可能的网络类型,请看ConnectivityManager。
通常,Wi-Fi比较快。而且移动数据经常是计费的,这可能很昂贵。应用程序通常的策略是只有在Wi-Fi网络有效时,才获取大数据。
在执行网络操作之前,检查网络连接的状态是一种好的实践。这样可以防止你的应用程序无意的使用了错误的网络收音机。如果网络连接时无效的,你的应用程序应该给用户提供良好的响应。通常使用以下类来检查网络连接:
1. ConnectivityManager:这个类可以提供网络连接的状态。在网络连接发生变化时,它也会通知应用程序。
2. NetworkInfo:说明给定类型的网络接口的状态(当前可以是移动网络,也可以是Wi-Fi网络)
以下代码片段用于检查Wi-Fi和移动网络的连接。它判断这些网络接口是否有效(也就是说,判断是否可以建立网络连接)或已连接(也就是说,判断网络连接是否存在,并是否可以建立套接字和传递数据):
privatestaticfinalString 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);
注意,网络是否有效,不应该是你决策的基础。在网络操作之前,应该始终调用isConnected()方法来检查网络连接,因为isConnected()方法会处理移动网络、飞行模式,以及受限制的后台数据等情况。
以下是检查网络接口是否有效的更简洁的方法。GetActiveNetworkInfo()方法返回的NetworkInfo实例代表了它能够找到的第一个被连接的网络接口,如果返回null,则没有网络接口被连接(这意味着互联网连接无效):
publicboolean isOnline(){
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
使用NetworkInfo.DetailedState可以查询更详细的状态信息,但这几乎不需要。
管理网络的使用
你可以实现一个用于设置的Activity,以便用户明确的控制你的应用程序的网络资源的使用。例如:
1. 你可以让用户只有在设备连接到Wi-Fi网络时才上传视频;
2. 你可以根据指定的规范,如可用的网络、时间间隔等来同步(或不同步)数据。
要编写一个支持网络访问和管理网络使用的应用程序,你的清单文件必须要申请正确的权限和Intent过滤器。
以下摘录了清单中包含的权限:
android.permission.INTERNET---允许应用程序打开网络套接字。
android.permission.ACCESS_NETWORK_STATE---允许应用程序访问有关网络的信息。
你可以声明用于过滤ACTION_MANAGE_NETWORK_USAGE类型动作(在Android4.0中被引入)的Intent过滤器,指明你的应用程序定义了一个提供控制数据使用选项的Activity。ACTION_MANAGE_NETWORK_USAGE动作会显示特定应用程序中,用于管理网络数据使用的设置选项。当你的应用程序有了这个允许用户控制网络使用的设置Activity时,就应该给这个Activity设置这种类型的Intent过滤器。在示例应用程序中,这个操作是由SettingsActivity类来处理的,它会显示一个让用户决定的设置用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>
实现一个设置用的Activity
在上面的清单摘要中可以以看到,该示例应用程序的Activity---SettingsActivity有一个适用ACTION_MANAGE_NETWORK_USAGE动作的Intent过滤器。SettingsActivity是PreferenceActivity的子类。它会显示一个设置界面(如下图1所示),让用户指定以下设置:
1. 是否显示每个XML条目的概要,或只是显示每个条目的连接;
2. 如果网络连接有效时,是否下载XML数据,或是只有在Wi-Fi时才有效。
图1.设置Activity
注意,SettingsActivity实现了OnSharePreferenceChangeListener接口。当用户改变设置时,它会触发onSharedPreferenceChanged()方法,它把refreshDisplay设置为true。这会导致用户返回主Activity时显示的刷新:
publicclassSettingsActivityextendsPreferenceActivityimplementsOnSharedPreferenceChangeListener{
@Override
protected void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
// Loads theXML preferences file
addPreferencesFromResource(R.xml.preferences);
}
@Override
protected void onResume() {
super.onResume();
// Registersa listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
//Unregisters the listener set in onResume().
// It's bestpractice to unregister listeners when your app isn't using them to cut down on
//unnecessary system overhead. You do this in onPause().
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
}
// When the user changesthe preferences selection,
//onSharedPreferenceChanged() restarts the main activity as a new
// task. Sets the refreshDisplayflag to "true" to indicate that
// the main activityshould update its display.
// The main activityqueries the PreferenceManager to get the latest settings.
@Override
public voidonSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// SetsrefreshDisplay to true so that when the user returns to the main
// activity,the display refreshes to reflect the new settings.
NetworkActivity.refreshDisplay = true;
}
}
响应设置的改变
当用户改变这个设置界面中的设置选项时,它会影响应用程序的行为。在本段中,应用程序会onStart()方法中检查选项设置。如果有跟设置匹配的设备网络连接(例如,如果设置是Wi-Fi,并且设备的Wi-Fi连接有效),应程序就可以下载数据,并刷新显示。
publicclassNetworkActivityextendsActivity{
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";
// Whether there is aWi-Fi connection.
private static boolean wifiConnected = false;
// Whether there is amobile connection.
private static boolean mobileConnected = false;
// Whether the displayshould be refreshed.
public static boolean refreshDisplay = true;
// The user's currentnetwork preference setting.
public static String sPref = null;
// The BroadcastReceiverthat tracks network connectivity changes.
private NetworkReceiverreceiver = new NetworkReceiver();
@Override
public void onCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
// RegistersBroadcastReceiver to track network connection changes.
IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
receiver = new NetworkReceiver();
this.registerReceiver(receiver, filter);
}
@Override
public void onDestroy() {
super.onDestroy();
//Unregisters BroadcastReceiver when app is destroyed.
if (receiver != null) {
this.unregisterReceiver(receiver);
}
}
// Refreshes the displayif the network connection and the
// pref settings allow it.
@Override
public void onStart () {
super.onStart();
// Gets theuser's network preference settings
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
// Retrievesa string value for the preferences. The second parameter
// is thedefault value to use if a preference value is not found.
sPref = sharedPrefs.getString("listPref", "Wi-Fi");
updateConnectedFlags();
if(refreshDisplay){
loadPage();
}
}
// Checks the networkconnection and sets the wifiConnected and mobileConnected
// variables accordingly.
public voidupdateConnectedFlags() {
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;
}
}
// Uses AsyncTask subclassto download the XML feed from stackoverflow.com.
public void loadPage() {
if (((sPref.equals(ANY)) && (wifiConnected ||mobileConnected))
|| ((sPref.equals(WIFI)) && (wifiConnected))) {
// AsyncTask subclass
new DownloadXmlTask().execute(URL);
} else {
showErrorPage();
}
}
...
}
检测连接的变化
最后的问题是BroadcastReceiver之类---NetworkReceiver。当设备的网络连接变化时,NetworkReceiver会截获CONNECTIVITY_ACTION动作,来判断网络连接的状态,并相应把wifiConnected和mobileConnected标记设置为true或false。这样做的结果是,下次用户返回该App时,该应用程序只会下载最新的数据,并且如果NetworkActivity.refreshDisplay被设置为true,就会显示更新。
设置不必要的广播接收器被认为是系统资源的浪费。该示例应用程序在onCreate()方法中注册了广播接收器---NetworkReceiver,在onDestroy()方法中解除注册。这比在清单中声明<receiver>要轻量。当在清单中声明一个<receiver>时,会在任何时候唤醒你的应用程序,即使它很久没有被运行。通过在主Activity中注册和解除注册NetworkReceiver广播接收器,可以确保在用户退出应用程序后不被唤醒。如果在清单中声明了<receiver>,并且你清楚的知道需要它的地方,就可以使用setComponentEnabledSetting()方法来启用和禁用它。
以下是NetworkReceiver接收器:
publicclassNetworkReceiverextendsBroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
ConnectivityManager conn = (ConnectivityManager)
context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = conn.getActiveNetworkInfo();
// Checks the user prefsand the network connection. Based on the result, decides whether
// to refresh the displayor keep the current display.
// If the userpref isWi-Fi only, checks to see if the device has a Wi-Fi connection.
if (WIFI.equals(sPref) && networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI) {
// If devicehas its Wi-Fi connection, sets refreshDisplay
// to true.This causes the display to be refreshed when the user
// returnsto the app.
refreshDisplay = true;
Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show();
// If the setting is ANYnetwork and there is a network connection
// (which by process ofelimination would be mobile), sets refreshDisplay to true.
} else if (ANY.equals(sPref) && networkInfo != null) {
refreshDisplay = true;
// Otherwise, the appcan't download content--either because there is no network
// connection (mobile orWi-Fi), or because the pref setting is WIFI, and there
// is no Wi-Fi connection.
// Sets refreshDisplay tofalse.
} else {
refreshDisplay = false;
Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show();
}
}