Develop;Training(20)---执行网络操作

Posted 爱coding的卖油翁

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Develop;Training(20)---执行网络操作相关的知识,希望对你有一定的参考价值。

官方链接:https://developer.android.com/training/basics/network-ops/index.html
这节课讲解了网络连接参与一些基础的任务,监测网络连接(包括网络改变),让用户可以控制App的网络使用情况。这节课也描述了怎样解析和使用XML数据。
这节课包含了一个应用程序的例子,介绍了怎样执行常见的网络操作。
你可以在GitHub上找到代码示例,使用它作为你应用程序的可复用代码。
https://github.com/googlesamples/android-BasicNetworking/
通过这节课,你将掌握创建应用程序和构建基本模块的能力,在网络流量很小的时候,它可以高效的下载内容和解析数据。
注意:Transmitting Network Data Using Volley
是关于Volley的信息,一个HTTP请求的依赖库,可以让网络请求变的更容易、更快。Volley的GitHub地址:https://github.com/google/volley。Volley可以很方便的帮你提高应用程序在网络请求操作方面的性能。

连接到网络

为了应用程序可以执行网络操作,你的清单文件必须包含下面的权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

1. 设计安全的网络通信
在应用程序添加网络功能之前,你要确保在网络传输的时候,应用程序数据和信息的安全。以下几个就是网络安全的最佳实战:

  • 最小化网络传输个人数据和敏感用户信息。
  • 应用程序通过SSL发送网络传输。
  • 考虑创建一个网络安全的配置network security configuration允许应用程序信任自定义的CA证书,或者约束为系统的CA证书,它可以保证通信的时候是安全的。

关于更多的网络安全的原理,可以参考networking security tips

2. 选择一个HTTP客户端
大多数Android应用程序的网络连接都是使用HTTP来发送和接收的。Android平台包含了HttpsURLConnection
客户端,支持TLS,流上传和下载,配置超时,IPv6,连接池。

3. 网络操作运行在子线程
为了避免创建一个反应迟钝的UI界面,不能在UI线程中执行网络操作。默认的,Android 3.0(API 11)或者更高,要求执行网络操作必须在非主线程中,如果你没有这样做,将会抛出一个NetworkOnMainThreadException异常。

下面的Activity代码片段使用了一个无界面的Fragment封装了异步的网络操作。之后,你将使用这个Fragment的实现类NetworkFragment完成操作。Activity需要实现DownloadCallback接口,当连接状态变化或者需要更新UI时,Fragment就可以回调给Activity。

public interface DownloadCallback<T> 

    interface Progress 
        int ERROR = -1;
        int CONNECT_SUCCESS = 0;
        int GET_INPUT_STREAM_SUCCESS = 1;
        int PROCESS_INPUT_STREAM_IN_PROGRESS = 2;
        int PROCESS_INPUT_STREAM_SUCCESS = 3;
    

    /**
     * Indicates that the callback handler needs to update its appearance or information based on
     * the result of the task. Expected to be called from the main thread.
     */
    void updateFromDownload(T result);

    /**
     * Get the device's active network status in the form of a NetworkInfo object.
     */
    NetworkInfo getActiveNetworkInfo();

    /**
     * Indicate to callback handler any progress update.
     * @param progressCode must be one of the constants defined in DownloadCallback.Progress.
     * @param percentComplete must be 0-100.
     */
    void onProgressUpdate(int progressCode, int percentComplete);

    /**
     * Indicates that the download operation has finished. This method is called even if the
     * download hasn't completed successfully.
     */
    void finishDownloading();


