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 Life 和 Transferring 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 必须包括下面的权限:
- android.permission.INTERNET:允许App打开网络套接字。
- android.permission.ACCESS_NETWORK_STATE:允许App访问网络信息。
你要声明 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 preferences 和monitor 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实现过两个接口:
- KXmlParser via XmlPullParserFactory.newPullParser()
- ExpatPullParser via Xml.newPullParser()
两种选择都不错。这节课使用的是 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(19)---设备连接无线网
Develop -- Training(十五) -- 显示高效位图