Managing Network Usage

This lesson describes how to write applications that have fine-grained control over their usage of network resources. If your application performs a lot of network operations, you should provide user settings that allow users to control your app’s data habits, such as how often your app syncs data, whether to perform uploads/downloads only when on Wi-Fi, whether to use data while roaming, and so on. With these controls available to them, users are much less likely to disable your app’s access to background data when they approach their limits, because they can instead precisely control how much data your app uses.

本篇文檔想你展現了,如何實際這樣一個應用,應用能夠對本身訪問網絡資源進行有效地控制。若是應用執行了不少網絡操做,那麼你應該給用戶提供一些設置操做,好比,多久同步一次數據,是否當有wifi時執行上傳下載操做等等。有了這些設置項,用戶纔不會對你的應用反感。也就是,給用戶一些網絡操做方面的設置。html

For general guidelines on how to write apps that minimize the battery life impact of downloads and network connections, seeOptimizing Battery Life and Transferring Data Without Draining the Battery.java

Check a Device's Network Connection


A device can have various types of network connections. This lesson focuses on using either a Wi-Fi or a mobile network connection. For the full list of possible network types, see ConnectivityManager.android

 一個設備可能有不一樣類型的網絡鏈接。本片文檔將會聚焦於wifi和通常的移動數據鏈接。固然了,若是你對其餘的鏈接類型感興趣,能夠參考ConnectivityManager。這個類中定義了不少不一樣的網絡鏈接方式。web

Wi-Fi is typically faster. Also, mobile data is often metered, which can get expensive. A common strategy for apps is to only fetch large data if a Wi-Fi network is available.網絡

wifi的速度是很快的。當英語要獲取很大的數據時,首選固然是wifi了。app

Before you perform network operations, it's good practice to check the state of network connectivity. Among other things, this could prevent your app from inadvertently using the wrong radio. If a network connection is unavailable, your application should respond gracefully. To check the network connection, you typically use the following classes:less

在你的應用執行網絡操做以前,要養成檢測網絡鏈接的好習慣。若是沒有可用的網絡鏈接,你的應用要有友好的提示信息。爲了檢測網絡鏈接,你能夠經過如下兩種方式:所謂檢測網絡鏈接,能夠理解爲本地鏈接,在這種本地鏈接的基礎上,是HttpUrlConnection的鏈接。socket

 

  • ConnectivityManager: Answers queries about the state of network connectivity. It also notifies applications when network connectivity changes.

ConnectivityManager可用於檢查網絡鏈接狀態。並且,當網絡鏈接改變時,也提供同志功能。ide

  • NetworkInfo: Describes the status of a network interface of a given type (currently either Mobile or Wi-Fi).

NetworkInfo描述瞭如wifi等網絡鏈接更多的信息。測試

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);

tests network connectivity for Wi-Fi and mobile. It determines whether these network interfaces are available (that is, whether network connectivity is possible) and/or connected (that is, whether network connectivity exists and if it is possible to establish sockets and pass data):

下面的代碼測試了wifi鏈接,也就是檢測這些鏈接類型是否可用,也就是檢查下,網絡鏈接是否有可能創建,若是可以創建,那麼再檢查下網絡是否已經鏈接。只有鏈接了,才能使用sockets來收發數據

Note that you should not base decisions on whether a network is "available." You should always checkisConnected()before performing network operations, sinceisConnected()handles cases like flaky mobile networks, airplane mode, and restricted background data.

注意,在執行網絡操做以前,必定要經過isConnected檢查下網絡是否已經鏈接。

A more concise way of checking whether a network interface is available is as follows. The methodgetActiveNetworkInfo()returns a NetworkInfoinstance representing the first connected network interface it can find, or null if none of the interfaces is connected (meaning that an internet connection is not available):

一種更爲精確的檢查本地鏈接是否鏈接的方式以下。getActiveNetworkInfo方法會返回一個NetworkInfo實例。這個實例表明了getActiveNetworkInfo方法發現的第一個鏈接的本地鏈接,若是沒有本地鏈接時鏈接的話,則返回一個null。

public boolean isOnline() {
    ConnectivityManager connMgr = (ConnectivityManager) 
            getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
    return (networkInfo != null && networkInfo.isConnected());
}  

Manage Network Usage


You can implement a preferences activity that gives users explicit control over your app's usage of network resources. For example:

技術上而言,你還須要設計一個「設置」活動,容許用戶顯示的經過這個活動來設置網絡資源,好比:就像有些應用,剛開始運行時會檢查網絡鏈接,若是沒有鏈接的話,會讓用戶進入網絡界面進行設置。

  • You might allow users to upload videos only when the device is connected to a Wi-Fi network.

只有在設備鏈接上wi-fi時,你的應用纔會容許用戶上傳視頻。

  • You might sync (or not) depending on specific criteria such as network availability, time interval, and so on.

 

To write an app that supports network access and managing network usage, your manifest must have the right permissions and intent filters.