public class MainActivity extends FragmentActivity implements DownloadCallback 

    ...

    // Keep a reference to the NetworkFragment, which owns the AsyncTask object
    // that is used to execute network ops.
    private NetworkFragment mNetworkFragment;

    // Boolean telling us whether a download is in progress, so we don't trigger overlapping
    // downloads with consecutive button clicks.
    private boolean mDownloading = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) 
        ...
        mNetworkFragment = NetworkFragment.getInstance(getSupportFragmentManager(), "https://www.google.com");
    

    private void startDownload() 
        if (!mDownloading && mNetworkFragment != null) 
            // Execute the async download.
            mNetworkFragment.startDownload();
            mDownloading = true;
        
    

    @Override
    public void updateFromDownload(String result) 
        // Update your UI here based on result of download.
    

    @Override
    public NetworkInfo getActiveNetworkInfo() 
        ConnectivityManager connectivityManager =
                (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
        return networkInfo;
    

    @Override
    public void onProgressUpdate(int progressCode, int percentComplete) 
        switch(progressCode) 
            // You can add UI behavior for progress updates here.
            case Progress.ERROR:
                ...
                break;
            case Progress.CONNECT_SUCCESS:
                ...
                break;
            case Progress.GET_INPUT_STREAM_SUCCESS:
                ...
                break;
            case Progress.PROCESS_INPUT_STREAM_IN_PROGRESS:
                ...
                break;
            case Progress.PROCESS_INPUT_STREAM_SUCCESS:
                ...
                break;
        
    

    @Override
    public void finishDownloading() 
        mDownloading = false;
        if (mNetworkFragment != null) 
            mNetworkFragment.cancelDownload();
        
    

4. 实现一个无界面的Fragment来封装网络操作
NetworkFragment默认是运行在UI线程之中的,但它使用AsyncTask执行网络操作运行在后台线程之中。这个Fragment是无界面的,因为它没有任何引用和UI元素。相反,它只用于封装逻辑和处理生命周期事件,使Activity更新UI。

当使用AsyncTask子类来运行网络操作,你必须谨慎,不能造成内存泄漏,在AsyncTask完成后台任务之前,Activity销毁的时候还被AsyncTask引用着,就会造成内存泄漏。为了不发生这些事情,我们可以在Fragment的onDetach() 方法中清除Activity的任何引用:

/**
 * Implementation of headless Fragment that runs an AsyncTask to fetch data from the network.
 */
public class NetworkFragment extends Fragment 
    public static final String TAG = "NetworkFragment";

    private static final String URL_KEY = "UrlKey";

    private DownloadCallback mCallback;
    private DownloadTask mDownloadTask;
    private String mUrlString;

    /**
     * Static initializer for NetworkFragment that sets the URL of the host it will be downloading
     * from.
     */
    public static NetworkFragment getInstance(FragmentManager fragmentManager, String url) 
        NetworkFragment networkFragment = new NetworkFragment();
        Bundle args = new Bundle();
        args.putString(URL_KEY, url);
        networkFragment.setArguments(args);
        fragmentManager.beginTransaction().add(networkFragment, TAG).commit();
        return networkFragment;
    

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        mUrlString = getArguments().getString(URL_KEY);
        ...
    

    @Override
    public void onAttach(Context context) 
        super.onAttach(context);
        // Host Activity will handle callbacks from task.
        mCallback = (DownloadCallback) context;
    

    @Override
    public void onDetach() 
        super.onDetach();
        // Clear reference to host Activity to avoid memory leak.
        mCallback = null;
    

    @Override
    public void onDestroy() 
        // Cancel task when Fragment is destroyed.
        cancelDownload();
        super.onDestroy();
    

    /**
     * Start non-blocking execution of DownloadTask.
     */
    public void startDownload() 
        cancelDownload();
        mDownloadTask = new DownloadTask();
        mDownloadTask.execute(mUrlString);
    

    /**
     * Cancel (and interrupt if necessary) any ongoing DownloadTask execution.
     */
    public void cancelDownload() 
        if (mDownloadTask != null) 
            mDownloadTask.cancel(true);
        
    

    ...


/**
 * Implementation of AsyncTask designed to fetch data from the network.
 */
private class DownloadTask extends AsyncTask<String, Integer, DownloadTask.Result> 

    private DownloadCallback<String> mCallback;

    DownloadTask(DownloadCallback<String> callback) 
        setCallback(callback);
    

    void setCallback(DownloadCallback<String> callback) 
        mCallback = callback;
    

     /**
     * Wrapper class that serves as a union of a result value and an exception. When the download
     * task has completed, either the result value or exception can be a non-null value.
     * This allows you to pass exceptions to the UI thread that were thrown during doInBackground().
     */
    static class Result 
        public String mResultValue;
        public Exception mException;
        public Result(String resultValue) 
            mResultValue = resultValue;
        
        public Result(Exception exception) 
            mException = exception;
        
    

    /**
     * Cancel background network operation if we do not have network connectivity.
     */
    @Override
    protected void onPreExecute() 
        if (mCallback != null) 
            NetworkInfo networkInfo = mCallback.getActiveNetworkInfo();
            if (networkInfo == null || !networkInfo.isConnected() ||
                    (networkInfo.getType() != ConnectivityManager.TYPE_WIFI
                            && networkInfo.getType() != ConnectivityManager.TYPE_MOBILE)) 
                // If no connectivity, cancel task and update Callback with null data.
                mCallback.updateFromDownload(null);
                cancel(true);
            
        
    

    /**
     * Defines work to perform on the background thread.
     */
    @Override
    protected DownloadTask.Result doInBackground(String... urls) 
        Result result = null;
        if (!isCancelled() && urls != null && urls.length > 0) 
            String urlString = urls[0];
            try 
                URL url = new URL(urlString);
                String resultString = downloadUrl(url);
                if (resultString != null) 
                    result = new Result(resultString);
                 else 
                    throw new IOException("No response received.");
                
             catch(Exception e) 
                result = new Result(e);
            
        
        return result;
    

    /**
     * Updates the DownloadCallback with the result.
     */
    @Override
    protected void onPostExecute(Result result) 
        if (result != null && mCallback != null) 
            if (result.mException != null) 
                mCallback.updateFromDownload(result.mException.getMessage());
             else if (result.mResultValue != null) 
                mCallback.updateFromDownload(result.mResultValue);
            
            mCallback.finishDownloading();
        
    

    /**
     * Override to add special behavior for cancelled AsyncTask.
     */
    @Override
    protected void onCancelled(Result result) 
    
    ...

5. 使用HttpsUrlConnection获取数据
在上面的代码片段中,doInBackground()方法运行在后台线程之中。downloadUrl()给一个URL,然后使用HTTP GET发起请求。连接一旦建立,使用getInputStream()方法从InputStream中获取数据。

/**
 * Given a URL, sets up a connection and gets the HTTP response body from the server.
 * If the network request is successful, it returns the response body in String form. Otherwise,
 * it will throw an IOException.
 */
private String downloadUrl(URL url) throws IOException 
    InputStream stream = null;
    HttpsURLConnection connection = null;
    String result = null;
    try 
        connection = (HttpsURLConnection) url.openConnection();
        // Timeout for reading InputStream arbitrarily set to 3000ms.
        connection.setReadTimeout(3000);
        // Timeout for connection.connect() arbitrarily set to 3000ms.
        connection.setConnectTimeout(3000);
        // For this use case, set HTTP method to GET.
        connection.setRequestMethod("GET");
        // Already true by default but setting just in case; needs to be true since this request
        // is carrying an input (response) body.
        connection.setDoInput(true);
        // Open communications link (network traffic occurs here).
        connection.connect();
        publishProgress(DownloadCallback.Progress.CONNECT_SUCCESS);
        int responseCode = connection.getResponseCode();
        if (responseCode != HttpsURLConnection.HTTP_OK) 
            throw new IOException("HTTP error code: " + responseCode);
        
        // Retrieve the response body as an InputStream.
        stream = connection.getInputStream();
        publishProgress(DownloadCallback.Progress.GET_INPUT_STREAM_SUCCESS, 0);
        if (stream != null) 
            // Converts Stream to String with max length of 500.
            result = readStream(stream, 500);
        
     finally 
        // Close Stream and disconnect HTTPS connection.
        if (stream != null) 
            stream.close();
        
        if (connection != null) 
            connection.disconnect();
        
    
    return result;

注意: getResponseCode()方法会返回一个连接状态码,这是很有用的关于连接的额外信息。200表示连接成功。

6. 把InputStream转换成一个String
InputStream是一个可读的字节源,一旦得到一个InputStream,将解码或转换成目标数据类型是很常见的。例如,如果你下载的是一张图片,你可以用下面的方法解码并显示。

InputStream is = null;
...
Bitmap bitmap = BitmapFactory.decodeStream(is);
ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageBitmap(bitmap);

下面的代码InputStream表示为一个文本响应体,把InputStream转换为一个String,并在Activity上显示出来。

/**
 * Converts the contents of an InputStream to a String.
 */
public String readStream(InputStream stream, int maxReadSize)
        throws IOException, UnsupportedEncodingException 
    Reader reader = null;
    reader = new InputStreamReader(stream, "UTF-8");
    char[] rawBuffer = new char[maxReadSize];
    int readSize;
    StringBuffer buffer = new StringBuffer();
    while (((readSize = reader.read(rawBuffer)) != -1) && maxReadSize > 0) 
        if (readSize > maxReadSize) 
            readSize = maxReadSize;
        
        buffer.append(rawBuffer, 0, readSize);
        maxReadSize -= readSize;
    
    return buffer.toString();

下面是代码中的事件顺序:

  • 从Activity开始构建一个NetworkFragment并通过指定一个特别的URL。
  • 当用户触发了Activity的downloadData()方法,NetworkFragment就会执行DownloadTask。
  • AsyncTask的onPreExecute()方法,该方法是运行在UI线程之中的,如果设备没有连接到网络,需要取消任务。
  • AsyncTask的doInBackground()方法运行在后台线程之中,然后调用downloadUrl()方法。
  • downloadUrl()把一个URL字符串作为参数,使用HttpsURLConnection对象从web上获取数据。
  • InputStream通过readStream()方法,将流转化为String。
  • 最后,一旦后台线程工作完成,AsyncTask的onPostExecute()方法是运行在UI线程之中的,使用DownloadCallback把结果发送给上层的UI。

7. 健壮的配置变化
到目前为止,你已经成功的完成了Activity的网络操作。但是,如果用户决定改变设备的配置(比如旋转屏幕90度),这时候doInBackground()正在运行,Activity销毁并重新创建它自己,将会重新运行onCreate()方法,重新创建一个NetworkFragment引用。因此,AsyncTask就会有一个原始的NetworkFragment对象,它会持有DownloadCallback对象,就会有一个原始Activity的引用,但是不再更新UI了。因此,后台线程完成的网络工作将被浪费。

不停的制造配置变化,你确保每次重建Activity的时候,保留原始的Fragment。要做到这些,按照下面的修改代码:

//  NetworkFragment中的onCreate()方法需要添加setRetainInstance(true)
@Override
public void onCreate(@Nullable Bundle savedInstanceState) 
    ...
    // Retain this Fragment across configuration changes in the host Activity.
    setRetainInstance(true);


public static NetworkFragment getInstance(FragmentManager fragmentManager, String url) 
    // Recover NetworkFragment in case we are re-creating the Activity due to a config change.
    // This is necessary because NetworkFragment might have a task that began running before
    // the config change occurred and has not finished yet.
    // The NetworkFragment is recoverable because it calls setRetainInstance(true).
    NetworkFragment networkFragment = (NetworkFragment) fragmentManager
            .findFragmentByTag(NetworkFragment.TAG);
    if (networkFragment == null) 
        networkFragment = new NetworkFragment();
        Bundle args = new Bundle();
        args.putString(URL_KEY, url);
        networkFragment.setArguments(args);
        fragmentManager.beginTransaction().add(networkFragment, TAG).commit();
    
    return networkFragment;

现在你的App能成功的从网上获取数据了。
注意:还有一些其他的后台线程管理工具可以帮助你实现相同的目标。随着应用程序复杂性的增加,你可能会发现其他工具更适合你的应用程序。而值得研究的是IntentService,而不是AsyncTask。

管理网络使用

这节课将教你编写出的应用程序可以细粒度的控制网络使用资源。如果你的应用程序执行了大量的网络操作,你应该提供一个”用户设置”,允许用户控制应用程序的数习惯,比如多久同步App数据一次,是否只有在Wi-Fi才执行上传下载操作,是否使用漫游数据,等等。有了这些控制,当App接近这些阈值时,用户就可能更少的访问后台数据了,因为它们可以严格的控制App的数据使用。

为了学习更多的App关于网络的使用,想了解一段时间内网络的类型和数据量,可以参考Inspect Network Traffic with Network Profiler。对于大部分编写出App的指南,是关于下载和网络连接对机器的电池续航影响降到最低,请参考Optimizing Battery LifeTransferring Data Without Draining the Battery

1. 检查设备的网络连接
一个设备可以有多种网络连接。本课程的重点是使用Wi-Fi或移动网络连接。对于可能有的网络类型的完整列表,请看ConnectivityManager

Wi-Fi通常是很快的。此外,移动数据是按量计算的,非常贵。一个常见的做法是App在Wi-Fi可用的情况下,获取大量的数据。

在执行网络操作之前,一个好的做法是先检查网络的连接状态。除此之外,它能够防止App不小心错误的使用了音乐播放器。如果网络不可用,App应该很优雅的做出回应。对于检查网络连接,你通常可以使用下面几个类:

  • ConnectivityManager:回答关于网络连接状态的请求。网络状态发生改变也会通知App。
  • 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);

