如果应用有大量网络操作,则应该允许让用户设置喜好,例如应用同步数据的频率,是否仅在使用 Wi-Fi 时执行上传/下载,是否在漫游时使用数据,等等。
一、检查设备的网络连接
设备可以具有各种类型的网络连接,Wi-Fi 或者移动网络。常见策略是仅在 Wi-Fi 网络可用时才获取大数据。
在执行网络操作之前,最好检查网络连接状态。要检查网络连接,通常使用以下类:
ConnectivityManager:
响应有关网络连接状态的查询。它还会在网络连接发生变化时通知应用。NetworkInfo:
描述给定类型的网络接口的状态(当前是移动网络或 Wi-Fi)。
下面代码测试 Wi-Fi 和移动网络的网络连接。它确定这些网络接口是否可用(即是否可以进行网络连接)和是否连接(即是否存在网络连接以及是否可以建立套接字和传递数据):
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);
在执行网络操作之前,应该始终调用 isConnected()
进行检查,因为 isConnected()
处理如片状移动网络,飞行模式和受限制的后台数据之类的情况。
检查网络接口是否可用,更简洁方法如下。该 getActiveNetworkInfo()
方法返回一个 NetworkInfo 实例,表示它可以找到的第一个连接的网络接口,若返回 null 则没有任何连接的接口,意味着网络不可用:
public boolean isOnline() {
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
二、管理网络使用情况
可以实现一个首选项 Activity 使用户可以显式控制对网络资源的使用情况。例如:
你可能只允许用户在设备连接到 Wi-Fi 网络时上传视频。
你可以根据特定条件(例如网络可用性,时间间隔等)同步数据(或不同步)。
要让应用支持网络访问和管理网络,清单文件必须具有正确的权限和意图过滤器。
下面摘录的清单包括以下权限:
android.permission.INTERNET
- 允许应用程序打开网络套接字。android.permission.ACCESS_NETWORK_STATE
- 允许应用程序访问有关网络的信息。
你可以声明意图过滤器操作 ACTION_MANAGE_NETWORK_USAGE
(在 Android 4.0 中引入)来指示某个 Activity 可以设置数据使用。
ACTION_MANAGE_NETWORK_USAGE
显示管理网络数据使用情况的设置。在示例应用程序中,此操作由 SettingsActivity
类处理,该类显示首选项 UI 来让用户决定何时下载订阅源。
...
三、实现首选项 Activity
SettingsActivity 是 PreferenceActivity 的子类,它显示一个首选项屏幕来让用户指定以下内容:
显示每个 XML 订阅源条目的摘要,还是仅显示每个条目的链接。
是否使用任意可用网络下载 XML 源,或仅在 Wi-Fi 可用时下载 XML 源。
SettingsActivity 的代码如下,它实现了 OnSharedPreferenceChangeListener
。当用户更改首选项时,它将触发 onSharedPreferenceChanged()
,这会设置 refreshDisplay
为 true,导致用户返回主 Activity 时刷新界面:
public class SettingsActivity extends PreferenceActivity implements OnSharedPreferenceChangeListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Loads the XML preferences file
addPreferencesFromResource(R.xml.preferences);
}
@Override
protected void onResume() {
super.onResume();
// Registers a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
}
@Override
protected void onPause() {
super.onPause();
// Unregisters the listener set in onResume().
// It's best practice 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 changes the preferences selection,
// onSharedPreferenceChanged() restarts the main activity as a new
// task. Sets the refreshDisplay flag to "true" to indicate that
// the main activity should update its display.
// The main activity queries the PreferenceManager to get the latest settings.
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
// Sets refreshDisplay 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 连接),则应用会下载源并刷新显示。
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";
// Whether there is a Wi-Fi connection.
private static boolean wifiConnected = false;
// Whether there is a mobile connection.
private static boolean mobileConnected = false;
// Whether the display should be refreshed.
public static boolean refreshDisplay = true;
// The user's current network preference setting.
public static String sPref = null;
// The BroadcastReceiver that tracks network connectivity changes.
private NetworkReceiver receiver = new NetworkReceiver();
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Registers BroadcastReceiver 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 display if the network connection and the
// pref settings allow it.
@Override
public void onStart () {
super.onStart();
// Gets the user's network preference settings
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
// Retrieves a string value for the preferences. The second parameter
// is the default value to use if a preference value is not found.
sPref = sharedPrefs.getString("listPref", "Wi-Fi");
updateConnectedFlags();
if(refreshDisplay){
loadPage();
}
}
// Checks the network connection and sets the wifiConnected and mobileConnected
// variables accordingly.
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;
}
}
// Uses AsyncTask subclass to 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
。用户返回应用时,应用将下载最新的订阅源并在 NetworkActivity.refreshDisplay
设置为 true
时更新界面。
示例应用在 onCreate()
中注册了 NetworkReceiver
,并在 onDestroy()
中取消注册 。这比在清单中声明
更轻量。因为这样可以确保在用户离开应用后不会唤醒应用。如果在清单中声明了一个
并且明确知道什么时候需要它,则可以使用 setComponentEnabledSetting()
来启用和禁用该组件。
NetworkReceiver
的代码如下:
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();
// Checks the user prefs and the network connection. Based on the result, decides whether
// to refresh the display or keep the current display.
// If the userpref is Wi-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 device has its Wi-Fi connection, sets refreshDisplay
// to true. This causes the display to be refreshed when the user
// returns to the app.
refreshDisplay = true;
Toast.makeText(context, R.string.wifi_connected, Toast.LENGTH_SHORT).show();
// If the setting is ANY network and there is a network connection
// (which by process of elimination would be mobile), sets refreshDisplay to true.
} else if (ANY.equals(sPref) && networkInfo != null) {
refreshDisplay = true;
// Otherwise, the app can't download content--either because there is no network
// connection (mobile or Wi-Fi), or because the pref setting is WIFI, and there
// is no Wi-Fi connection.
// Sets refreshDisplay to false.
} else {
refreshDisplay = false;
Toast.makeText(context, R.string.lost_connection, Toast.LENGTH_SHORT).show();
}
}
六、优化网络流量消耗
从 Android 7.0(API 级别 24)开始,用户可以在整个设备上启用流量节省程序,以优化其设备的流量消耗,并减少流量消耗。
当用户在 Settings
中启用流量节省程序且设备位于按流量计费的网络上时,系统屏蔽后台流量消耗,同时指示应用在前台尽可能使用较少的数据。用户可以将特定应用加入白名单以允许后台按流量计费的流量消耗,即使在打开流量节省程序时也是如此。
N Developer Preview 扩展 ConnectivityManager API,为应用提供检索用户的流量节省程序首选项和监控首选项变更的方式。
1. 检查流量节省程序首选项
在 N Developer Preview 中,应用可以使用 ConnectivityManager API 来确定正在应用的是哪些流量消耗限制。
getRestrictBackgroundStatus()
方法返回下列值之一:
RESTRICT_BACKGROUND_STATUS_DISABLED
流量节省程序已停用。RESTRICT_BACKGROUND_STATUS_ENABLED
用户已为此应用启用流量节省程序。应用应努力限制前台流量消耗,并妥善处理后台流量消耗限制。RESTRICT_BACKGROUND_STATUS_WHITELISTED
用户已启用流量节省程序,但应用在白名单中。应用应努力限制前台和后台流量消耗。
这被认为是在设备连接到按流量计费的网络时限制流量消耗的有效方法,即使流量节省程序被停用或应用在白名单中。
以下示例代码使用 ConnectivityManager.isActiveNetworkMetered()
和 ConnectivityManager.getRestrictBackgroundStatus()
来确定应用应使用多少数据:
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
// Checks if the device is on a metered network
if (connMgr.isActiveNetworkMetered()) {
// Checks user’s Data Saver settings.
switch (connMgr.getRestrictBackgroundStatus()) {
case RESTRICT_BACKGROUND_STATUS_ENABLED:
// Background data usage is blocked for this app. Wherever possible,
// the app should also use less data in the foreground.
case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
// The app is whitelisted. Wherever possible,
// the app should use less data in the foreground and background.
case RESTRICT_BACKGROUND_STATUS_DISABLED:
// Data Saver is disabled. Since the device is connected to a
// metered network, the app should use less data wherever possible.
}
} else {
// The device is not on a metered network.
// Use data as required to perform syncs, downloads, and updates.
}
2. 请求白名单权限
如果应用需要使用后台数据,它可以通过发送一项包含应用软件包名称的 URI 的 Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS
Intent 来请求白名单权限:例如 package:MY_APP_ID。
发送 Intent 和 URI 将启动 Settings 应用,还会显示该应用的流量消耗设置。 用户随后可以决定是否启用应用的后台数据。 在发送此 Intent 之前,先询问用户是否希望启用 Settings 应用,以启用后台流量消耗,这是一种有效的做法。
3. 监控流量节省程序首选项变更
应用可以通过创建一个 BroadcastReceiver
以侦听 ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED
以及使用 Context.registerReceiver()
动态注册接收器来监控流量节省程序首选项变更。当应用接收到这条广播时,应通过调用 ConnectivityManager.getRestrictBackgroundStatus()
来检查新的流量节省程序首选项是否会影响其权限。
注:系统只会向使用
Context.registerReceiver()
进行动态注册的应用发送此广播。在其清单中注册接收此广播的应用将不会收到它们。