要想讓用戶可以經過你的應用來設置網絡,你須要在manifest文件設置相應的權限等。這裏強調了兩件事情,一個是容許應用訪問網絡,一個是容許應用提供網絡訪問的控制。

  android.permission.ACCESS_NETWORK_STATE-容許用戶訪問網絡信息。這個是容許應用訪問網絡信息。

  • You can declare the intent filter for the ACTION_MANAGE_NETWORK_USAGE action (introduced in Android 4.0) to indicate that your application defines an activity that offers options to control data usage.ACTION_MANAGE_NETWORK_USAGE shows settings for managing the network data usage of a specific application. When your app has a settings activity that allows users to control network usage, you should declare this intent filter for that activity. In the sample application, this action is handled by the classSettingsActivity, which displays a preferences UI to let users decide when to download a feed.

另外,還需在manifest文件的「網絡設置」活動的intent過濾器中,設置ACTION_MANAGE_NETWORK_USAGE行爲。該行爲會顯示網絡設置。在一樣的應用中,這個行爲會被類SettingsActivity處理,這個類,也就是一個活動,會顯示一個配置界面,容許用戶聲明合適下載一個種子(這些都是官方源碼裏的東西)。所以,若是應用要給用戶提供一個能夠配置的活動,就須要在活動的intent filter聲明一個ACTION_MANAGE_NETWORK_USAGE

<?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>

 

Implement a Preferences Activity


As you can see in the manifest excerpt above, the sample app's activity SettingsActivity has an intent filter for the ACTION_MANAGE_NETWORK_USAGE action. SettingsActivity is a subclass of PreferenceActivity. It displays a preferences screen (shown in figure 1) that lets users specify the following:

正如上面manifest文件中可看到,例子源碼中的SettingsActivity活動有一個intent過濾器,該過濾器有一個ACTION_MANAGE_NETWORK_USAGE行爲。SettingsActivityPreferenceActivity子類。SettingsActivity會顯示一個配置界面,這個配置界面會容許用戶:

  • Whether to display summaries for each XML feed entry, or just a link for each entry.

源碼中的東西

  • Whether to download the XML feed if any network connection is available, or only if Wi-Fi is available.

源碼中的東西。

Here is SettingsActivity. Note that it implements OnSharedPreferenceChangeListener. When a user changes a preference, it fires onSharedPreferenceChanged(), which sets refreshDisplay to true. This causes the display to refresh when the user returns to the main activity:

如下是SettingsActivity的源碼。注意,SettingsActivity實現了OnSharedPreferenceChangeListener接口。當用戶修改了一個配置項,就會觸發OnSharedPreferenceChangeListener事件。這個事件會將變量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;
    }
}

 

Respond to Preference Changes


When the user changes preferences in the settings screen, it typically has consequences for the app's behavior. In this snippet, the app checks the preferences settings in onStart(). if there is a match between the setting and the device's network connection (for example, if the setting is "Wi-Fi" and the device has a Wi-Fi connection), the app downloads the feed and refreshes the display.

用戶會在配置活動中改變一些配置項。以下源碼顯示,應用會在啓動時檢查舊有的配置數據,若是,好比,設置中設置了若是已經鏈接了wifi就會如何如何,而應用啓動時,設備還真的已經鏈接了wifi,應用就會下載種子。

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();
        }
    }
...
    
}

 

Detect Connection Changes


The final piece of the puzzle is the BroadcastReceiver subclass, NetworkReceiver. When the device's network connection changes, NetworkReceiver intercepts the action CONNECTIVITY_ACTION, determines what the network connection status is, and sets the flags wifiConnected and mobileConnected to true/false accordingly. The upshot is that the next time the user returns to the app, the app will only download the latest feed and update the display if NetworkActivity.refreshDisplay is set to true.

在上面的代碼中,還有一個BroadcastReceiver子類,也就是NetworkReceiver。當設備的網絡鏈接發生變化時,NetworkReceiver會攔截CONNECTIVITY_ACTION行爲,它會探知目前是wifi鏈接上,仍是手機網鏈接上,而後相應的設置標誌位。

Setting up a BroadcastReceiver that gets called unnecessarily can be a drain on system resources. The sample application registers the BroadcastReceiver NetworkReceiver in onCreate(), and it unregisters it in onDestroy(). This is more lightweight than declaring a <receiver> in the manifest. When you declare a <receiver> in the manifest, it can wake up your app at any time, even if you haven't run it for weeks. By registering and unregistering NetworkReceiver within the main activity, you ensure that the app won't be woken up after the user leaves the app. If you do declare a <receiver> in the manifest and you know exactly where you need it, you can use setComponentEnabledSetting() to enable and disable it as appropriate.

例子源碼中在onCreate中註冊了BroadcastReceiverNetworkReceiver接收器,而後在onDestroy中卸載接收器。這可比在manifest文件中聲明一個<receiver>元素要輕量級多了若是你在manifest文件中聲明瞭一個<receiver>,這個元素會在任什麼時候候喚起你的應用,即便你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();
    }
}
相關文章
相關標籤/搜索