注意:判断网络连接不应该用”available”,你应该用 isConnected() 方法来检查网络连接,isConnected()可用处理奇怪的移动网络,飞行模式或者限制后台数据。

检查网络接口是否可用可用用一种更加简单的方法。getActiveNetworkInfo() 方法返回 NetworkInfo实例对象,表示可以找到的第一个连接的网络接口,为null表示网络接口未连接。

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

查询更细粒度的网络状态,请使用 NetworkInfo.DetailedState,但这不是必须的。

2. 管理网络使用
你可以实现一个设置Activity,让用户能够控制App的网络使用资源。例如:

  • 允许用户上传视频,当设备连接Wi-Fi的时候。
  • 你可以同步或者不同步,视具体情况而定,例如网络可用性、时间间隔等。

为了编写可以控制网络访问和网络管理,你的 manifest 文件必须配置 permissions 和 intent filters。这个 manifest 必须包括下面的权限:

你要声明 intent filter 为 ACTION_MANAGE_NETWORK_USAGE 表明你的App定义了一个Activity,提供了控制网络数据的选项。 ACTION_MANAGE_NETWORK_USAGE 显示设置用于管理网络数据对于一个指定的App。当你的App有一个设置Activity,允许用户控制网络使用,你就需要给这个Activity申明该intent filter。

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

3. 实现一个偏好Activity
正如上面的manifest文件那样,SettingsActivity有一个 intent filter 为 ACTION_MANAGE_NETWORK_USAGE。SettingsActivity是PreferenceActivity
的子类,它显示一个首选项屏幕,允许用户指定以下内容:

  • 是否为每个XML条目显示简介,或者为每个条目显示一个链接。
  • 是否可以下载XML简介,是任何网络连接可用,或者仅当Wi-Fi可用时。

SettingsActivity需要实现 OnSharedPreferenceChangeListener 接口,当用户改变首选项时,会回调 onSharedPreferenceChanged(),然后设置 refreshDisplay 为true。

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;
    

4. 回应配置变化
当用户改变了配置的时候,这对App的行为是很重要的。下面的代码片段,用户在 onStart() 方法检查
首选项配置。

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


5. 检测网络变化
最后,NetworkReceiver是 BroadcastReceiver 的子类。当设备网络发生变化的时候,NetworkReceiver就会拦截 CONNECTIVITY_ACTION 动作,它决定了网络的状态,因为可以设置 wifiConnected 和 mobileConnected 变量的值。结果当用户下一次返回到App的时候,如果NetworkActivity.refreshDisplay变量设置为true,App就会下载最新的数据并更新显示。

设置一个广播是对系统资源的浪费。该App在onCreate() 方法中注册了NetworkReceiver 广播,在 onDestroy() 取消注册。这比在manifest中注册是更轻量级的。当在manifest文件中申明的时候,它能够在任何时候唤醒App,甚至几个礼拜没有运行了。通过在Activity中注册和注销NetworkReceiver 广播,可以确保当用户离开App的时候不会被唤醒。当你确定你需要在manifest中注册广播的时候,你可以通过 setComponentEnabledSetting() 启用或者禁用它。

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

