注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術(shù)一般,由于喜愛安卓而產(chǎn)生了翻譯的念頭,純屬個(gè)人興趣愛好。
原文鏈接: http://developer.android.com/training/basics/network-ops/managing.html
這節(jié)課將講解如何寫一個(gè)對使用網(wǎng)絡(luò)資源具有細(xì)粒度控制的應(yīng)用。如果你的應(yīng)用要執(zhí)行很多網(wǎng)絡(luò)操作,你需要提供用戶設(shè)置,使得用戶可以控制你的應(yīng)用處理數(shù)據(jù)的行為,比如你的應(yīng)用同步數(shù)據(jù)的頻率,是僅在有Wi-Fi連接的情況下上傳/下載,在漫游時(shí)是否處理數(shù)據(jù)等。當(dāng)用戶可以進(jìn)行這些設(shè)置時(shí),用戶就不太會(huì)阻止你的應(yīng)用訪問后臺(tái)數(shù)據(jù)了,因?yàn)樗麄兡芫_地控制應(yīng)用能處理的數(shù)據(jù)范圍。
有關(guān)一些通用的關(guān)于如何編寫最小化影響電池壽命的應(yīng)用程序,相關(guān)的引導(dǎo)可以閱讀:
Optimizing Battery Life
和
Transferring Data Without Draining the Battery
。
一). 檢查設(shè)備的網(wǎng)絡(luò)連接
一個(gè)設(shè)備可以有許多種網(wǎng)絡(luò)連接。這節(jié)課關(guān)注于使用Wi-Fi連接或者移動(dòng)網(wǎng)絡(luò)連接。完整的連接類型列表,可以閱讀: ConnectivityManager 。
一般而言,Wi-Fi速度更快。另外移動(dòng)數(shù)量一般按照流量計(jì)費(fèi),所以會(huì)很昂貴。通常的策略是只在有Wi-Fi連接的情況下獲取大數(shù)據(jù)。
在你執(zhí)行網(wǎng)絡(luò)操作之前,檢查網(wǎng)絡(luò)的連接狀態(tài)時(shí)一個(gè)好的習(xí)慣。這可以防止你的應(yīng)用錯(cuò)誤地使用網(wǎng)絡(luò)連接方式。如果網(wǎng)絡(luò)連接無法獲取,你的應(yīng)用應(yīng)該恰當(dāng)?shù)刈龀鲰憫?yīng)。要檢查網(wǎng)絡(luò)連接狀態(tài),通常你會(huì)使用下列類:
- ConnectivityManager ?- 響應(yīng)網(wǎng)絡(luò)連接狀態(tài)的查詢。它也在網(wǎng)絡(luò)連接放生改變時(shí),通知應(yīng)用。
- NetworkInfo ?- 對給定的類型(移動(dòng)數(shù)據(jù)或者Wi-Fi)描繪網(wǎng)絡(luò)接口的狀態(tài)。
這個(gè)代碼檢測Wi-Fi和移動(dòng)數(shù)據(jù)的網(wǎng)絡(luò)連接。它確定這些網(wǎng)絡(luò)接口是否可用(網(wǎng)絡(luò)連接是否可以獲取)或者是否已連接(也就是說,網(wǎng)絡(luò)連接是否存在或者是否可能建立套接字并傳輸數(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);
注意,在做決策時(shí)不可以僅根據(jù)網(wǎng)絡(luò)是否可獲得來進(jìn)行。應(yīng)該在每次進(jìn)行網(wǎng)絡(luò)操作之間檢查
isConnected()
,因?yàn)?
isConnected()
能夠處理諸如片狀移動(dòng)網(wǎng)絡(luò)(flaky mobile networks),飛行模式,以及后臺(tái)限制的數(shù)據(jù)。
一個(gè)更簡潔的方法是檢查是否有一個(gè)可獲得的網(wǎng)絡(luò)連接接口,如下所示。 getActiveNetworkInfo() 方法返回一個(gè) NetworkInfo 實(shí)例,它代表它能發(fā)現(xiàn)的第一個(gè)已連接的網(wǎng)絡(luò)接口,或者在沒有已連接的網(wǎng)絡(luò)接口時(shí)返回 null (意味著無法獲取一個(gè)網(wǎng)絡(luò)連接):
public boolean isOnline() { ConnectivityManager connMgr = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connMgr.getActiveNetworkInfo(); return (networkInfo != null && networkInfo.isConnected()); }
要查詢更詳細(xì)的連接狀態(tài),你可以使用 NetworkInfo.DetailedState ,但一般來說這是不必要的。
二). 管理網(wǎng)絡(luò)的使用
你可以實(shí)現(xiàn)一個(gè)用以對應(yīng)用進(jìn)行設(shè)置的activity,使得用戶可以對應(yīng)用對網(wǎng)絡(luò)資源的使用進(jìn)行控制。例如:
- 你可以只允許用戶在連接了Wi-Fi的情況下上傳視頻;
- 你可以根據(jù)多個(gè)標(biāo)準(zhǔn),如網(wǎng)絡(luò)可獲得否,時(shí)間間隔,等等,進(jìn)行同步(或不進(jìn)行)。
要寫一個(gè)支持網(wǎng)絡(luò)訪問和管理網(wǎng)絡(luò)使用的應(yīng)用,你的應(yīng)用需要有相應(yīng)的權(quán)限和intent過濾器。
-
下面摘錄的的配置清單包含下列權(quán)限:
- android.permission.INTERNET ?- 允許應(yīng)用打開網(wǎng)絡(luò)套接字。
- android.permission.ACCESS_NETWORK_STATE ?- 允許應(yīng)用獲取有關(guān)網(wǎng)絡(luò)的信息。
- 你可以聲明針對于 ACTION_MANAGE_NETWORK_USAGE (該action自Android 4.0開始引入)的intent過濾器,它用來表明你的應(yīng)用定義了一個(gè)可以對數(shù)據(jù)使用進(jìn)行控制的activity。 ACTION_MANAGE_NETWORK_USAGE 展示了對某個(gè)特定應(yīng)用的數(shù)據(jù)使用管理的設(shè)置。當(dāng)你的應(yīng)用有允許用戶設(shè)置網(wǎng)絡(luò)使用的activity時(shí),你應(yīng)該為該activity配置此intent過濾器。在樣例代碼中,該action由 SettingsActivity 捕獲并處理,它顯示一個(gè)配置界面,讓用戶來選擇何時(shí)下載:
<? 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 >
三). 實(shí)現(xiàn)一個(gè)配置Activity
如同你所看到的上述清單文件,樣例中的activity: SettingsActivity有一個(gè)針對于 ACTION_MANAGE_NETWORK_USAGE 的intent過濾器。 SettingsActivity是 PreferenceActivity 的一個(gè)子類。它顯示一個(gè)配置界面(如圖1所示)讓用戶可以進(jìn)行下列控制:
- 是否顯示每一個(gè)XML源條目的摘要,或僅對每個(gè)條目顯示其鏈接;
- 是否在任何情況下下載XML源,或只在連接了Wi-Fi的情況下。
?
圖1. 配置activity
下面的代碼是 SettingsActivity ,注意它實(shí)現(xiàn)了 OnSharedPreferenceChangeListener 。當(dāng)用戶進(jìn)行了設(shè)置的變更時(shí),會(huì)激活 onSharedPreferenceChanged() ,在該方法中會(huì)把 refreshDisplay 變?yōu)? true ,這會(huì)導(dǎo)致當(dāng)用戶回到主activity時(shí)發(fā)生顯示刷新:
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 ; } }
四). 響應(yīng)配置變更
當(dāng)用戶在配置界面改變了設(shè)置,它會(huì)改變應(yīng)用的行為。在下面的代碼片段中,應(yīng)用在
onStart()
方法內(nèi)檢查各項(xiàng)設(shè)置。如果在設(shè)置和設(shè)備網(wǎng)絡(luò)連接之間能夠匹配的上(例如,設(shè)置是“Wi-Fi”且應(yīng)用具有一個(gè)可使用的Wi-Fi連接),那么應(yīng)用會(huì)下載數(shù)據(jù)源并刷新頁面:
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(); } } ... }
五). 檢測連接變更
最后一部分內(nèi)容是關(guān)于 NetworkReceiver , BroadcastReceiver 的子類。當(dāng)設(shè)備的網(wǎng)絡(luò)連接發(fā)生了變化, NetworkReceiver 會(huì)攔截 CONNECTIVITY_ACTION ,確定當(dāng)前的連接狀態(tài)時(shí)什么,并相應(yīng)地將 wifiConnected 和 mobileConnected 設(shè)置為true/false。產(chǎn)生的結(jié)果是,下一次用戶回到應(yīng)用時(shí),應(yīng)用會(huì)僅下載最后一個(gè)源并在 NetworkActivity.refreshDisplay 為true的情況下刷新頁面。
配置一個(gè)會(huì)被不必要地調(diào)用的
BroadcastReceiver會(huì)消耗系統(tǒng)資源。樣例中,在
onCreate()
方法中注冊了
BroadcastReceiver
?
NetworkReceiver,并在
onDestroy()
中銷毀它。一種更輕量級(jí)的解決方法是在清單文件中聲明一個(gè)
?
<receiver>
。當(dāng)你這樣做了之后,它會(huì)在任何時(shí)間喚起你的應(yīng)用,甚至是你已經(jīng)好幾周沒有運(yùn)行該應(yīng)用了。如果在主
activity
中注冊或注銷
NetworkReceiver,
用戶在離開應(yīng)用后就不會(huì)再被喚起了。如果你在清單文件中聲明了一個(gè)
<receiver>,并且你明確的知道你在什么地方會(huì)需要它,那么你可以使用
setComponentEnabledSetting()
在恰當(dāng)?shù)貢r(shí)機(jī)啟用或禁用它。
下面是 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(); } }
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長非常感激您!手機(jī)微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
