Do you want to spend the rest of your life selling sugared water or do you want a chance to change the world?
你想用賣糖水來度過余生,還是想要一個機會來改變世界?——喬布斯
小弟初學安卓,該文算是小弟的學習過程,課后筆記與一些自己的思考,希望在自己的自學路上留下印記,也許有一些自己想的不對的地方,希望各位前輩斧正。(心有愧疚,這篇筆記沒有太多自己思考的地方,暫時把已經了解的先記下,未來深入研究)
一、使用HTTP協議訪問網絡獲取數據
簡單的來說就是我們向服務器發送一條HTTP請求,服務器接收請求后返回一些數據給我們,然后我們可以對這些數據處理或者解析。
HTTP請求方式有八種:
- OPTIONS
- GET
- HEAD
- POST
- PUT
- DELETE
- TRACE
- CONNECT
這些請求的具體作用可以通過搜索了解,小弟就不在這復制別人的了,對我們來說最常用的應該是"GET"和"POST"了,"GET"是向服務器請求數據,"POST"是向服務器提交數據。
Android要發送HTTP請求原本有兩種,HttpURLConnection 和HttpClient,但是HttpClient已經在最新的幾個API中棄用了,且HttpURLConnection 受到了Google的青睞。感興趣的朋友可以搜索看看。
1.獲取HttpURLConnection 的實例
URL url = new URL(http://www.baidu.com);//以百度為例
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
其中URL實例化對象會拋出MalformedURLException異常,而URL的openConnection()方法會拋出IOException異常,需要我們捕獲。
2.連接前的設置
首先是我們之前提到的HTTP請求方式,我們這里用"GET":
connection.setRequestMethod("GET");//該方法同樣會拋出ProtocolException異常,該異常類是IOException的子類,指在底層協議中存在錯誤,如 TCP 錯誤。
注意,安卓中該方法參數不能使用"CONNECT"。且我們可以在HttpURLConnection類中看到一個字符串數組PERMITTED_USER_METHODS,里面是不含"CONNECT"的。
然后就是我們平時在上網時或是玩網絡游戲時會出現的很可惡的東西,"網絡連接超時!",這時候我們要不就是重新登錄要不就是放棄抵抗,而實際上,超時超時,到底超過多久時間算超時呢?實際上這是可以由我們自己設置的,代碼如下:
connection.setConnectTimeout(20000);//設置超時時間20秒
connection.setReadTimeout(20000);//還可以設置讀取超時的時間20秒
最后設置完成就可以連接了:
connection.connect();//請求連接
另外我們可以在嘗試連接后去獲取返回碼與返回信息,分別是HttpURLConnection的getResponseCode()和getResponseMessage()兩個方法,返回值類型分別為int和String類型。
可以從上圖看出,返回信息顯示OK就是連接成功了,而返回碼為200,在HttpURLConnection中的靜態常量HTTP_OK值就是200,就是表示HTTP請求成功的意思,實際上還有很多返回碼,像我們常用的404.
3.獲取服務器返回的輸入流
我們之前的請求其實令我們獲得了百度首頁的HTML文本,它以輸入流的形式傳給了我們,而我們需要獲得這個輸入流只需要使用HttpURLConnection的getInputStream()方法就行了:
InputStream inputStream = connection.getInputStream();
當我們完成我們需要的操作時,應該斷開HTTP連接:
connection.disconnect();
二、獲取網站HTML文本小DEMO
主要功能,輸入網站地址后點擊按鈕,在底下的文本框中顯示HTML文本內容。
注意,一定要在AndroidManifest.xml中申請網絡權限:
<uses-permission android:name="android.permission.INTERNET"/>
主活動HttpActivity的XML布局 activity_http.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.qdf.test.HttpActivity">
<EditText
android:id="@+id/editText_uri"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:inputType="textUri"/>
<Button
android:id="@+id/btn_getData"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/editText_uri"
android:layout_centerHorizontal="true"
android:text="獲取數據"/>
<ScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/btn_getData"
android:layout_centerHorizontal="true">
<TextView
android:id="@+id/textView_present"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="New Text"
android:textSize="15sp"/>
</ScrollView>
</RelativeLayout>
其中用來顯示HTML文本的TextView放在ScrollView中,因為一個屏幕可能顯示不完整個HTML文本。
主活動代碼HttpActivit.class:
package com.qdf.test;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class HttpActivity extends AppCompatActivity {
private EditText mUriEditText;
private TextView mPresentTextView;
private Button mGetDataButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_http);
mUriEditText = (EditText) findViewById(R.id.editText_uri);
mPresentTextView = (TextView) findViewById(R.id.textView_present);
mGetDataButton = (Button) findViewById(R.id.btn_getData);
mGetDataButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String url = getEditTextUrl(mUriEditText).trim();
if (!url.startsWith("http://")) {
Toast.makeText(HttpActivity.this, "URL輸入錯誤",Toast.LENGTH_SHORT).show();
return;
}
new RequestNetWorkDataTask().execute(url);
}
});
}
private String getEditTextUrl(EditText editText) {
return editText != null ? editText.getText().toString() : "";
}
private String requestData(String strUrl) {
HttpURLConnection connection = null;
try {
URL url = new URL(strUrl);
//打開鏈接
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(20000);//設置超時時間
connection.setRequestMethod("GET");//HTTP的請求方式,GET是發送一個請求來取得服務器上的某一資源。
connection.connect();//請求連接
int responseCode = connection.getResponseCode();
String responseMessage = connection.getResponseMessage();
Log.i("tag", "responseCode: " + responseCode + " responseMessage: " + responseMessage);
if (responseCode == HttpURLConnection.HTTP_OK) {
//當連接成功時
InputStream inputStream = connection.getInputStream();
Reader reader = new InputStreamReader(inputStream, "UTF-8");
char[] buffer = new char[1024];
int len = 0;
String result = null;
while ((len = reader.read(buffer)) != -1) {
result = result + new String(buffer, 0, len);
}
reader.close();
return result;
}
} catch (MalformedURLException e) {
e.printStackTrace();
Toast.makeText(HttpActivity.this, "非法的URL", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (connection != null) {
connection.disconnect();
}
}
return null;
}
class RequestNetWorkDataTask extends AsyncTask<String, Integer, String> {
//在后臺work之前
@Override
protected void onPreExecute() {
//在主線程中
//可加載數據,如:UI Loading
super.onPreExecute();
}
@Override
protected String doInBackground(String[] params) {
return requestData(params[0]);
}
@Override
protected void onPostExecute(String result) {
//執行完之后在主線程中
super.onPostExecute(result);
mPresentTextView.setText(result);
}
@Override
protected void onProgressUpdate(Integer... values) {
//進度更新操作
super.onProgressUpdate(values);
}
}
}
因為IO操作應該放在線程中,但因為我們要將獲得的HTML文本輸入到TextView上,這是UI操作不可以在線程中進行,我們這里利用了AsyncTask來解決,使用起來很方便,也可以使用Handler來處理。關于AsyncTask,推薦一篇博客:[詳解Android中AsyncTask的使用]
稍微說一下requestData()這個方法,當我點擊“獲取數據”按鈕后就會先將文本輸入框中的字符串(url)獲取,然后傳入RequestNetWorkDataTask類的匿名對象,而requestData()方法將在RequestNetWorkDataTask的doInBackground()方法內執行,即開啟線程并運行了requestData()方法,而為什么requestData()方法的參數是params[0]呢,首先這個params[0]就是我們之前傳進來的url,而這個params在AsyncTask是個可變參數,它的數量是可變的,最后JAVA將其處理為數組,現在我們只傳了一個字符串(url),那它自然是這個數組的第一位元素(params[0])。
而當我們connection.connect()連接后,獲取了回應碼,我們通過它來判斷是否連接成功,如果成功了才去獲取輸入流。最后我將輸入流內的信息都存放在result這個字符串中,然后返回這個值。而這個值正是doInBackground的返回值,它被onPostExecute()方法接收,而此時已經回到了UI線程,我們再將其輸出到TextView上。
最后的效果,說起來我也應該學習一下HTML呢。
總結
其實我挺害怕學習網絡編程的,因為我覺得網絡這塊領域真的好龐大,事實上確實自己懂得不是很多,所以先灌了自己一口雞湯,未來繼續拼搏吧。