优化网络数据使用

在智能手机的使用过程中,花费的数据流量很可能会超过设备本身。用户能够启用设备上的数据保护,以优化设备上的数据使用,让流量变得更少。此功能在漫游时、周期账单或小的预付费数据包中尤其有用。

当用户在Settings中启动了”数据保护”,系统会防止后台数据使用,在前台也减少App得数据量。用户可以指定一个App白名单,运行这些App在”数据保护”打开得情况下可以访问后台数据。

Android 7.0(API 24)继承了 ConnectivityManager API,给App提供了这些方法 retrieve the user’s Data Saver preferencesmonitor preference changes 。App会检查用户是否启用了数据保护程序,并努力限制前台和后台数据的使用,这被认为是很好做法。

1. 检查数据保护应用程序得首选项
在Android 7.0(API 24),ConnectivityManager API 决定了App是否使用了数据使用限制。根据 getRestrictBackgroundStatus() 返回值来判断:

  • RESTRICT_BACKGROUND_STATUS_DISABLED:数据保护程序被禁用。
  • RESTRICT_BACKGROUND_STATUS_ENABLED:数据保护程序可以使用。App努力限制前台的数据使用,并优雅地处理后台数据使用。
  • RESTRICT_BACKGROUND_STATUS_WHITELISTED:数据保护程序可以使用,但App在白名单之中。App仍然应该努力限制前台和后台数据的使用。

一个好的做法是,当App使用计量的网络时,应该限制网络数据的使用,甚至数据保护程序被禁用或者在白名单之中,参考下面代码的做法:

ConnectivityManager connMgr = (ConnectivityManager)
        getSystemService(Context.CONNECTIVITY_SERVICE);
