編輯:關於Android編程
本文是翻譯自Google 官方課程 Building Apps with Connectivity & the Cloud 第二節 Performing Network Operations
本文的demo為NetworkUsage,是一款根據不同網絡狀態,刷新UI的 web app,數據都來自網絡
初始狀態:
可以設置是否展示文章詳情<喎?/kf/ware/vc/" target="_blank" class="keylink">vc3Ryb25nPqO6PC9wPg0KPHA+PGltZyBhbHQ9"這裡寫圖片描述" src="/uploadfile/Collfiles/20160806/2016080609140797.gif" title="\" />
設備要求:
設備可以連接移動網絡或WI-FI
這篇文章將講解連接網絡的基本操作,檢測網絡連接(包括狀態改變),讓用戶根據網絡狀態控制*app,最後還講了如何解析XML*數據
通過這篇文章,你將了解原理-構建webAndroid應用,它可以高效下載並解析數據,減小網絡擁堵
也可以閱讀我之前翻譯的《Volley官方課程》,一個HTTP庫使得Android app的網絡連接更快更簡潔,Volley是開源的,可以幫助你提高app網絡連接的性能
本文包括三部分:
連接網絡
如何連接網絡,選擇HTTP 客戶端,避免在主線程執行網絡任務
管理網絡使用
學習如何檢查設備的網絡連接,創建UI**偏好設置,響應**網絡連接狀態的改變
解析XML數據
這部分將實現一個簡單的可以連接網絡的app,包含了創建網絡app的所有基本步驟
這部分依照如下順序講解:
選擇HTTP 客戶端 檢查當前設備網絡連接 在子線程中執行網絡任務 下載網絡數據 將流解析成字符串對於初學者,可能還需要了解《Volley官方課程》,《Web Apps開發》 以及後續的博文《延長電池壽命》,《App 基本組件》
額,不算提醒的提醒,app在使用網絡之前,需要添加權限:
選擇HTTP 客戶端
大部分的Android app都是使用HTTP協議發送接收數據, Android系統提供了HttpURLConnection編程接口,它是支持HTTPS協議的,並且支持上傳和下載數據,配置超時,IPV6和連接池
檢查網絡連接
在使用網絡之前,app得先檢查當前設備的網絡連接是否可用,通過getActiveNetworkInfo() 和isConnected()可以判斷網絡連接。提醒各位,我們使用的手機設備有可能在網絡覆蓋區域之外,或者WI-FI也不可用,更多的情形可以在下一節《管理網絡連接》看到
public void myClickHandler(View view) {
...
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
// fetch data
} else {
// display error
}
...
}
在子線程中執行網絡任務
網絡任務有可能被延遲或阻塞,這種情況下用戶體驗非常糟糕,所以我們得使用子線程創建網絡任務,主線程去做刷新UI的事情就好
推薦使用AsyncTask ,它提供了一種非常簡單的方式–在啟動子線程異步完成任務的同時,又能通知主線程刷新界面
對於初識AsyncTask的人來說,這玩意咋用呢?
通常做法是繼承AsyncTask,按要求重寫方法
doInBackground(),調用downloadUrl()方法,去請求一些數據 onPostExecute(),將上一步請求的數據,顯示在主線程中
public class HttpExampleActivity extends Activity {
private static final String DEBUG_TAG = "HttpExample";
private EditText urlText;
private TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
urlText = (EditText) findViewById(R.id.myUrl);
textView = (TextView) findViewById(R.id.myText);
}
// When user clicks button, calls AsyncTask.
// Before attempting to fetch the URL, makes sure that there is a network connection.
public void myClickHandler(View view) {
// Gets the URL from the UI's text field.
String stringUrl = urlText.getText().toString();
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
if (networkInfo != null && networkInfo.isConnected()) {
new DownloadWebpageTask().execute(stringUrl);
} else {
textView.setText("No network connection available.");
}
}
// Uses AsyncTask to create a task away from the main UI thread. This task takes a
// URL string and uses it to create an HttpUrlConnection. Once the connection
// has been established, the AsyncTask downloads the contents of the webpage as
// an InputStream. Finally, the InputStream is converted into a string, which is
// displayed in the UI by the AsyncTask's onPostExecute method.
private class DownloadWebpageTask extends AsyncTask {
@Override
protected String doInBackground(String... urls) {
// params comes from the execute() call: params[0] is the url.
try {
return downloadUrl(urls[0]);
} catch (IOException e) {
return "Unable to retrieve web page. URL may be invalid.";
}
}
// onPostExecute displays the results of the AsyncTask.
@Override
protected void onPostExecute(String result) {
textView.setText(result);
}
}
...
}
示例中的執行順序如下:
當用戶點擊button,將調用myClickHandler(),這個app傳遞特殊的URL給DownloadWebpageTask 這個AsyncTask子類首先調用doInBackground()方法,去執行downloadUrl() downloadUrl()創建URL對象 通過HtppURLConnection使用URL建立網絡連接 一旦建立了網絡連接,HttpURLConnection將獲得Web端反饋的流數據 流通過readIt()函數轉為一個字符串 最終,AsyncTask的onPostExecute()方法將會把上一步的字符串展示到activity的界面上
連接並下載數據
通過HttpURLConnection去執行GET請求下載網絡上的數據,在調用connect()方法之後,可以獲得網絡上返回的字節流
// Given a URL, establishes an HttpUrlConnection and retrieves
// the web page content as a InputStream, which it returns as
// a string.
private String downloadUrl(String myurl) throws IOException {
InputStream is = null;
// Only display the first 500 characters of the retrieved
// web page content.
int len = 500;
try {
URL url = new URL(myurl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Starts the query
conn.connect();
int response = conn.getResponseCode();
Log.d(DEBUG_TAG, "The response is: " + response);
is = conn.getInputStream();
// Convert the InputStream into a string
String contentAsString = readIt(is, len);
return contentAsString;
// Makes sure that the InputStream is closed after the app is
// finished using it.
} finally {
if (is != null) {
is.close();
}
}
}
getResponseCode()獲得網絡連接的 status coded(狀態碼),200,400,403 404等等
將流轉換成字符串
InputStream是可讀的數據,一旦你獲得一個InputStream,通常可以解析成其他數據類型,比如我們下載圖片,可以解析並展示它,
InputStream is = null;
...
Bitmap bitmap = BitmapFactory.decodeStream(is);
ImageView imageView = (ImageView) findViewById(R.id.image_view);
imageView.setImageBitmap(bitmap);
In the example shown above, the InputStream represents the text of a web page. This is how the example converts the InputStream to a string so that the activity can display it in the UI:
// Reads an InputStream and converts it to a String.
public String readIt(InputStream stream, int len) throws IOException, UnsupportedEncodingException {
Reader reader = null;
reader = new InputStreamReader(stream, "UTF-8");
char[] buffer = new char[len];
reader.read(buffer);
return new String(buffer);
}
管理網絡使用
上一節我們學會了通過AsyncTask下載數據並將數據展示到界面中
接著,我們得考慮全面一些,網絡連接是否可用?網絡連接中斷了咋辦?
嗯,這一節將涉及:
檢查設備的網絡連接 管理網絡使用 實現Activity的偏好設置 響應偏好設置的改變(是否自動刷新?展示標題還是標題和內容一起展示?) 關閉網絡連接
這一部分的例子將展示如何控制網絡的數據,如果app執行大量的網絡任務,我們就需要提供偏好設置允許用戶控制app的數據,比如是否同步數據,是否僅在WI-FI狀態下更新上傳數據,是否漫游數據,諸如此類
當我們考慮的如此周到,用戶不喜歡我們的應用才怪哩!
檢查設備的網絡連接
一個設備可以有很多種類型的網絡連接,本篇博文專注於使用WI-FI或者移動蜂窩網絡連接,對於所有可用的網絡連接類型,可以參考ConnectivityManager
WI-FI 的傳輸速度很快,移動網絡經常是可測量的,但需要花費流量,代價比較高,通常在WI-FI可用的情況下,app連接網絡都是通過WI-FI傳輸數據
在執行網絡任務之前,檢查網絡狀態是非常好的習慣,如果網絡連接不可用,app應該給用戶“柔和”的提示,如果打算檢查網絡連接,可以從以下兩個類入手:
ConnectivityManager:可以獲得網絡連接的狀態,也能通知app設備網絡連接狀態發生了改變 NetworkInfo:描述當前網絡連接的類型(比如當前是移動網絡還是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);
它可以測定網絡是否可用,如果是,是否網絡已連接,如果已經連接,那麼將可以通過建立socket傳輸數據
我們不應該以網絡是否可用作為接下來業務邏輯的根據,而是應該以是否連接作為下一步執行的依據,檢查是否連接可以使用
isConnected(),判斷完畢網絡連接狀態後,我們就可以處理移動網絡數據交互,飛行模式,微量的後台數據
推薦一種簡潔的檢查網絡連接的方式:
public boolean isOnline() {
ConnectivityManager connMgr = (ConnectivityManager)
getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = connMgr.getActiveNetworkInfo();
return (networkInfo != null && networkInfo.isConnected());
}
更多的信息,可以參考
NetworkInfo.DetailedState
管理網絡使用
我們可以實現偏好設置,給用戶足夠多的控制權,去控制app訪問網絡的方式,例如:
可以允許用戶上傳視頻,當設備連接WI-FI網絡的時候 可以同步信息,當網絡可用的時候
記得添加這倆權限:
android.permission.INTERNET 允許app打開網絡sockets android.permission.ACCESS_NETWORK_STATE 允許app獲得網絡狀態的信息
我們也可以聲明intent filter 的action為ACTION_MANAGE_NETWORK_USAGE,定義app提供一種可選擇的方式控制網絡數據傳輸,
...
上述示例中的SettingsActivity,設置了action為MANAGE_NETWORK_USAGE,app才可以控制網絡使用,
它提供了一個偏好設置讓用戶去決定在什麼情況下(WI-FI,Network)去上傳或下載數據
響應偏好設置改變
當用戶改變了app的偏好設置,我們還需要在activity的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();
}
}
...
}
檢測網絡連接改變
最後的部分就是實現BroadcastReceiver子類—NetworkReceiver,當設備網絡連接改變的時候,NetworkReceiver根據CONNECTIVITY_ACTION行為,判斷當前的網絡連接狀態,來設置wifiConnected和moblileConnected 的狀態為true或false,這兩個變量主要是記錄當前網絡狀態的:
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();
}
}
解析XML數據
這節得重點有:
選擇一個 解析器 分析網絡的反饋 初始化解析器 閱讀網絡的反饋 解析XML 跳過你不必關心的部分 使用XML數據
這節將講解如何解析XML文檔,並且使用這些XML數據
選擇一個解析器
建議使用XmlPullParser解析XML,Android提供了兩個編程接口:
KXmlParser:通過XmlPullParserFactory.newPullParser()獲得; ExpatPullParser:通過Xml.Xml.newPullParser()獲得
下面的解析以ExpatPullParser為例
確定數據源
第一步,想選擇感興趣的 “數據反饋”作為解析源
接下來的例子是以著名網站“StackOverflow”作為“數據源”,
" + getResources().getString(R.string.page_title) + "");
htmlString.append("" + getResources().getString(R.string.updated) + " " +
formatter.format(rightNow.getTime()) + "");
try {
stream = downloadUrl(urlString);
entries = stackOverflowXmlParser.parse(stream);
// Makes sure that the InputStream is closed after the app is
// finished using it.
} finally {
if (stream != null) {
stream.close();
}
}
// StackOverflowXmlParser returns a List (called "entries") of Entry objects.
// Each Entry object represents a single post in the XML feed.
// This section processes the entries list to combine each entry with HTML markup.
// Each entry is displayed in the UI as a link that optionally includes
// a text summary.
for (Entry entry : entries) {
htmlString.append("" + entry.title + "
");
// If the user set the preference to include summary text,
// adds it to the display.
if (pref) {
htmlString.append(entry.summary);
}
}
return htmlString.toString();
}
// Given a string representation of a URL, sets up a connection and gets
// an input stream.
private InputStream downloadUrl(String urlString) throws IOException {
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setReadTimeout(10000 /* milliseconds */);
conn.setConnectTimeout(15000 /* milliseconds */);
conn.setRequestMethod("GET");
conn.setDoInput(true);
// Starts the query
conn.connect();
return conn.getInputStream();
}
由於精確度等原因,手勢識別在android中用的並不多,不過這並不妨礙我們來玩玩這個神奇的玩意。在android中要使用手勢,先得建立手勢庫,建立手勢庫非常簡單,新建一個
看一下Activity是怎麼通過View,Window等來用於自己的顯示的。上圖是Activity的Lifecycle。這裡只想說一下按back鍵和按home鍵退出,重新
每次看IOS上的應用,應用中狀態欄的顏色總能與應用標題欄顏色保持一致,用戶體驗很不錯,對於這種效果,像我這種好奇心強的人就會去看看那安卓是否可以呢?若是在安卓4.4之前,
前言:前面一篇分析了mediaplayerservice及MediaPlayer中的CS模型,但是對於如何能把數據解析出來,渲染到最終的SurfaceView上顯示,並且