[置頂] android網絡編程學習與實戰之旅一<HttpUrlConnection上傳單個或多個文件>
來源:程序員人生 發布時間:2016-07-28 09:28:35 閱讀次數:2847次
1.HttpUrlConnection類概述
HttpUrlConnection是1個HTTP協議的UrlConnection,用于通過web收發數據。數據可以是任意類型和長度。這個類主要用于收發提早不知長度的數據流。
這個類的用法遵守以下模式:
- 首先取得1個HttpUrlConnection的實例。通過調用URL類中的openConnection()函數。并做強迫類型轉換。
- 準備要求。要求的基本屬性是1個URL,要求頭可能包括1些元數據,比如:證書, 首選的內容類型,session和cookie等。
- 可選的上傳1個要求體。HttpUrlConnection的實例如果包括1個要求體的話,必須使用setDoOutput(true)函數設置1下。然后可以通過getOutPutStream取得1個輸出流,向流中寫入數據便可傳輸數據。
- 讀響應。典型響應頭包括著這樣1些元數據,比如:1.響應體的內容類型和長度,2.修改的日期,3.session和cookie等。響應體可以從getInputStread()返回的流中讀取。如果響應沒有響應體,getInputStread()返回為空。
- 斷開連接。1旦響應體被讀取,HttpUrlConnection應當通過調用disconnect()方法來關閉。這個關閉會釋放這個連接所持有的資源。
根據上述要求,初步寫以下實驗程序,這個程序的功能就是向服務器發送1個要求,并取得服務器的響應,把這些響應顯示在1個TextView中。
<span style="font-family:SimSun;font-size:14px;">public class MainActivity extends AppCompatActivity {
URL url = null;
TextView textView;
Handler handler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = (TextView) findViewById(R.id.text);
Log.d("hello","oncreate");
handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.d("hello",msg.getData().getString("name"));
textView.setText(msg.getData().getString("name"));
return true;
}
});
new Thread(new Runnable() {
HttpURLConnection connection;
@Override
public void run() {
try {
url = new URL("https://www.baidu.com/?tn=57095150_1_oem_dg");
connection = (HttpURLConnection) url.openConnection();
InputStream in = new BufferedInputStream(connection.getInputStream());
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line=bufferedReader.readLine())!=null){
stringBuilder.append(line);
}
Message message = new Message();
Bundle bundle= new Bundle();
bundle.putString("name",stringBuilder.toString());
message.setData(bundle);
handler.sendMessage(message);
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
connection.disconnect();
}
}
}).start();
}
}</span>
這段代碼訪問https://www.baidu.com/?tn=57095150_1_oem_dg頁面,并把取得的內容放到Textview中顯示。需要注意的幾點:
- Android的Ui線程不可以訪問網絡等耗時的工作,所以這里將其放在1個子線程中。
- TextView1頁可能顯示不完,最好把TextView放到1個ScrollView中,這樣就能夠向下轉動查看所有內容了。
- 使用Handler可Message更新UI.
- 不要忘記添加網絡訪問權限:
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
1.2.基于HTTPS安全連接
首先也是使用openConnection()方法取得1個HttpsURLConnection
實例。這個URL固然是使用“https://”開頭的URL.它允許覆寫
HostnameVerifier
和SSLSocketFactory接口。1個支持SSLSocketFactory的利用程序可以提供1個自定義的X509TrustManager,
用于證書鏈的驗證,并且1個自定義的X509KeyManager用于客戶真個驗證。
1.3.發送內容
為了上傳數據到服務器,配置連接為輸出,使用setDoOutput(true),為了更高實現更好的額性能,是使用setFixedLengthStreamingMode(int)
設置數據長度,如果
數據長度提早可以知道的話,或使用setChunkedStreamingMode(int)當不知道數據長度的時候。否則,
HttpUrlConnection將
會緩沖完全的要求體,緩沖完成后才會發送,這致使了內存的浪費和延遲的增加。
2.上傳1張或多張圖片到服務器
通過以上理論知識的學習,上傳1張圖片到服務器成為可能,為了完成這個功能,首先需要解決以下問題:
解決方法是:自己搭建Apache服務器,接收使用php好了,由于php之前略有涉略,其他的Web服務器真個語言均1無所知。
上傳的文件在哪里呢?簡單起見,就是放在assets目錄下的文件。assets目錄下的文件可使用AssetManager進行管理,非常方便,為了使用assets目錄,在實現上傳文件功能之前,先做這樣1件嘗試:
2.1把assets目錄下的1張圖片顯示到ImageView中
這是很簡單的1步,為了項目的完全,還是把它貼出來,這是1個讀assets目錄下文件的方法:
<span style="font-family:SimSun;font-size:14px;"> public Bitmap readAssetsFile(String name){
Bitmap image = null;
InputStream inputStream = null;
try {
inputStream = getResources().getAssets().open(name);
image = BitmapFactory.decodeStream(inputStream);
} catch (IOException e) {
e.printStackTrace();
}
finally {
if(inputStream != null) try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return image;
}</span>
2.2 php保存上傳的文件
由于我對php只是了解性的學習過,所以這部份也單獨拿出來,先把它做好,然后再往下做。這個進程分為以下幾步:
- 先搭建好服務器,建議直接裝wamp,這個比較簡單,然后啟動它。
- 使用html給上傳文件,并用php腳本接受文件,確保php可以正常工作。
- 編寫android端代碼并且測試。
服務器的搭建這里就不啰嗦了,啟動它也沒啥好說的,現在測試使用html上傳文件,并用php接受的代碼。
html的代碼以下:
<span style="font-family:SimSun;font-size:14px;"><!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf⑻" />
<title>file upload</title>
</head>
<body>
<form action="http://127.0.0.1:8088/php/fileupload.php" method="post" enctype="multipart/form-data">
<input type="file" name="myfile" class="file" id="fileField" onchange="document.getElementById('textfield').value=this.value" />
<input type="submit" name="submit" value="上傳" />
</form>
</body>
</html></span>
這個代碼非常簡單,就是使用1個表單上傳文件。它的模樣是這樣的:
php代碼以下:
<span style="font-family:SimSun;font-size:14px;"><pre name="code" class="php"><?php
echo "file arrived";
echo "Upload: ".$_FILES["myfile"]["name"]."<br />";
echo "Type: ".$_FILES["myfile"]["type"]."<br />";
echo "Size: ".($_FILES["myfile"]["size"]/1024)." Kb<br />";
echo "Temp file: " . $_FILES["myfile"]["tmp_name"]."<br />";
if (file_exists("D:/wamp/www/"."upload/" . $_FILES["myfile"]["name"]))
{
echo $_FILES["myfile"]["name"]." already exists. ";
}
else
{
if(move_uploaded_file($_FILES["myfile"]["tmp_name"],"D:/wamp/www/"."upload/" . $_FILES["myfile"]["name"])){
echo "Stored in: " . "upload/" . $_FILES["myfile"]["name"];
}else{
echo "Store failed";
}
}
?></span>
這段代碼中,為了方便調錯,首先將上傳文件的信息都打印出來。然后 判斷這個文件在服務器的upload目錄下存在不?存在就甚么都不做,不存在就把它存儲下來。這里要注意:文件的路勁要用絕對路勁,不然會報錯,甚么緣由這里就不深究了,反正加上絕對路勁就能夠了。就這樣簡簡單單的1些代碼,就實現了文件的上傳與保存。這些代碼主要用來測試服務器端可以正常的接受并且保存文件。有了這些驗證,我們就能夠安心的專注于Android真個代碼了。
這里把這段代碼接遭到上傳文件后頁面與文件上傳到服務器的情形給個交代:
2.3android端上傳文件的代碼
在正式寫代碼之前,先回顧1下第1小節的基礎內容。通過第1小節的學習,知道了怎樣訪問1個服務器,怎樣獲得服務器的響應,怎樣發送數據塊給服務器。可以想象1下,上傳1張圖片就是把1個數據塊發送給服務器,所以這里的知識已基本具有了。但是為了更好的理解HttpUrlConnection這個類,在看看這個類的API文檔,耐心的把它看完,寫代碼是1件不用著急的事情。
2.3.1性能
為了能寫出比較好的代碼,學習如何提高性能是很有必要的。除1.3介紹的設置發送的內容的長度或明確告知不知道數據內容長度外,還需要注意甚么呢?
- 這個類返回的輸入輸出流是不緩沖存。大部份時候都需要把輸入輸出流包裝為緩沖流。如果是直接簡單的對大塊數據進行讀寫,也能夠疏忽緩存。
- 為了減少延遲,這個類可能會為多個要求/ 響應對使用相同的底層的socket。這致使http連接會保持打開狀態很長時間,所以當不要連接的時候。通過設置http.keepAlive系統屬性為false,這類行動可以被制止。使用http.maxConnections可以設置每一個連接到服務器的最大的空閑連接的數目。
- 默許的,這個類要求服務器的時候會使用gzip自動緊縮,并且當調用者調用getInputSream時會自動解緊縮。這類情況下,Content-Encoding 和Content-Length要被聲明,gzip緊縮可以被關閉使用以下方法:
<span style="font-family:SimSun;font-size:14px;">urlConnection.setRequestProperty("Accept-Encoding", "identity");</span>
- getContentLength()可以取得傳輸的字節數,但是不能被用作獲得getInputStread中可被讀取的字節數。取而代之的是,應當使用read()方法,直到它返回⑴。表明數據讀完。
2.3.2處理登錄
1些wifi網絡阻塞用戶訪問因特網,直到用戶通過登錄頁面。這類登錄頁面典型的代表是使用http重定向。你可使用getUrl()方法可以堅持連接有無被意外的重定向。這類檢查在已接遭到響應頭以后就不可用了,這里的響應頭的獲得可使用
getHeaderFields()
或getInputStream()方法。比如,為了檢查1個響應有無重定向到另外1個主機,可以像以下這樣:
<span style="font-family:SimSun;font-size:14px;"> HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
InputStream in = new BufferedInputStream(urlConnection.getInputStream());
if (!url.getHost().equals(urlConnection.getURL().getHost())) {
// we were redirected! Kick the user out to the browser to sign on?
...
} finally {
urlConnection.disconnect();
}
}</span>
2.3.2HTTP認證
HttpUrlConnection支持HTTP基本的認證功能。使用Authenticator支持虛擬機級別的認證處理:
<span style="font-family:SimSun;font-size:14px;"> Authenticator.setDefault(new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password.toCharArray());
});
}</span>
除非使用HTTPS,否則,這不是1種安全的用戶認證方式。特別是,用戶名,密碼,要求,響應都沒有被加密卻在網絡間傳遞。
2.4rfc1867協議
http本來不支持文件上傳,為了支持文件上傳,rfc1867協議對其做了修改,主要設計兩方面:要求頭的修改和要求體的修改。
2.4.1要求頭的修改
Content-Type由原來的
Content-Type: application/x-www-form-urlencoded
改成了:
Content-Type: multipart/form-data; boundary=------⑺d71f4234700b8
其中,mutipart/form-data;是必須的,boundary再要求體中用到,它會把要求體分成多個塊,1塊代表1塊獨立的數據,或說是文件吧。
2.4.2要求體的修改
<span style="font-family:SimSun;font-size:14px;">----------------------------⑺d71f4234700b8
Content-Disposition: form-data; name="formhash"
59329e15
----------------------------⑺d71f4234700b8
Content-Disposition: form-data; name="isblog"
----------------------------⑺d71f4234700b8
Content-Disposition: form-data; name="fid"
104
----------------------------⑺d71f4234700b8</span>
可以看到要求體編程了多個塊,每塊可以代表1個獨立的文件。這里的name和php中$_FILES_[name]保持1致,也和input標簽的name屬性的值保持1致。后面還可加文件名,文件長度等信息。
2.5正式寫android代碼
2.5.1上傳1個文件
android的代碼主要難點是設置http和rfc1867協議的1些參數,那現在就以下面1個完全的POST要求的格式為例,逐條配置android代碼:
<span style="font-family:SimSun;font-size:14px;">POST /upload_file/UploadFile HTTP/1.1
Accept: text/plain, */*
Accept-Language: zh-cn
Host: 192.168.29.65:80
Content-Type:multipart/form-data;boundary=--------------------------⑺d33a816d302b6
User-Agent: Mozilla/4.0 (compatible; OpenOffice.org)
Content-Length: 424
Connection: Keep-Alive ----------------------------⑺d33a816d302b6
Content-Disposition:form-data;
name="userfile1";
filename="E:\s"Content-Type:
application/octet-stream abbXXXccc
----------------------------⑺d33a816d302b6
Content-Disposition: form-data;
name="text1" foo
----------------------------⑺d33a816d302b6
Content-Disposition: form-data;
name="password1" bar
----------------------------⑺d33a816d302b6-- </span>
下面的代碼逐條配置了上面模板中的條目,代碼中注釋有很詳細的解釋:
<span style="font-family:SimSun;font-size:14px;">package com.konka.networktest;
import android.content.Context;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
/**
* Created by Jinwei on 2016/6/29.
*/
public class Utils {
public static void doupLoadPic(Context context, Handler handler){
//首先設置1些常量
final String end = "\r\n";
final String twoHyphens = "--";
final String boundary = "******";
URL url = null;
HttpURLConnection httpURLConnection = null;
//1.創建URL,并且獲得連接
try {
url = new URL("http://192.168.0.103:8088/php/fileupload.php");
httpURLConnection = (HttpURLConnection) url.openConnection();
//2.根據這個類的使用要求,要配置1些必要的參數
httpURLConnection.setChunkedStreamingMode(128 * 1024);// 128K,這個配置可以提高性能
httpURLConnection.setDoOutput(true); //允許輸出。
httpURLConnection.setDoInput(true); //允許讀入,后面我們要讀取
服務器端返回的信息
httpURLConnection.setUseCaches(false); //不使用緩沖
//3.設置rfc1867要求頭
//3.1要求行設置 POST /upload_file/UploadFile HTTP/1.1
httpURLConnection.setRequestMethod("POST"); //要求行只需設置這1個參數便可
//3.2 Accept: text/plain, */*
httpURLConnection.setRequestProperty("Accept","ext/plain, */*");
//3.3 Accept-Language: zh-cn
httpURLConnection.setRequestProperty("Accept-Language","zh-cn");
//3.3 Host: 192.168.0.105:80
//Host 接受響應的主機,默許就是發起連接的這里,所以這里不設置
//3.4 Content-Type:multipart/form-data;boundary=--------------------------⑺d33a816d302b6
httpURLConnection.setRequestProperty("Content-Type","multipart/form-data;boundary="+boundary);
//3.5 //User-Agent: Mozilla/4.0 (compatible; OpenOffice.org)
//閱讀器類型,和我們無關,不設置
//3.6 //Content-Length: 424
//數據長度,這個類不是可以收發不知長度的數據流嗎?我們不知道長度的話就不設置了。
//3/7 //Connection: Keep-Alive ----------------------------⑺d33a816d302b6
httpURLConnection.setRequestProperty("Connection","Keep-Alive");//這里要注意,keep-Alive后面的是要求體中的內容了。所以,要求頭就設置完了
//4,設置要求體
//4.1獲得輸出流
DataOutputStream dout = new DataOutputStream(httpURLConnection.getOutputStream());
//4.2 寫分隔符,先把Keep-Alive后面的事情做完
dout.writeBytes("--"+boundary+end);
//4.3 Content-Disposition:form-data;
dout.writeBytes("Content-Disposition:form-data;");
//4.4 name="userfile1";
dout.writeBytes("name="+"\"myfile\";");
//4.5 filename="E:\s"
dout.writeBytes("filename="+"\"one.jpg\"");
//注意這里以后有兩個回車換行
dout.writeBytes(end);
dout.writeBytes(end);
//4.6 application/octet-stream abbXXXccc,這里就是正文了,而正文需要讀文件,以下是讀文件的部份
AssetManager am = context.getAssets();
InputStream inputStream = am.open("one.jpg");
//4.7有了文件輸入流后,我們把文件讀出來,寫到http要求體中
byte[] buffer = new byte[8192]; // 8k
int count = 0;
while ((count = inputStream.read(buffer)) != ⑴) {
dout.write(buffer, 0, count);
}//這樣文件就寫完了
//我們只有1個文件,就這樣就能夠了。依照格式,文件結束還要寫入分隔符
dout.writeBytes(end+"--"+boundary+end);
//這樣發送部份就做完了。
//另外,我們還希望接受返回的數據。
//1.打開讀流
BufferedReader br = new BufferedReader(new InputStreamReader(httpURLConnection.getInputStream(),"utf⑻"));
StringBuilder builder = new StringBuilder();
String result;
//2.開始讀入
while((result = br.readLine())!= null){
builder.append(result);
}
//通知UI更新
Message message = new Message();
Bundle bundle= new Bundle();
bundle.putString("name",builder.toString());
message.setData(bundle);
handler.sendMessage(message);
//最后做1些清算工作
br.close();
inputStream.close();
dout.close();
httpURLConnection.disconnect();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
</span>
這個這個類我完全的貼出來了,這個類中的hander用于通知UI已收到了數據了。注意,這里并沒有管文件的類型,服務器端也沒有檢查文件的類型,注意1下,需要的時候把它們加上。
2.5.2上傳多個文件
只要會上傳1個文件,而且知道了上傳多個文件就是使用"--"+boundary+"/r/n"把多個文件分開,然后在分割附后添加文件的類型,文件名等信息就能夠了,以以下出添加的代碼:
<span style="font-family:SimSun;font-size:14px;"> //發送第2個文件
//boundary已寫入了,現在寫文件的類型,名字等信息
//4.3 Content-Disposition:form-data;
dout.writeBytes("Content-Disposition:form-data;");
//4.4 name="userfile1";
dout.writeBytes("name="+"\"myfile1\";");
//4.5 filename="E:\s"
dout.writeBytes("filename="+"\"two.jpg\"");
//注意這里以后有兩個回車換行
dout.writeBytes(end);
dout.writeBytes(end);
//打開第2個文件的流
InputStream inputStream1 = am.open("two.jpg");
while ((count = inputStream1.read(buffer)) != ⑴) {
dout.write(buffer, 0, count);
}//這樣第2個文件就寫完了
inputStream1.close();
//寫入分隔符
dout.writeBytes(end+"--"+boundary+end);</span>
服務器端也只是簡單的把接受1個文件的代碼復制1份:
<span style="font-family:SimSun;font-size:14px;">//第2個文件
echo "second file arrived";
echo "Upload: ".$_FILES["myfile1"]["name"]."<br />";
echo "Type: ".$_FILES["myfile1"]["type"]."<br />";
echo "Size: ".($_FILES["myfile1"]["size"]/1024)." Kb<br />";
echo "Temp file: " . $_FILES["myfile"]["tmp_name"]."<br />";
if (file_exists("D:/wamp/www/"."upload/" . $_FILES["myfile1"]["name"]))
{
echo $_FILES["myfile1"]["name"]." already exists. ";
}
else
{
if(move_uploaded_file($_FILES["myfile1"]["tmp_name"],"D:/wamp/www/"."upload/" . $_FILES["myfile1"]["name"])){
echo "Stored in: " . "upload/" . $_FILES["myfile1"]["name"];
}else{
echo "Store failed";
}
} </span>
圖片展現以下:
手機端收到的信息:
服務器端多了兩個文件:

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