// Checks if the device is on a metered network
if (connMgr.isActiveNetworkMetered()) 
  // Checks user’s Data Saver settings.
  switch (connMgr.getRestrictBackgroundStatus()) 
    case RESTRICT_BACKGROUND_STATUS_ENABLED:
    // Background data usage is blocked for this app. Wherever possible,
    // the app should also use less data in the foreground.

    case RESTRICT_BACKGROUND_STATUS_WHITELISTED:
    // The app is whitelisted. Wherever possible,
    // the app should use less data in the foreground and background.

    case RESTRICT_BACKGROUND_STATUS_DISABLED:
    // Data Saver is disabled. Since the device is connected to a
    // metered network, the app should use less data wherever possible.
  
 else 
  // The device is not on a metered network.
  // Use data as required to perform syncs, downloads, and updates.

2. 请求白名单权限
如果App需要在后台使用数据,它能够请求一个白名单权限,通过发送Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS Intent,包含一个URI,就是你App的包名,例如:package:MY_APP_ID。

发送Intent和URI给Settings App,显示你App的数据使用设置。用户能够决定App是否可以启动后台数据,在你发送这个Intent之前,一个很好的做法是是先询问用户,如果用户想启动后台数据就运行Settings App。

3. 监测数据保护程序改变的首选项
通过广播App能监测数据服务程序保护配置的变化,要监听ConnectivityManager.ACTION_RESTRICT_BACKGROUND_CHANGED 动作,通过Context.registerReceiver() 方法动态监听广播,当一个应用程序接收到广播,它应该通过ConnectivityManager.getRestrictBackgroundStatus() 方法 check if the new Data Saver preferences affect its permissions
注意:只有当你通过Context.registerReceiver() 方法注册了广播,系统才会发送广播给你的App,App如果是在 manifest 注册的就会收不到广播。

4. 测试Android调试桥命令
Android Debug Bridge (ADB) 提供了一些命令可以用来测试App的数据保护程序。你能检查和配置网络权限,或者设置无限网络来测试App的网络计量情况。

  • $ adb shell dumpsys netpolicy:生成一份报告,包括当前所有的后台网络限制设置,当前白名单的包的UID,其他已知包的网络权限。
  • $ adb shell cmd netpolicy:显示一个完整的网络策略管理列表。
  • $ adb shell cmd netpolicy set restrict-background :开启或者禁用数据保护程序。
  • $ adb shell cmd netpolicy add restrict-background-whitelist :添加指定的程序到白名单,允许使用后台数据。
  • $ adb shell cmd netpolicy remove restrict-background-whitelist :在数据保护程序开启的情况下,从白名单中移除指定程序。
  • $ adb shell cmd netpolicy list wifi-networks:列出所有Wi-Fi网络,显示它们是否是计量的。
  • $ adb shell cmd netpolicy set metered-network true:设置Wi-Fi与指定的SSID作为计量,允许你在不是计量网络的情况下模拟计量网络。

解析XML数据

可扩展标记语言(XML)是一组机器可读形式编码文档。XML是网络上共享数据的常用格式。网站频繁地更新他们的内容,比如一条新闻或者博客,提供XML的数据以便外部程序可以跟上内容的变化。App连接网络上传或者解析XML数据是很常见的任务。这节课就告诉我们怎么解析XML文档和使用数据。

1. 选择一个解析器
我们推荐 XmlPullParser 解析器,在Android上它解析XML是高效的、方便的。在历史上,Android实现过两个接口:

两种选择都不错。这节课使用的是 ExpatPullParser via Xml.newPullParser()

2. 分析标签
第一步就是解析标签,那些你感兴趣的标签。解析器能够提取这些字段的数据,忽略其他字段。下面的片段是解析一个示例App,每个发送到 StackOverflow.com 的文章都出现在 entry 标签中,在其中还嵌套了几个标签。

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" ...">
<title type="text">newest questions tagged android - Stack Overflow</title>
...
    <entry>
    ...
    </entry>
    <entry>
        <id>http://stackoverflow.com/q/9439999</id>
        <re:rank scheme="http://stackoverflow.com">0</re:rank>
        <title type="text">Where is my data file?</title>
        <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="android"/>
        <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="file"/>
        <author>
            

以上是关于Develop;Training(20)---执行网络操作的主要内容,如果未能解决你的问题,请参考以下文章

Develop;Training(18)---添加动画

Develop;Training(19)---设备连接无线网

Develop -- Training(十五) -- 显示高效位图

Develop -- Training(十三) -- 拍照

Develop -- Training(十四) -- 打印内容

WeChall_Encodings: URL (Training, Encoding)