在咱們的應用中支持網絡功能是絕對有必要的,大部分的應用程序都須要從服務器獲取網絡數據而後顯示在界面中。前兩篇文章咱們介紹了 WebView 的一些用法和知識點。可是並不是全部的網絡功能都能經過 Webview 來實現,好比咱們從服務器獲取一段 json 數據,其中包含了咱們想要的信息,這時候,咱們就不能使用Webview 了,而是須要直接獲取到一個 Http 請求的響應數據。咱們可使用 HttpUrlConnection 或者其餘的第三方網絡框架來實現網絡訪問。html
在 Android 6.0 以前,原生的有兩種方式能夠進行網絡請求,HttpClient 和 HttpUrlConnection,HttpClient 的 API 多而複雜,拓展困難,所以這種方式在 Android 6.0 以後就被官方移除了。HttpUrlConnection 的 API 簡單,體積較小,很是適合 Android 開發,也是官方推薦的網絡請求方式。咱們這篇文章就來看看 HttpUrlConnection 的相關知識。android
使用 HttpUrlConnection 來進行網絡請求大體上能夠分爲4個步驟:web
咱們依次來看看這些步驟中須要作哪些工做:編程
使用 URL 對象的 openConnection()方法獲取到 HttpUrlConnection 對象,這個對象是咱們進行網絡請求的核心。json
網絡請求在響應時間上具備很大的不肯定性,若是將網絡請求放在主線程中執行時,過長的耗時操做會阻塞主線程,致使程序卡死。所以,網絡請求都應該放在子線程中執行。數組
如如下示例代碼:瀏覽器
URL url = new URL("http://lixiaoyu.cc");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();複製代碼
獲取到 HttpUrlConnection 對象後,就能夠調用這個對象的一些方法,進行一些網絡設置,好比設置鏈接超時時間,讀取超時時間,網絡請求方式等。如如下代碼所示:bash
//設置網絡請求方式,如GET、POST、HEAD等
conn.setRequestMethod("GET");
//設置鏈接超時時間
conn.setConnectTimeout(8000);
//設置讀取超時時間
conn.setReadTimeout(8000);
//設置Http請求頭部
conn.setRequestProperty("Accept-Encoding", "identity");
//設置能夠讀取輸入流
conn.setDoInput(true);
//設置能夠讀取輸出流,在使用POST向服務器提交數據時必需要將該方法設置爲true
conn.setDoOutput(true);
//進行Http鏈接,必須寫在setDoInput()方法後面
conn.connect();複製代碼
進行數據處理包括兩個方面,一個是從服務器讀取相應數據,一個是向服務器發送數據(POST 方法會用到),分別對應以前的 setDoInput() 和 setDoOutput() 方法。服務器
從服務器讀取數據網絡
先來看看從服務器讀取數據,經過調用 HttpUrlConnection 對象的一些方法能夠獲取到服務器發送給客戶端的相應信息,如狀態碼、響應內容長度、包含了響應內容的輸入流等等。如如下示例代碼:
//獲取響應狀態碼,如 200 表示成功等
int responseCode = conn.getResponseCode();
//獲取包含響應內容的輸入流
InputStream in = conn.getInputStream();
//獲取響應內容長度
int contentLength = conn.getContentLength();複製代碼
在獲取輸入流以後,就能夠利用 Java 中的 IO 流的知識對該輸入流進行流處理,從而獲得咱們想要的數據。(這部分代碼在完整示例代碼中給出)
向服務器提交數據
咱們經常使用 POST 方法向服務器提交一個表單,在向服務器提交數據時,須要先經過 HttpUrlConnection 對象的 getOutputStream() 方法獲取到輸出流對象,在經過輸出流對象的 write() 方法向服務器寫數據。POST 方法的每條數據都以鍵值對的形式提交,數據之間用 「&」 進行分隔。如如下示例代碼:
//將網絡請求方法改成 POST
conn.setRequestMethod("POST");
//設置支持輸出流
conn.setDoOutput(true);
//獲取 HttpUrlConnection 的輸出流對象
OutputStream out = conn.getOutputStream();
//給這個輸出流添加一個處理流,方便操做
DataOutputStream dos = new DataOutputStream(out);
//使用 writeBytes() 方法將數據提交到服務器
dos.writeBytes("username=admin&password=123456");
//進行 Http 鏈接
conn.connect();複製代碼
在咱們完成了全部數據寫入和讀取的流操做後,應該調用 disconnect() 方法關閉 Http 鏈接。
//關閉 Http 鏈接
conn.disconnect();複製代碼
接下來,經過兩個實例加深對 HttpUrlConnection 的理解。
在 layout 文件中放置一個 Button 和一個 TextView,咱們但願點擊 Button 後,獲取到某個網站的 HTML 源碼,並以文本的形式展現在 TextView 中。這個實例較爲簡單,就直接將代碼貼出,關鍵部分會有註釋。
layout 文件中,Button 指定了一個名爲 getCode 的 onClick()方法,能夠直接在 Activity 中實現這個方法,進行 Button 的點擊事件監聽。
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="getCode"
android:text="獲取網頁源代碼"/>
<TextView
android:id="@+id/main_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>複製代碼
在 Activity 中:
//處理 Button 的點擊事件
public void getCode(View view) {
new Thread(new Runnable() {
@Override
public void run() {
try {
URL url = new URL("http://lixiaoyu.cc");
//獲取 HttpURLConnection 對象
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//設置請求方法爲 GET
conn.setRequestMethod("GET");
//設置鏈接超時時間爲 8 秒
conn.setConnectTimeout(8000);
//設置讀取超時時間爲 8 秒
conn.setReadTimeout(8000);
//支持輸入流
conn.setDoInput(true);
//獲取響應狀態碼
int responseCode = conn.getResponseCode();
Log.i(TAG, "responseCode=" + responseCode);
//獲取輸入流
InputStream in = conn.getInputStream();
//將輸入流封裝成 BufferedReader
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuffer sb = new StringBuffer();
String line;
while ((line = reader.readLine()) != null) {
sb.append(line);
}
//將 StringBuffer 的數據轉化成 String,在主線程中設置到 TextView 上
showCode(sb.toString());
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
private void showCode(final String s) {
//必須在主線程中操做 UI
runOnUiThread(new Runnable() {
@Override
public void run() {
tvCode.setText(s);
}
});
}複製代碼
程序運行的結果如圖所示:
在上篇 WebView 的文章中講到在 Webview 下載文件能夠有兩種方式,一時經過隱式 Intent 調用系統瀏覽器進行下載,一種是拿到文件的 URL 後本身建立線程進行下載,上篇文章中只介紹了第一種方法,這裏就介紹第二種方法的實現。其實原理很是簡單,就是在獲取到文件 URL 後,使用 HttpUrlConnection 進行網絡請求,經過其對象的輸入流讀取到該文件的二進制數據,將二進制數據保存爲相應格式的文件便可。
完整代碼以下:
public class WebActivity extends AppCompatActivity {
private WebView mWebView;
private String mUrl = null;
private static final String TAG = "WebActivity";
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_haha);
mWebView = (WebView) findViewById(R.id.webview);
mWebView.getSettings().setJavaScriptEnabled(true);
//加載豌豆莢應用市場的網頁
mWebView.loadUrl("http://wandoujia.com");
mWebView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
view.loadUrl(url);
return true;
}
});
//設置下載監聽器
mWebView.setDownloadListener(new DownloadListener() {
@Override
public void onDownloadStart(String url, String s1, String s2, String s3, long l) {
//若是URL以「.apk」結尾,就進行下載
if (url.endsWith(".apk")) {
mUrl = url;
//程序運行在Android 6.0以上的系統中,因此在讀寫SD卡時須要動態申請權限
if(ContextCompat.checkSelfPermission(WebActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(WebActivity.this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
}else{
//若是已經申請該權限,則直接下載
downloadApk(mUrl);
}
}
}
});
}
/**
* 下載Apk文件的方法
* @param url
*/
private void downloadApk(final String url) {
new Thread(new Runnable() {
@Override
public void run() {
//獲取SD卡的目錄
File sdCard = Environment.getExternalStorageDirectory();
//經過URL拿到apk文件名
String apkName = url.substring(url.lastIndexOf("/"));
//在SD卡的根目錄下新建一個文件
File apkFile = new File(sdCard, apkName);
try {
if (!apkFile.exists()){
apkFile.createNewFile();
}else{
apkFile.delete();
apkFile.createNewFile();
}
FileOutputStream fos = new FileOutputStream(apkFile);
HttpURLConnection conn = (HttpURLConnection) (new URL(url)).openConnection();
conn.setRequestMethod("GET");
conn.setDoInput(true);
conn.setConnectTimeout(80000);
conn.setReadTimeout(80000);
conn.connect();
InputStream in = conn.getInputStream();
//新建一個byte數組buffer,將輸入流中讀到的數據寫入buffer中
byte [] buffer = new byte[1024 * 1024];
//每次讀到的數據長度
int len;
while ((len = in.read(buffer)) != -1) {
//將每次讀取到的數據寫入SD卡中的文件裏
fos.write(buffer,0,len);
}
Log.i("TAG", "download success");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
/**
* 權限申請的回調函數
* @param requestCode
* @param permissions
* @param grantResults
*/
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED){
//用戶已贊成該權限的申請
if (mUrl != null){
downloadApk(mUrl);
}
}else{
//用戶拒絕了權限的申請
Toast.makeText(this, "你拒絕了權限申請", Toast.LENGTH_SHORT).show();
}
break;
}
}
}複製代碼
每次有網絡請求時,若是都使用上面的方式來實現,效率顯然是極低的,由於每次咱們都要把全部的代碼都再寫一遍。更好的想法就是將網絡請求封裝成一個工具類,每次要用的時候直接調用這個工具類的相關方法。我對 HttpUrlConnection 進行了一個簡單的封裝。
在 HttpUtils 這個工具類中,提供了四個 public 的靜態方法:
String httpGet(String url)
String httpGet(String url, HttpCallback callback)
String httpPost(String url, List< PostParam > paramList)
String httpGet(String url, List< PostParam > paramList, HttpCallback callback)
前兩個是 GET 方法,後兩個是 POST 方法。具體的區別請看代碼以及註釋。
HttpUtils 類:
public class HttpUtils {
/**
* GET 方法,返回字符串類型的響應內容,返回值爲空表示失敗,不爲空表示成功了
* @param url 網址
* @return
*/
@Nullable
public static String httpGet(String url){
HttpResponse response = baseGet(url);
if(response.getCode() == 200){
//成功
Log.i(TAG, "httpGet: 成功");
return response.getContent();
}else{
//失敗
Log.i(TAG, "httpGet: 失敗---" + response.getCode());
return null;
}
}
/**
* GET方法,使用一個回調接口,成功則回調onSuccess方法,失敗則回調onError方法
* @param url 網址
* @param callback 回調接口
* @return
*/
@Nullable
public static String httpGet(String url, HttpCallback callback){
HttpResponse response = baseGet(url);
if(response.getCode() == 200){
//成功
Log.i(TAG, "httpGet: 成功");
callback.onSuccess(response.getContent());
}else{
//失敗
Log.i(TAG, "httpGet: 失敗---" + response.getCode());
callback.onError(response.getCode(), new Exception());
}
return null;
}
/**
* 基礎的GET實現,不對外公佈此方法,僅僅是被上面兩個方法調用
* 返回的HttpResponse類,包含狀態碼和響應內容。
* @param url 網址
* @return
*/
private static HttpResponse baseGet(String url){
HttpURLConnection conn = getHttpUrlConnection(url);
HttpResponse response = new HttpResponse();
BufferedReader reader;
try {
//設置請求方式
conn.setRequestMethod("GET");
//創建鏈接
conn.connect();
//獲取狀態碼
response.setCode(conn.getResponseCode());
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = reader.readLine()) != null){
sb.append(line);
}
//獲取響應內容
response.setContent(sb.toString());
//關閉流
reader.close();
//關閉鏈接
conn.disconnect();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
/**
* POST 方法,返回字符串類型的響應內容,返回值爲空表示失敗,不爲空表示成功了
* @param url 網址
* @param paramList Post提交的參數列表,鍵值對形式
* @return
*/
@Nullable
public static String httpPost(String url, List<PostParam> paramList){
HttpResponse response = basePost(url, paramList);
if(response.getCode() == 200){
//成功
Log.i(TAG, "httpPost: 成功");
return response.getContent();
}else{
//失敗
Log.i(TAG, "httpPost: 失敗---" + response.getCode());
return null;
}
}
/**
* POST方法,使用一個回調接口,成功則回調onSuccess方法,失敗則回調onError方法
* @param url 網址
* @param paramList post提交的參數列表
* @param callback 回調接口
* @return 返回值無心義
*/
@Nullable
public static String httpPost(String url, List<PostParam> paramList, HttpCallback callback){
HttpResponse response = basePost(url, paramList);
if(response.getCode() == 200){
//成功
Log.i(TAG, "httpPost: 成功");
callback.onSuccess(response.getContent());
}else{
//失敗
Log.i(TAG, "httpPost: 失敗---" + response.getCode());
callback.onError(response.getCode(), new Exception());
}
return null;
}
/**
* 基礎的POST實現,不對外公佈此方法,只被上面兩個POST方法調用
* @param url 網址
* @param paramList 提交的參數列表
* @return
*/
private static HttpResponse basePost(String url, List<PostParam> paramList){
HttpURLConnection conn = getHttpUrlConnection(url);
HttpResponse response = new HttpResponse();
String post = parseParamList(paramList);
BufferedReader reader;
try {
//設置請求方式
conn.setRequestMethod("POST");
//獲取輸出流並轉化爲處理流
DataOutputStream dos = new DataOutputStream(conn.getOutputStream());
//寫入參數
dos.writeUTF(post);
//創建鏈接
conn.connect();
//獲取狀態碼
response.setCode(conn.getResponseCode());
reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
StringBuffer sb = new StringBuffer();
String line;
while ((line = reader.readLine()) != null){
sb.append(line);
}
//獲取響應內容
response.setContent(sb.toString());
//關閉流
reader.close();
//關閉鏈接
conn.disconnect();
} catch (ProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return response;
}
/**
* 將參數列表轉化成一段字符串
* @param paramList
* @return
*/
@NonNull
private static String parseParamList(@NonNull List<PostParam> paramList){
StringBuffer sb = new StringBuffer();
for (PostParam param :
paramList) {
if(sb == null){
sb.append(param.toString());
}else{
sb.append("&"+param.toString());
}
}
return sb.toString();
}
/**
* 獲取HttpUrlConnection對象,並進行基礎網絡設置
* @param url
* @return
*/
private static HttpURLConnection getHttpUrlConnection(String url){
HttpURLConnection conn = null;
try {
//獲取HttpURLConnection對象
URL mUrl = new URL(url);
conn = (HttpURLConnection) mUrl.openConnection();
//進行一些通用設置
conn.setConnectTimeout(80000);
conn.setReadTimeout(80000);
conn.setRequestProperty("Conection","Keep-Alive");
conn.setDoInput(true);
conn.setDoOutput(true);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return conn;
}
/**
* 回調接口,有兩個方法,分別在成功和失敗時回調
*/
public interface HttpCallback{
void onSuccess(String response);
void onError(int responseCode, Exception e);
}
/**
* Post提交的參數類
* 包含String類型的name和String類型的value
*/
public class PostParam{
private String name;
private String value;
public PostParam(String name, String value){
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public String getValue() {
return value;
}
@Override
public String toString() {
return name+"="+value;
}
}
}複製代碼
HttpResponse 類
/**
* 包含網絡請求的響應狀態碼和響應內容
*/
public class HttpResponse {
private int code;
private String content;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}複製代碼
有了這個工具類,咱們實現實例一的功能,就能夠這樣來寫:
public void getCode(View view) {
new Thread(new Runnable() {
@Override
public void run() {
String url = "http://www.cnmooc.org";
String response = HttpUtils.httpGet(url);
if(response != null){
showCode(response);
}
}
}).start();
}
private void showCode(final String s) {
//必須在主線程中操做UI
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i(TAG, "run: code--->"+s);
tvCode.setText(s);
}
});
}複製代碼
或者使用帶回調接口的GET方法:
public void getCode(View view) {
new Thread(new Runnable() {
@Override
public void run() {
String url = "http://www.cnmooc.org";
HttpUtils.httpGet(url, new HttpUtils.HttpCallback() {
@Override
public void onSuccess(String response) {
showCode(response);
}
@Override
public void onError(int responseCode, Exception e) {
e.printStackTrace();
}
});
}
}).start();
}
private void showCode(final String s) {
//必須在主線程中操做UI
runOnUiThread(new Runnable() {
@Override
public void run() {
Log.i(TAG, "run: code--->"+s);
tvCode.setText(s);
}
});
}複製代碼
這篇文章中對 HttpUrlConnection 的用法、使用示例以及如何封裝一個簡單的 Http 工具類作了一個介紹,對於瞭解 HttpUrlConnection 的相關內容仍是有所幫助的。關於封裝部分,因爲水平有限,其實封裝的並很差,仍是須要先建立線程,在子線程中進行網絡操做,完了後也須要手動切換回主線程來操做 UI,在接下來學習第三方網絡加載框架時,會重點留意這個問題,學習如何更好地封裝,還請繼續支持。感恩。
再見。
這篇文章的參考資料主要有:
郭霖《第一行代碼(第二版)》
郭霖:Android 訪問網絡,使用 HttpURLConnection 仍是 HttpClient?
blog.csdn.net/guolin_blog…劉望舒:Android 網絡編程(二)HttpClient 與 HttpURLConnection
liuwangshu.cn/application…