W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
編寫(xiě):kesenhoo - 原文:http://developer.android.com/training/basics/network-ops/managing.html
這一課會(huì)介紹如何細(xì)化管理使用的網(wǎng)絡(luò)資源。如果我們的程序需要執(zhí)行大量網(wǎng)絡(luò)操作,那么應(yīng)該提供用戶(hù)設(shè)置選項(xiàng),來(lái)允許用戶(hù)控制程序的數(shù)據(jù)偏好。例如,同步數(shù)據(jù)的頻率,是否只在連接到 WiFi 才進(jìn)行下載與上傳操作,是否在漫游時(shí)使用套餐數(shù)據(jù)流量等等。這樣用戶(hù)才不大可能在快到達(dá)流量上限時(shí),禁止我們的程序獲取后臺(tái)數(shù)據(jù),因?yàn)樗麄兛梢跃_控制我們的 app 使用多少數(shù)據(jù)流量。
關(guān)于如何編寫(xiě)一個(gè)最小化下載與網(wǎng)絡(luò)操作對(duì)電量影響的程序,請(qǐng)參考:優(yōu)化電池壽命和高效下載。
設(shè)備可以有許多種網(wǎng)絡(luò)連接。這節(jié)課主要關(guān)注使用 Wi-Fi 或移動(dòng)網(wǎng)絡(luò)連接的情況。關(guān)于所有可能的網(wǎng)絡(luò)連接類(lèi)型,請(qǐng)看 ConnectivityManager。
通常 Wi-Fi 是比較快的。移動(dòng)數(shù)據(jù)通常都是需要按流量計(jì)費(fèi),會(huì)比較貴。通常我們會(huì)選擇讓 app 在連接到 WiFi 時(shí)去獲取大量的數(shù)據(jù)。
在執(zhí)行網(wǎng)絡(luò)操作之前,檢查設(shè)備當(dāng)前連接的網(wǎng)絡(luò)連接信息是個(gè)好習(xí)慣。這樣可以防止我們的程序在無(wú)意間連接使用了非意向的網(wǎng)絡(luò)頻道。如果網(wǎng)絡(luò)連接不可用,那么我們的應(yīng)用應(yīng)該優(yōu)雅地做出響應(yīng)。為了檢測(cè)網(wǎng)絡(luò)連接,我們需要使用到下面兩個(gè)類(lèi):
這段代碼檢查了 Wi-Fi 與移動(dòng)網(wǎng)絡(luò)的網(wǎng)絡(luò)連接。它檢查了這些網(wǎng)絡(luò)接口是否可用(也就是說(shuō)網(wǎng)絡(luò)是通的)及是否已連接(也就是說(shuō)網(wǎng)絡(luò)連接存在,并且可以建立 socket 來(lái)傳輸數(shù)據(jù)):
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);
請(qǐng)注意我們不應(yīng)該僅僅靠網(wǎng)絡(luò)是否可用來(lái)做出決策。由于 isConnected() 能夠處理片狀移動(dòng)網(wǎng)絡(luò)(flaky mobile networks),飛行模式和受限制的后臺(tái)數(shù)據(jù)等情況,所以我們應(yīng)該總是在執(zhí)行網(wǎng)絡(luò)操作前檢查 isConnected()。
一個(gè)更簡(jiǎn)潔的檢查網(wǎng)絡(luò)是否可用的示例如下。getActiveNetworkInfo() 方法返回一個(gè) NetworkInfo 實(shí)例,它表示可以找到的第一個(gè)已連接的網(wǎng)絡(luò)接口,如果返回 null,則表示沒(méi)有已連接的網(wǎng)絡(luò)接口(意味著網(wǎng)絡(luò)連接不可用):
public boolean isOnline() {
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
我們可以使用 NetworkInfo.DetailedState,來(lái)獲取更加詳細(xì)的網(wǎng)絡(luò)信息,但很少有這樣的必要。
我們可以實(shí)現(xiàn)一個(gè)偏好設(shè)置的 activity ,使用戶(hù)能直接設(shè)置程序?qū)W(wǎng)絡(luò)資源的使用情況。例如:
寫(xiě)一個(gè)支持連接網(wǎng)絡(luò)和管理網(wǎng)絡(luò)使用的 app,manifest 里需要有正確的權(quán)限和 intent filter。
manifest 文件里包括下面的權(quán)限:
android.permission.INTERNET——允許應(yīng)用程序打開(kāi)網(wǎng)絡(luò)套接字。
android.permission.ACCESS_NETWORK_STATE——允許應(yīng)用程序訪問(wèn)網(wǎng)絡(luò)連接信息。
我們可以為 ACTION_MANAGE_NETWORK_USAGE action(Android 4.0中引入)聲明 intent filter,表示我們的應(yīng)用定義了一個(gè)提供控制數(shù)據(jù)使用情況選項(xiàng)的 activity。ACTION_MANAGE_NETWORK_USAGE 顯示管理指定應(yīng)用程序網(wǎng)絡(luò)數(shù)據(jù)使用情況的設(shè)置。當(dāng)我們的 app 有一個(gè)允許用戶(hù)控制網(wǎng)絡(luò)使用情況的設(shè)置 activity 時(shí),我們應(yīng)該為 activity 聲明這個(gè) intent filter。在章節(jié)概覽提供的示例應(yīng)用中,這個(gè) action 被 SettingsActivity
類(lèi)處理,它提供了偏好設(shè)置 UI 來(lái)讓用戶(hù)決定何時(shí)進(jìn)行下載。
<?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>
正如上面 manifest 片段中看到的那樣,SettingsActivity
有一個(gè) ACTION_MANAGE_NETWORK_USAGE action 的 intent filter。SettingsActivity
是 PreferenceActivity 的子類(lèi),它展示一個(gè)偏好設(shè)置頁(yè)面(如下兩張圖)讓用戶(hù)指定以下內(nèi)容:
Figure 1. 首選項(xiàng) activity
下面是 SettingsActivity
。請(qǐng)注意它實(shí)現(xiàn)了 OnSharedPreferenceChangeListener。當(dāng)用戶(hù)改變了他的偏好,就會(huì)觸發(fā) onSharedPreferenceChanged(),這個(gè)方法會(huì)設(shè)置 refreshDisplay
為 true(這里的變量存在于自己定義的 activity,見(jiàn)下一部分的代碼示例)。這會(huì)使得當(dāng)用戶(hù)返回到 main activity 的時(shí)候進(jìn)行刷新:
(請(qǐng)注意,代碼中的注釋?zhuān)坏貌徽f(shuō),Googler 寫(xiě)的 Code 看起來(lái)就是舒服)
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;
}
}
當(dāng)用戶(hù)在設(shè)置界面改變了偏好,它通常都會(huì)對(duì) app 的行為產(chǎn)生影響。在下面的代碼示例中,app 會(huì)在 onStart()
方法中檢查偏好設(shè)置。如果設(shè)置的類(lèi)型與當(dāng)前設(shè)備的網(wǎng)絡(luò)連接類(lèi)型相一致,那么程序就會(huì)下載數(shù)據(jù)并刷新顯示。(例如, 如果設(shè)置是"Wi-Fi" 并且設(shè)備連接了 Wi-Fi)。
(這是一個(gè)很好的代碼示例,如何選擇合適的網(wǎng)絡(luò)類(lèi)型進(jìn)行下載操作)
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();
}
}
...
}
最后一部分是關(guān)于 BroadcastReceiver 的子類(lèi):NetworkReceiver
。 當(dāng)設(shè)備網(wǎng)絡(luò)連接改變時(shí),NetworkReceiver
會(huì)監(jiān)聽(tīng)到 CONNECTIVITY_ACTION,這時(shí)需要判斷當(dāng)前網(wǎng)絡(luò)連接類(lèi)型并相應(yīng)的設(shè)置好 wifiConnected
與 mobileConnected
。這樣做的結(jié)果是下次用戶(hù)回到 app 時(shí),app 只會(huì)下載最新返回的結(jié)果。如果 NetworkActivity.refreshDisplay
被設(shè)置為 true
,app 會(huì)更新顯示。
我們需要控制好 BroadcastReceiver 的使用,不必要的聲明注冊(cè)會(huì)浪費(fèi)系統(tǒng)資源。示例應(yīng)用在 onCreate()
中注冊(cè) BroadcastReceiver
NetworkReceiver
,在 onDestroy()
中銷(xiāo)毀它。這樣做會(huì)比在 manifest 里面聲明 <receiver>
更輕巧。當(dāng)我們?cè)?manifest 里面聲明一個(gè) <receiver>
,我們的程序可以在任何時(shí)候被喚醒,即使我們已經(jīng)好幾個(gè)星期沒(méi)有運(yùn)行這個(gè)程序了。而通過(guò)前面的辦法注冊(cè)NetworkReceiver
,可以確保用戶(hù)離開(kāi)我們的應(yīng)用之后,應(yīng)用不會(huì)被喚起。如果我們確實(shí)要在 manifest 中聲明 <receiver>
,且確保知道何時(shí)需要使用到它,那么可以在合適的地方使用 setComponentEnabledSetting() 來(lái)開(kāi)啟或者關(guān)閉它。
下面是 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();
}
}
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話(huà):173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: