Android Training - 网络操作(二) - 管理网络用途
Android Training - 网络操作(2) - 管理网络用途
如果程序需要执行很多网络操作,那么你应该让用户能够设置网络连接和管理数据,比如,多久同步一次数据,是否只有Wi-Fi连接时才上传和下载数据,漫游情况下是否使用数据等。
设备可以使用多种方式连接网络,下面我们只关注Wi-Fi和移动网络。
你可以使用一个activity来让用户显式的控制网络资源,例如:
上面的SettingsActivity是PreferenceActivity的子类,它显示一个选择窗口让用户选择:
在onStart()中检查选项设置,这里有一个设置和网络连接的匹配,比如,设置为“Wi-Fi”,而网络连接也是Wi-Fi,那么程序下载feed。
最后的难题是BroadcastReceiver子类,NetworkReceiver。当网络连接方式改变时,NetworkReceiver拦截CONNECTIVITY_ACTION动作,判断网络连接状态,然后设置wifiConnected和mobileConnected为true或者false。结果就是当用户下一次返回程序时,如果NetworkActivity.refreshDisplay为true就下载最新的feed,更新显示。
如果程序需要执行很多网络操作,那么你应该让用户能够设置网络连接和管理数据,比如,多久同步一次数据,是否只有Wi-Fi连接时才上传和下载数据,漫游情况下是否使用数据等。
检查设备的网络连接
设备可以使用多种方式连接网络,下面我们只关注Wi-Fi和移动网络。
Wi-Fi比较快,移动网络一般是计费的,通常的做法是在Wi-Fi状态下获取大数据。
在使用网络前,我们应该检查网络连接状态,然后根据状态执行操作或者返回提示,检查网络需要下面这些类:
- ConnectivityManager:返回网络状态,网络连接改变时发出通知。
- NetworkInfo:网络接口状态的类型。
下面的代码判断什么网络类型可用,并且已连接:
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()来检查网络是否连接。
还有一张方法可以检查到一个网络是否可用,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 - 允许程序获取网络信息。 - 你可以在intent filter中添加ACTION_MANAGE_NETWORK_USAGE action(4.0开始使用)来声明程序定义一个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
上面的SettingsActivity是PreferenceActivity的子类,它显示一个选择窗口让用户选择:
- 为每个XML feed entry展示概要,还是只显示链接。
- 有任何网络连接就下载XML,还是只是有Wi-Fi时才下载。
下面是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 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,那么程序下载feed。
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就下载最新的feed,更新显示。
程序在onCreate()中注册BroadcastReceiver,前提是需要在清单文件中声明<receiver>:
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(); } }