注:本文翻譯自Google官方的Android Developers Training文檔,譯者技術一般,由于喜愛安卓而產生了翻譯的念頭,純屬個人興趣愛好。
原文鏈接: http://developer.android.com/training/basics/network-ops/managing.html
這節課將講解如何寫一個對使用網絡資源具有細粒度控制的應用。如果你的應用要執行很多網絡操作,你需要提供用戶設置,使得用戶可以控制你的應用處理數據的行為,比如你的應用同步數據的頻率,是僅在有Wi-Fi連接的情況下上傳/下載,在漫游時是否處理數據等。當用戶可以進行這些設置時,用戶就不太會阻止你的應用訪問后臺數據了,因為他們能精確地控制應用能處理的數據范圍。
有關一些通用的關于如何編寫最小化影響電池壽命的應用程序,相關的引導可以閱讀:
Optimizing Battery Life
和
Transferring Data Without Draining the Battery
。
一). 檢查設備的網絡連接
一個設備可以有許多種網絡連接。這節課關注于使用Wi-Fi連接或者移動網絡連接。完整的連接類型列表,可以閱讀: ConnectivityManager 。
一般而言,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()
能夠處理諸如片狀移動網絡(flaky mobile networks),飛行模式,以及后臺限制的數據。
一個更簡潔的方法是檢查是否有一個可獲得的網絡連接接口,如下所示。 getActiveNetworkInfo() 方法返回一個 NetworkInfo 實例,它代表它能發現的第一個已連接的網絡接口,或者在沒有已連接的網絡接口時返回 null (意味著無法獲取一個網絡連接):
public
boolean
isOnline() {
ConnectivityManager connMgr
=
(ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo
=
connMgr.getActiveNetworkInfo();
return
(networkInfo !=
null
&&
networkInfo.isConnected());
}
要查詢更詳細的連接狀態,你可以使用 NetworkInfo.DetailedState ,但一般來說這是不必要的。
二). 管理網絡的使用
你可以實現一個用以對應用進行設置的activity,使得用戶可以對應用對網絡資源的使用進行控制。例如:
- 你可以只允許用戶在連接了Wi-Fi的情況下上傳視頻;
- 你可以根據多個標準,如網絡可獲得否,時間間隔,等等,進行同步(或不進行)。
要寫一個支持網絡訪問和管理網絡使用的應用,你的應用需要有相應的權限和intent過濾器。
-
下面摘錄的的配置清單包含下列權限:
- android.permission.INTERNET ?- 允許應用打開網絡套接字。
- android.permission.ACCESS_NETWORK_STATE ?- 允許應用獲取有關網絡的信息。
- 你可以聲明針對于 ACTION_MANAGE_NETWORK_USAGE (該action自Android 4.0開始引入)的intent過濾器,它用來表明你的應用定義了一個可以對數據使用進行控制的activity。 ACTION_MANAGE_NETWORK_USAGE 展示了對某個特定應用的數據使用管理的設置。當你的應用有允許用戶設置網絡使用的activity時,你應該為該activity配置此intent過濾器。在樣例代碼中,該action由 SettingsActivity 捕獲并處理,它顯示一個配置界面,讓用戶來選擇何時下載:
<?
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所示)讓用戶可以進行下列控制:
- 是否顯示每一個XML源條目的摘要,或僅對每個條目顯示其鏈接;
- 是否在任何情況下下載XML源,或只在連接了Wi-Fi的情況下。
?
圖1. 配置activity
下面的代碼是 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();
}
}
...
}
五). 檢測連接變更
最后一部分內容是關于 NetworkReceiver , BroadcastReceiver 的子類。當設備的網絡連接發生了變化, NetworkReceiver 會攔截 CONNECTIVITY_ACTION ,確定當前的連接狀態時什么,并相應地將 wifiConnected 和 mobileConnected 設置為true/false。產生的結果是,下一次用戶回到應用時,應用會僅下載最后一個源并在 NetworkActivity.refreshDisplay 為true的情況下刷新頁面。
配置一個會被不必要地調用的
BroadcastReceiver會消耗系統資源。樣例中,在
onCreate()
方法中注冊了
BroadcastReceiver
?
NetworkReceiver,并在
onDestroy()
中銷毀它。一種更輕量級的解決方法是在清單文件中聲明一個
?
<receiver>
。當你這樣做了之后,它會在任何時間喚起你的應用,甚至是你已經好幾周沒有運行該應用了。如果在主
activity
中注冊或注銷
NetworkReceiver,
用戶在離開應用后就不會再被喚起了。如果你在清單文件中聲明了一個
<receiver>,并且你明確的知道你在什么地方會需要它,那么你可以使用
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();
}
}
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

