github:https://github.com/MichaelBeechan
CSDN:https://blog.csdn.net/u011344545
============================================================
一、從網絡上獲取數據(圖片、網頁、XML、JSON等)
1.從網絡獲取一張圖片,然後顯示在手機上
[java] view plaincopy
- ①public byte [] getImageFromNet(){
- try {
- URL url = new URL("http://img10.360buyimg.com/n1/4987/9dceed99-e710-4ca8-b7f1-4e9dc01a0f75.jpg");
- HttpURLConnection conn = (HttpURLConnection)url.openConnection();
- conn.setRequestMethod("GET");
- conn.setConnectTimeout(5 * 1000);
- conn.connect();
- InputStream inStream = conn.getInputStream();
- byte [] data = readInputStream(inStream);//獲取圖片的二進制數據
- //FileOutputStream outStream = new FileOutputStream("360buy.jpg");
- //outStream.write(data);
- //outStream.close();
- return data;
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- private byte [] readInputStream(InputStream inStream) throws IOException {
- ByteArrayOutputStream byteOutputStream = new ByteArrayOutputStream();
- byte[] buffer = new byte[1024];
- int len = -1;
- while((len = inStream.read(buffer)) != -1){
- byteOutputStream.write(buffer, 0, len);
- }
- inStream.close();
- byte [] data = byteOutputStream.toByteArray();
- byteOutputStream.close();
- return data;
- }
②使用ImageView組件顯示圖片。
③生成位圖並設置到ImageView中
[java] view plaincopy
- Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
- imageView.setImageBitmap(bitmap);
④在AndroidManifest.xml文件添加網絡訪問權限:
[java] view plaincopy
- <uses-permission android:name="android.permission.INTERNET"/>
2.從網絡獲取指定網頁的html代碼,然後顯示在手機上
[java] view plaincopy
- ①public String getHtmlCodeFromNet(){
- try {
- URL url = new URL("http://www.163.com");
- HttpURLConnection conn = (HttpURLConnection)url.openConnection();
- conn.setRequestMethod("GET");
- conn.setConnectTimeout(5 * 1000);
- conn.connect();
- InputStream inStream = conn.getInputStream();
- byte [] data = readInputStream(inStream);
- String htmlString = new String(data, "gb2312");
- System.out.println(htmlString);
- return htmlString;
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
②使用TextView組件顯示網頁代碼
ScrollView 滾動條
[java] view plaincopy
- <ScrollView
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:id="@+id/textView"
- />
- </ScrollView>
③在AndroidManifest.xml文件添加網絡訪問權限:
[java] view plaincopy
- <uses-permission android:name="android.permission.INTERNET"/>
3.從服務器上獲取最新的視頻資訊信息,該信息以XML格式返回給Android客戶端,然後列表顯示在手機上。
>>最新資訊
喜羊羊與灰太狼 時長:60
盜夢空間 時長:120
生化危機 時長:100
①開發web端,在此採用Struts 2技術
②設計顯示界面,使用ListView
③開發Android手機視頻資訊客戶端
注意:不能使用127.0.0.1或者localhost訪問在本機開發的web應用
部分代碼:
[java] view plaincopy
- public List<Video> getXMLLastVideos(String urlPath) throws Exception{
- URL url = new URL(urlPath);
- HttpURLConnection conn = (HttpURLConnection)url.openConnection();
- conn.setRequestMethod("GET");
- conn.setConnectTimeout(5 * 1000);
- conn.connect();
- InputStream inStream = conn.getInputStream();
- return parseXML(inStream);
- }
[java] view plaincopy
- private List<Video> parseXML(InputStream inStream) throws Exception {
- List<Video> videos = null;
- Video video = null;
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(inStream, "UTF-8");
- int eventType = parser.getEventType();
- while(eventType != XmlPullParser.END_DOCUMENT){
- switch (eventType) {
- case XmlPullParser.START_DOCUMENT:
- videos = new ArrayList<Video>();
- break;
- case XmlPullParser.START_TAG:
- String name = parser.getName();
- if("video".equals(name)){
- video = new Video();
- video.setId(new Integer(parser.getAttributeValue(0)));
- }
- if(video != null){
- if("title".equals(name)){
- video.setTitle(parser.nextText());
- }else if("timeLength".equals(name)){
- video.setTimeLength(new Integer(parser.nextText()));
- }
- }
- break;
- case XmlPullParser.END_TAG:
- String pname = parser.getName();
- if("video".equals(pname)){
- videos.add(video);
- video = null;
- }
- break;
- default:
- break;
- }
- eventType = parser.next();
- }
- return videos;
- }
④在AndroidManifest.xml文件添加網絡訪問權限:
[java] view plaincopy
- <uses-permission android:name="android.permission.INTERNET"/>
4.從服務器上獲取最新的視頻資訊信息,該信息以JSON格式返回給Android客戶端,然後列表顯示在手機上。
服務器端需要返回的JSON數據:
[java] view plaincopy
- [{id:1,title:"aaa1",timeLength:50},{id:2,title:"aaa2",timeLength:50},{id:3,title:"aaa3",timeLength:50}]
[java] view plaincopy
- public List<Video> getJSONLastVideos(String urlPath) throws Exception{
- URL url = new URL(urlPath);
- HttpURLConnection conn = (HttpURLConnection)url.openConnection();
- conn.setRequestMethod("GET");
- conn.setConnectTimeout(5 * 1000);
- conn.connect();
- InputStream inStream = conn.getInputStream();
- byte [] data = StreamTools.readInputStream(inStream);
- String json = new String(data);
- JSONArray array = new JSONArray(json);
- List<Video> videos = new ArrayList<Video>();
- for(int i = 0;i < array.length(); i++){
- JSONObject item = array.getJSONObject(i);
- int id = item.getInt("id");
- String title = item.getString("title");
- int timeLength = item.getInt("timeLength");
- videos.add(new Video(id, title, timeLength));
- }
- return videos;
- }
二、通過HTTP協議提交文本數據(GET/POST)
GET、POST、HttpClient
1.通過GET方式提交參數給服務器:注意處理亂碼(Android系統默認編碼是UTF-8),提交的數據最大2K。
①服務器端代碼
[java] view plaincopy
- HttpServletRequest request = ServletActionContext.getRequest();
- //服務器端編碼處理,先以ISO-8859-1編碼得到二進制數據,然後使用UTF-8對數據重新編碼
- byte [] data = request.getParameter("title").getBytes("ISO-8859-1");
- String titleString = new String(data, "UTF-8");
- System.out.println("this.title==" + titleString);
- System.out.println("this.timeLength==" + this.timeLength);
②客戶端代碼
[java] view plaincopy
- public boolean sendGetRequest(String path, Map<String, String> params, String enc) throws Exception{
- StringBuilder sb = new StringBuilder(path);
- sb.append('?');
- if(params != null && !params.isEmpty()){
- for(Map.Entry<String, String> entry : params.entrySet()){
- sb.append(entry.getKey())
- .append('=')
- //對客戶端發送GET請求的URL重新編碼
- .append(URLEncoder.encode(entry.getValue(), enc))
- .append('&');
- }
- sb.deleteCharAt(sb.length()-1);
- }
- URL url = new URL(sb.toString());
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("GET");
- conn.setConnectTimeout(5 * 1000);
- if(conn.getResponseCode() == 200){
- return true;
- }
- return false;
- }
2.通過POST方式提交參數給服務器:
①<form method="post"/> 瀏覽器會把提交的數據轉換成Http協議
②分析Http協議(使用HttpWatch)
第一部分:發送給服務器的
請求頭部分(**********表示Http協議中必須提供的部分)
[java] view plaincopy
- POST /videoweb/managerPost.action HTTP/1.1---(請求方式 請求路徑 使用Http協議是1.1)
- Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel,
- application/vnd.ms-powerpoint, application/msword, application/QVOD, application/QVOD, */* ---(瀏覽器接收的數據類型)
- Referer: http://127.0.0.1:8081/videoweb/index.jsp---(請求來源,即從哪個頁面發出請求的)
- Accept-Language: zh-cn,en-US;q=0.5
- User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; CIBA; .NET CLR 2.0.50727) ---(用戶的瀏覽器類型)
- Content-Type: application/x-www-form-urlencoded ---(POST請求的內容類型)**********
- Accept-Encoding: gzip, deflate
- Host: 127.0.0.1:8081 ---(POST請求的服務器主機名和端口)**********
- Content-Length: 46 ---(POST請求的內容長度,即實體數據部分的長度)**********
- Connection: Keep-Alive ---(長連接)
- Cache-Control: no-cache
- Cookie: JSESSIONID=EFD762A0997BE1191DABFC311B345EE7
實體數據部分
[java] view plaincopy
- title=aaa&timeLength=22&sub=%E6%8F%90%E4%BA%A4
第二部分:客戶端接收到的
[java] view plaincopy
- HTTP/1.1 200 OK
- Server: Apache-Coyote/1.1
- Content-Type: text/html;charset=UTF-8
- Content-Length: 275
- Date: Sun, 06 Mar 2011 10:57:55 GMT
- <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
- <html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
- <title>Insert title here</title>
- </head>
- <body>
- 淇濆瓨瀹屾垚錛?
- </body>
- </html>
③服務器端代碼
[java] view plaincopy
- HttpServletRequest request = ServletActionContext.getRequest();
- request.setCharacterEncoding("UTF-8");
- System.out.println("doPostRequest");
- System.out.println("this.title==" + this.title);
- System.out.println("this.timeLength==" + this.timeLength);
④客戶端代碼
[java] view plaincopy
- public boolean sendPostRequest(String path, Map<String, String> params, String enc) throws Exception{
- //分析http協議
- //發出post請求時,瀏覽器會自動爲實體數據部分進行重新編碼。由於我們使用的是Android,沒有用IE瀏覽器,因此需要手動對URL重新編碼。
- //username=%E5%BC%A0%E4%B8%89&sub=%E7%99%BB%E9%99%86
- StringBuilder sb = new StringBuilder();
- if(params != null && !params.isEmpty()){
- for(Map.Entry<String, String> entry : params.entrySet()){
- sb.append(entry.getKey())
- .append('=')
- //對客戶端post請求的URL手動重新編碼
- .append(URLEncoder.encode(entry.getValue(), enc))
- .append('&');
- }
- sb.deleteCharAt(sb.length()-1);
- }
- URL url = new URL(path);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("POST");
- conn.setConnectTimeout(5 * 1000);
- //如果通過post提交數據,必須設置允許對外輸出數據。
- conn.setDoOutput(true);
- //Content-Type: application/x-www-form-urlencoded
- //Content-Length: 46 獲取實體數據的二進制長度
- byte [] data = sb.toString().getBytes();
- //設置請求屬性
- conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
- conn.setRequestProperty("Content-Length", String.valueOf(data.length));
- OutputStream outputStream = conn.getOutputStream();
- outputStream.write(data);
- outputStream.flush();
- outputStream.close();
- if(conn.getResponseCode() == 200){
- return true;
- }
- return false;
- }
3.使用HttpClient開源項目提交參數給服務器
①服務器端代碼
[java] view plaincopy
- HttpServletRequest request = ServletActionContext.getRequest();
- request.setCharacterEncoding("UTF-8");
- System.out.println("this.title==" + this.title);
- System.out.println("this.timeLength==" + this.timeLength);
②客戶端代碼
[java] view plaincopy
- public boolean sendRequestByHttpClient(String path, Map<String, String> params, String enc) throws Exception{
- //名值對
- List<NameValuePair> paramPairs = new ArrayList<NameValuePair>();
- if(params != null && !params.isEmpty()){
- for(Map.Entry<String, String> entry : params.entrySet()){
- paramPairs.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
- }
- }
- //對實體數據進行重新編碼
- UrlEncodedFormEntity entity = new UrlEncodedFormEntity(paramPairs, enc);
- //相當於form
- HttpPost post = new HttpPost(path);
- post.setEntity(entity);
- //相當於客戶端瀏覽器
- DefaultHttpClient client = new DefaultHttpClient();
- //執行請求
- HttpResponse response = client.execute(post);
- if(response.getStatusLine().getStatusCode() == 200){
- return true;
- }
- return false;
- }
三、通過HTTP協議上傳文件數據
分析上傳文件的HTTP協議
Content-Type: multipart/form-data; boundary=---------------------------7db1861b605fa
實體數據分隔線:用於分隔每一個請求參數
示例:
(1)定義部分:boundary=---------------------------7db1861b605fa
(2)實體數據部分: -----------------------------7db1861b605fa(多出兩個--)
-----------------------------7db1861b605fa--(最後的--表示實體數據部分結束)
服務器對上傳文件大小有限制,一般最大是2M(文件過大時不建議使用HTTP協議)。
[java] view plaincopy
- /**
- * 上傳文件
- */
- public class FormFile {
- /* 上傳文件的數據 */
- private byte[] data;
- private InputStream inStream;
- private File file;
- /* 文件名稱 */
- private String filename;
- /* 請求參數名稱*/
- private String parameterName;
- /* 內容類型 */
- private String contentType = "application/octet-stream";
- //上傳小容量的文件建議使用此構造方法
- public FormFile(String filename, byte[] data, String parameterName, String contentType) {
- this.data = data;
- this.filename = filename;
- this.parameterName = parameterName;
- if(contentType != null)
- this.contentType = contentType;
- }
- //上傳大容量的文件建議使用此構造方法
- public FormFile(String filename, File file, String parameterName, String contentType) {
- this.filename = filename;
- this.parameterName = parameterName;
- this.file = file;
- try {
- this.inStream = new FileInputStream(file);
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- }
- if(contentType != null)
- this.contentType = contentType;
- }
- ..................................
- }
- public static boolean post(String path, Map<String, String> params, FormFile[] files) throws Exception {
- // 定義客戶端socket對象
- Socket socket = null;
- // 定義字節輸入流對象
- OutputStream outStream = null;
- // 定義字符輸入流對象
- BufferedReader reader = null;
- try{
- // 定義數據分隔線
- final String BOUNDARY = "---------------------------7db1861b605fb";
- // 定義數據結束標誌
- final String ENDLINE = "--" + BOUNDARY + "--\r\n";
- // 獲取實體數據內容及其總長度
- // 定義保存文本類型實體數據的字符串
- StringBuilder textEntity = new StringBuilder();
- int textDataLength = 0;
- // 1、獲取文本類型參數的實體數據及長度
- for (Map.Entry<String, String> entry : params.entrySet()) {
- textEntity.append("--");
- textEntity.append(BOUNDARY);
- textEntity.append("\r\n");
- textEntity.append("Content-Disposition: form-data; name=\"" + entry.getKey() + "\"\r\n\r\n");
- textEntity.append(entry.getValue());
- textEntity.append("\r\n");
- }
- byte [] textData = textEntity.toString().getBytes(enc);
- textDataLength = textData.length;
- int fileDataLength = 0;
- // 定義保存文件類型實體數據的字符串
- StringBuilder fileEntity = new StringBuilder();
- byte [] fileData = null;
- // 2、獲取文件類型參數的實體數據及長度
- for (FormFile uploadFile : files) {
- fileEntity.append("--");
- fileEntity.append(BOUNDARY);
- fileEntity.append("\r\n");
- fileEntity.append("Content-Disposition: form-data;name=\""
- + uploadFile.getParameterName() + "\";filename=\""
- + uploadFile.getFilename() + "\"\r\n");
- fileEntity.append("Content-Type: " + uploadFile.getContentType() + "\r\n\r\n");
- fileData = fileEntity.toString().getBytes(enc);
- fileDataLength += fileData.length;
- fileDataLength += "\r\n".getBytes(enc).length;
- if (uploadFile.getInStream() != null) {
- fileDataLength += uploadFile.getFile().length();
- } else {
- fileDataLength += uploadFile.getData().length;
- }
- }
- // 計算傳輸給服務器的實體數據總長度
- int dataLength = textDataLength + fileDataLength + ENDLINE.getBytes(enc).length;
- System.out.println("dataLength: " + dataLength);
- // 編寫HTTP協議發送數據
- URL url = new URL(destpath);
- int port = url.getPort() == -1 ? 80 : url.getPort();
- System.out.println("url.getHost(): " + url.getHost());
- System.out.println("port: " + port);
- // 創建Socket連接
- socket = new Socket(InetAddress.getByName(url.getHost()), port);
- // 獲取輸入流對象
- outStream = socket.getOutputStream();
- /** 下面完成HTTP請求頭的發送 start **/
- StringBuilder requestHead = new StringBuilder();
- requestHead.append("POST " + url.getPath() + " HTTP/1.1\r\n");
- requestHead.append("Accept: image/gif, image/jpeg, image/pjpeg, image/pjpeg, application/x-shockwave-flash, application/xaml+xml, " +
- "application/vnd.ms-xpsdocument, application/x-ms-xbap, application/x-ms-application, application/vnd.ms-excel, " +
- "application/vnd.ms-powerpoint, application/msword, */*\r\n");
- requestHead.append("Accept-Language: zh-CN\r\n");
- requestHead.append("Content-Type: multipart/form-data; boundary=" + BOUNDARY + "\r\n");
- requestHead.append("Host: " + url.getHost() + ":" + port + "\r\n");
- requestHead.append("Content-Length: " + dataLength + "\r\n");
- requestHead.append("Connection: Keep-Alive\r\n");
- // 根據HTTP協議在HTTP請求頭後面需要再寫一個回車換行
- requestHead.append("\r\n".getBytes(enc));
- outStream.write(requestHead.toString().getBytes(enc));
- /** 上面完成HTTP請求頭的發送 end **/
- /** 下面發送實體數據 start **/
- // 發送所有文本類型的實體數據
- outStream.write(textData);
- // 發送所有文件類型的實體數據
- for (FormFile uploadFile : files) {
- // 發送文件類型實體數據
- outStream.write(fileData);
- // 發送文件數據
- if (uploadFile.getInStream() != null) {
- byte[] buffer = new byte[1024];
- int len = 0;
- while ((len = uploadFile.getInStream().read(buffer)) != -1) {
- outStream.write(buffer, 0, len);
- }
- uploadFile.getInStream().close();
- } else {
- outStream.write(uploadFile.getData(), 0, uploadFile.getData().length);
- }
- outStream.write("\r\n".getBytes(enc));
- }
- // 發送數據結束標誌,表示數據已經結束
- outStream.write(ENDLINE.getBytes(enc));
- outStream.flush();
- /** 上面發送實體數據 end **/
- // 判斷數據發送是否成功
- reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- String content = reader.readLine();
- System.out.println("content: " + content);
- // 讀取web服務器返回的數據,判斷請求碼是否爲200,如果不是200,代表請求失敗
- if (content.indexOf("200") == -1) {
- return false;
- }
- } catch(Exception e){
- throw e;
- } finally {
- if(outStream != null){
- outStream.close();
- }
- if(reader != null){
- reader.close();
- }
- if(socket != null){
- socket.close();
- }
- }
- return true;
- }
- <!-- 訪問網絡的權限 -->
- <uses-permission android:name="android.permission.INTERNET"/>
- <!-- 在SDCard中創建與刪除文件權限 -->
- <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
- <!-- 往SDCard寫入數據權限 -->
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
四、通過HTTP協議發送XML數據(作爲實體數據,不是作爲請求參數),並調用WebService
調用WebService時需要發送XML實體數據
1、發送XML數據給服務器
[java] view plaincopy
- public boolean sendXML(String path, String xml) throws Exception{
- byte [] data = xml.getBytes();
- URL url = new URL(path);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("POST");
- conn.setConnectTimeout(5 * 1000);
- conn.setDoOutput(true);
- conn.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");
- conn.setRequestProperty("Content-Length", String.valueOf(data.length));
- OutputStream outputStream = conn.getOutputStream();
- outputStream.write(data);
- outputStream.flush();
- outputStream.close();
- if(conn.getResponseCode() == 200){
- return true;
- }
- return false;
- }
2、發送SOAP數據給服務器調用WebService,實現手機號歸屬地查詢
SOAP協議基於XML格式
http://www.webxml.com.cn/
http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx
SOAP 1.2 請求示例。所顯示的[]需替換爲實際值。
[java] view plaincopy
- POST /WebServices/MobileCodeWS.asmx HTTP/1.1
- Host: webservice.webxml.com.cn
- Content-Type: application/soap+xml; charset=utf-8
- Content-Length: length
- <?xml version="1.0" encoding="utf-8"?>
- <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
- <soap12:Body>
- <getMobileCodeInfo xmlns="http://WebXml.com.cn/">
- <mobileCode>[string]</mobileCode>
- <userID>[string]</userID>
- </getMobileCodeInfo>
- </soap12:Body>
- </soap12:Envelope>
SOAP 1.2 響應示例。所顯示的[]需替換爲實際值。
[java] view plaincopy
- HTTP/1.1 200 OK
- Content-Type: application/soap+xml; charset=utf-8
- Content-Length: length
- <?xml version="1.0" encoding="utf-8"?>
- <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
- <soap12:Body>
- <getMobileCodeInfoResponse xmlns="http://WebXml.com.cn/">
- <getMobileCodeInfoResult>[string]</getMobileCodeInfoResult>
- </getMobileCodeInfoResponse>
- </soap12:Body>
- </soap12:Envelope>
[java] view plaincopy
- /**
- * 發送SOAP數據給服務器調用WebService,實現手機號歸屬地查詢
- * @param path
- * @param soapXML
- * @return
- * @throws Exception
- */
- public String getMobileCodeInfo(String path, InputStream inStream, String mobileCode) throws Exception{
- String soapXML = readSoapXML(inStream, mobileCode);
- byte [] data = soapXML.getBytes();
- URL url = new URL(path);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("POST");
- conn.setConnectTimeout(5 * 1000);
- conn.setDoOutput(true);
- conn.setRequestProperty("Content-Type", "application/soap+xml; charset=utf-8");
- conn.setRequestProperty("Content-Length", String.valueOf(data.length));
- OutputStream outputStream = conn.getOutputStream();
- outputStream.write(data);
- outputStream.flush();
- outputStream.close();
- if(conn.getResponseCode() == 200){
- //byte [] responseData = StreamTools.readInputStream(conn.getInputStream());
- //String responseXML = new String(responseData);
- //return responseXML;
- return parseResponseXML(conn.getInputStream());
- }
- return null;
- }
- private String parseResponseXML(InputStream inStream) throws Exception{
- XmlPullParser parser = Xml.newPullParser();
- parser.setInput(inStream, "UTF-8");
- int eventType = parser.getEventType();
- while(eventType != XmlPullParser.END_DOCUMENT){
- switch (eventType) {
- case XmlPullParser.START_TAG:
- String name = parser.getName();
- if("getMobileCodeInfoResult".equals(name)){
- return parser.nextText();
- }
- break;
- }
- eventType = parser.next();
- }
- return null;
- }
- /**
- * 讀取MobileCodeWS.xml(要符合SOAP協議),並且替換其中的佔位符
- * @param inStream
- * @param mobileCode 真實手機號碼
- * @return
- * @throws Exception
- */
- public String readSoapXML(InputStream inStream, String mobileCode) throws Exception{
- byte [] data = StreamTools.readInputStream(inStream);
- String soapXML = new String(data);
- Map<String, String> params = new HashMap<String, String>();
- params.put("mobileCode", mobileCode);
- //替換掉MobileCodeWS.xml中佔位符處的相應內容
- return replace(soapXML, params);
- }
- private String replace(String soapXML, Map<String, String> params) throws Exception{
- String result = soapXML;
- if(params != null && !params.isEmpty()){
- for(Map.Entry<String, String> entry : params.entrySet()){
- String regex = "\\*"+ entry.getKey();
- Pattern pattern = Pattern.compile(regex);
- Matcher matcher = pattern.matcher(result);
- if(matcher.find()){
- result = matcher.replaceAll(entry.getValue());
- }
- }
- }
- return result;
- }
五、通過HTTP協議實現多線程斷點下載
使用多線程下載文件可以提高文件下載的速度,更快完成文件的下載。多線程下載文件之所以快,是因爲其搶佔的服務器資源多。假設服務器同時最多服務100個用戶,在服務器中一條線程對應一個用戶,100條線程在計算機中並非併發執行,而是由CPU劃分時間片輪流執行,如果A用戶使用了99條線程下載文件,那麼相當於佔用了99個用戶的資源,如果一秒內CPU分配給每條線程的平均執行時間是10ms,A用戶在服務器中一秒內就得到了990ms的執行時間,而其他用戶在一秒內只有10ms的執行時間。好比一個水龍頭,每秒出水量相等的情況下,放990毫秒的水肯定比放10毫秒的水要多。
1、多線程下載的實現過程:
①、首先得到下載文件的長度,然後設置本地文件的長度。
[java] view plaincopy
- //獲取下載文件的長度
- int fileLength = HttpURLConnection.getContentLength();
- //在本地硬盤創建和需下載文件長度相同的文件。
- RandomAccessFile file = new RandomAccessFile("QQ2011.exe", "rwd");
- //設置本地文件的長度和下載文件長度相同
- file.setLength(fileLength);
- file.close();
②、根據文件長度和線程數計算每條線程下載的數據長度和下載位置。
a:每條線程下載的數據長度: 文件長度 % 線程數 == 0 ? 文件長度 / 線程數 : 文件長度 / 線程數 + 1
例如:文件的長度爲6M,線程數爲3,那麼每條線程下載的數據長度爲2M。
b: 每條線程從文件的什麼位置開始下載到什麼位置結束:
開始位置:線程id(從0開始) * 每條線程下載的數據長度
結束位置:(線程id + 1) * 每條線程下載的數據長度 - 1
③、使用HTTP的請求頭字段Range指定每條線程從文件的什麼位置開始下載,到什麼位置下載結束。
例如指定從文件的2M位置開始下載,下載到位置(4M-1byte)結束:
[java] view plaincopy
- HttpURLConnection.setRequestProperty("Range", "bytes=2097152-4194303");
④、保存文件。使用RandomAccessFile類指定每條線程從本地文件的什麼位置開始寫入數據。
[java] view plaincopy
- RandomAccessFile threadfile = new RandomAccessFile("QQ2011.exe", "rwd");
- //指定從文件的什麼位置開始寫入數據
- threadfile.seek(2097152);
⑤示例
[java] view plaincopy
- /**
- * 多線程下載
- * 從路徑中獲取文件名稱
- * @param path 下載路徑
- * @return
- */
- public static String getFilename(String path){
- return path.substring(path.lastIndexOf('/') + 1);
- }
- /**
- * 多線程下載
- * 下載文件
- * @param path 下載路徑
- * @param threadNum 線程數
- */
- public void download(String path, int threadNum) throws Exception{
- URL url = new URL(path);
- HttpURLConnection conn = (HttpURLConnection)url.openConnection();
- conn.setRequestMethod("GET");
- conn.setConnectTimeout(5 * 1000);
- //獲取要下載文件的長度
- int filelength = conn.getContentLength();
- //從路徑中獲取文件名稱
- String filename = getFilename(path);
- File saveFile = new File(filename);
- RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");
- //設置本地文件的長度和下載文件長度相同
- accessFile.setLength(filelength);
- accessFile.close();
- //計算每條線程下載的數據長度
- int block = filelength % threadNum == 0 ? filelength / threadNum : filelength / threadNum + 1;
- for(int threadid = 0 ; threadid < threadNum ; threadid++){
- new DownloadThread(url, saveFile, block, threadid).start();
- }
- }
- /**
- * 多線程下載
- * 下載線程
- */
- private final class DownloadThread extends Thread{
- private URL url;//下載文件的url
- private File saveFile;//本地文件
- private int block;//每條線程下載的數據長度
- private int threadid;//線程id
- public DownloadThread(URL url, File saveFile, int block, int threadid) {
- this.url = url;
- this.saveFile = saveFile;
- this.block = block;
- this.threadid = threadid;
- }
- @Override
- public void run() {
- //計算開始位置公式:線程id * 每條線程下載的數據長度
- //計算結束位置公式:(線程id + 1)* 每條線程下載的數據長度 - 1
- int startposition = threadid * block;
- int endposition = (threadid + 1 ) * block - 1;
- try {
- RandomAccessFile accessFile = new RandomAccessFile(saveFile, "rwd");
- //設置從什麼位置開始寫入數據
- accessFile.seek(startposition);
- HttpURLConnection conn = (HttpURLConnection)url.openConnection();
- conn.setRequestMethod("GET");
- conn.setConnectTimeout(5 * 1000);
- conn.setRequestProperty("Range", "bytes=" + startposition + "-" + endposition);
- InputStream inStream = conn.getInputStream();
- byte[] buffer = new byte[1024];
- int len = 0;
- while( (len = inStream.read(buffer)) != -1 ){
- accessFile.write(buffer, 0, len);
- }
- inStream.close();
- accessFile.close();
- System.out.println("線程id:" + threadid + " 下載完成");
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
- }
2、如何實現多線程斷點下載呢?
需要把每條線程下載數據的最後位置保存起來。
[java] view plaincopy
- main.xml文件:
- <?xml version="1.0" encoding="utf-8"?>
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/downloadpath"
- />
- <EditText
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="http://dl_dir.qq.com/qqfile/qq/QQ2011/QQ2011Beta2.exe"
- android:id="@+id/downloadpath"
- />
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/download"
- android:id="@+id/download"
- />
- <!-- 進度條 -->
- <ProgressBar
- android:layout_width="fill_parent"
- android:layout_height="20px"
- style="?android:attr/progressBarStyleHorizontal"
- android:id="@+id/downloadbar"
- />
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center"//設置內容居中
- android:id="@+id/result"
- />
- </LinearLayout>
strings.xml文件:
[java] view plaincopy
- <?xml version="1.0" encoding="utf-8"?>
- <resources>
- <string name="hello">Hello World, DownloadActivity!</string>
- <string name="app_name">多線程文件下載器</string>
- <string name="downloadpath">下載路徑</string>
- <string name="download">下載</string>
- <string name="sdcarderror">SDCard不存在或者寫保護</string>
- <string name="success">下載完成</string>
- <string name="failure">下載失敗</string>
- </resources>
DownloadActivity:
[java] view plaincopy
- public class DownloadActivity extends Activity {
- private EditText downloadpathText;
- private TextView resultView;
- private ProgressBar progressBar;
- // 當Handler被創建時會關聯到創建它的當前線程(UI線程)的消息隊列中,Handler類用於往消息隊列發送消息,
- // 消息隊列中的消息由當前線程內部進行處理。
- private Handler handler = new Handler(){
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case 1:
- //runOnUiThread();
- progressBar.setProgress(msg.getData().getInt("size"));
- float num = (float)progressBar.getProgress() / (float)progressBar.getMax();
- int result = (int)(num * 100);
- resultView.setText(result + "%");
- if(progressBar.getProgress() == progressBar.getMax()){
- Toast.makeText(DownloadActivity.this, R.string.success, 1).show();
- }
- break;
- case -1:
- Toast.makeText(DownloadActivity.this, R.string.failure, 1).show();
- break;
- }
- }
- };
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- downloadpathText = (EditText) this.findViewById(R.id.downloadpath);
- progressBar = (ProgressBar) this.findViewById(R.id.downloadbar);
- resultView = (TextView) this.findViewById(R.id.result);
- Button downloadButton = (Button) this.findViewById(R.id.download);
- downloadButton.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- String downloadpath = downloadpathText.getText().toString();
- if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
- download(downloadpath, Environment.getExternalStorageDirectory());
- }else{
- Toast.makeText(DownloadActivity.this, R.string.sdcarderror, 1).show();
- }
- }
- });
- }
- // 該方法可能會執行幾秒鐘(下載2、3M的文件),也有可能執行幾十分鐘或者更長時間(下載幾百M的文件),因此會阻塞當前實例所在的主線程(UI線程)。
- /* 當應用程序啓動時,系統會爲應用程序創建一個主線程(main)或者叫UI線程,它負責分發事件到不同的組件,包括繪畫事件,完成你的應用程序與Android UI組件交互。例如,當您觸摸屏幕上的一個按鈕時,UI線程會把觸摸事件分發到組件上,更改狀態並加入事件隊列,UI線程會分發請求和通知到各個組件,完成相應的動作。單線程模型的性能是非常差的,除非你的應用程序相當的簡單,特別是當所有的操作都在主線程中執行,比如訪問網絡或數據庫之類的耗時操作將會導致用戶界面鎖定,所有的事件將不能分發,應用程序就像死了一樣,更嚴重的是當超過5秒時,系統就會彈出(ANR)“應用程序無響應”的對話框。如果你想看看什麼效果,可以寫一個簡單的應用程序,在一個Button的OnClickListener中寫上Thread.sleep(2000),運行程序你就會看到在應用程序回到正常狀態前按鈕會保持按下狀態2秒,當這種情況發生時,您就會感覺到應用程序反映相當的慢。總之,我們需要保證主線程(UI線程)不被鎖住,如果有耗時的操作,我們需要把它放到一個【單獨的後臺線程】中執行。對於顯示控件的界面更新只是由UI線程負責,如果是在非UI線程更新控件的屬性值,更新後的顯示界面不會反映到屏幕上。怎麼辦? */
- private void download(final String downloadpath, final File savedir) {
- new Thread(new Runnable() {
- public void run() {
- // 在Android中不建議開啓太多下載線程,因此在此處開啓3個下載線程
- FileDownloader loader = new FileDownloader(DownloadActivity.this, downloadpath, savedir, 3);
- // 設置進度條的最大刻度爲文件的長度
- progressBar.setMax(loader.getFileSize());
- try {
- loader.download(new DownloadProgressListener() {
- // 實時獲知文件已經下載的數據長度
- public void onDownloadSize(int size) {
- // 讓進度條的當前刻度等於已下載文件的數據長度
- //progressBar.setProgress(loader.getFileSize());
- //float num = (float)progressBar.getProgress() / (float)progressBar.getMax();
- //int result = (int)(num * 100);
- //resultView.setText(result + "%");
- Message msg = new Message();
- // 定義消息ID
- msg.what = 1;
- msg.getData().putInt("size", size);
- // 發送消息(發送到UI線程綁定的消息隊列)
- handler.sendMessage(msg);
- }
- });
- } catch (Exception e) {
- //Message msg = new Message();
- //msg.what = -1;
- //handler.sendMessage(msg);
- handler.obtainMessage(-1).sendToTarget();
- }
- }
- }).start();
- }
- }
六、通過TCP/IP(Socket)協議實現斷點續傳上傳文件(實現多用戶併發訪問)
斷點續傳
大容量文件
多用戶、併發訪問
1、服務器在指定端口監聽。
2、客戶端連接至服務器的指定端口。
3、客戶端發送協議給服務器(第一次):
Content-length=69042560;filename=***.exe;sourceid=
4、服務器判斷sourceid是否存在,然後判斷是否存在該文件的上傳記錄,如果不存在sourceid,則生成sourceid,發送給客戶端。
服務器發送協議給客戶端(第一次)
sourceid=244242411345677;position=0
5、在客戶端將sourceid與filename進行關聯綁定,然後從position指定的位置開始上傳。
6、如果不是第一次上傳,獲取上傳文件的絕對路徑,在客戶端上傳記錄中尋找對應的sourceid,然後發送協議給服務器。
Content-length=897869;filename=***.exe;sourceid=244242411345677
7、服務器根據sourceid,在上傳的斷點記錄中查找是否存在該記錄,如果存在,獲取最後上傳的位置,發送協議給客戶端。
sourceid=244242411345677;position=223
8、客戶端從position位置繼續上傳文件。
ExecutorService:線程池
PushbackInputStream擁有一個PushBack緩衝區,通過PushbackInputStream讀出數據後,只要PushBack緩衝區沒有滿,就可以使用unread()將數據推回流的前端。
簡單地說,該流可以把剛讀過的字節退回到輸入流中,以便重新再讀一遍。
main.xml:
[java] view plaincopy
- <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent"
- >
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="@string/filename"
- />
- <EditText
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:text="SPlayer.exe"
- android:id="@+id/filename"
- />
- <Button
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="@string/upload"
- android:id="@+id/upload"
- />
- <ProgressBar
- android:layout_width="fill_parent"
- android:layout_height="20px"
- style="?android:attr/progressBarStyleHorizontal"
- android:id="@+id/uploadbar"
- />
- <TextView
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:gravity="center"
- android:id="@+id/result"
- />
- </LinearLayout>
strings.xml:
[java] view plaincopy
- <resources>
- <string name="hello">Hello World, UploadActivity!</string>
- <string name="app_name">大視頻文件斷點上傳</string>
- <string name="filename">文件名稱</string>
- <string name="upload">上傳</string>
- <string name="sdcarderror">SDCard不存在或者寫保護</string>
- <string name="success">上傳完成</string>
- <string name="failure">上傳失敗</string>
- <string name="fileNotExsit">文件不存在</string>
- </resources>