Android应用开发:网络编程-2
網絡編程
- Java基礎:網絡編程
- Uri、URL、UriMatcher、ContentUris詳解
- Android應用開發:網絡編程1
- Android應用開發:網絡編程2
1. 使用HttpClient發送get請求
HttpClient是Apache開發的第三方框架,Google把它封裝到了Android API中,用于發送HTTP請求。
在Android.jar包中,可以看到有很多java的API,這些都是被Android改寫的API,也可以看到Android封裝了大量的Apache API。
示例:res\layout\activity_main.xml
<LinearLayout 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"tools:context=".MainActivity"android:orientation="vertical"><EditText android:id="@+id/et_name"android:layout_width="match_parent"android:layout_height="wrap_content"/><EditText android:id="@+id/et_pass"android:layout_width="match_parent"android:layout_height="wrap_content"/><Button android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="get登陸"android:onClick="click1"/><Button android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="post登陸"android:onClick="click2"/> </LinearLayout>src/cn.itcast.getmethod/MainActivity.java
package cn.itcast.getmethod;import java.io.InputStream; import java.net.URLEncoder;import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient;import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.EditText; import android.widget.Toast; import cn.itcast.getmethod.tool.Tools;public class MainActivity extends Activity {Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg) {Toast.makeText(MainActivity.this, (String)msg.obj, 0).show();}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void click1(View v){EditText et_name = (EditText)findViewById(R.id.et_name);EditText et_pass = (EditText)findViewById(R.id.et_pass);String name = et_name.getText().toString();String pass = et_pass.getText().toString();final String path = "http://192.168.1.100:8080/Web/servlet/Login?name="+ URLEncoder.encode(name) + "&pass=" + pass;Thread t = new Thread(){@Overridepublic void run() {//1. 創建客戶端對象//HttpClient是一個接口,不要new一個HttpClient對象,否則要實現很多的方法HttpClient client = new DefaultHttpClient();//2. 創建Http GET請求對象HttpGet get = new HttpGet(path);try {//3. 使用客戶端發送get請求HttpResponse response = client.execute(get);//獲取狀態行StatusLine line = response.getStatusLine();//從狀態行中拿到狀態碼if(line.getStatusCode() == 200){//獲取實體,實體里存放的是服務器返回的數據的相關信息HttpEntity entity = response.getEntity();//獲取服務器返回的輸入流InputStream is = entity.getContent();String text = Tools.getTextFromStream(is);//發送消息,讓主線程刷新UIMessage msg = handler.obtainMessage();msg.obj = text;handler.sendMessage(msg);}} catch (Exception e) {e.printStackTrace();}}};t.start();} }添加權限:
點擊get登陸按鈕,運行結果:
2. 使用HttpClient發送post請求
src/cn.itcast.postmethod/MainActivity.java
package cn.itcast.postmethod;import java.io.InputStream; import java.util.ArrayList; import java.util.List;import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair;import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.EditText; import android.widget.Toast; import cn.itcast.getmethod.R; import cn.itcast.getmethod.tool.Tools;public class MainActivity extends Activity {Handler handler = new Handler(){@Overridepublic void handleMessage(Message msg) {Toast.makeText(MainActivity.this, (String)msg.obj, 0).show();}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void click2(View v){EditText et_name = (EditText)findViewById(R.id.et_name);EditText et_pass = (EditText)findViewById(R.id.et_pass);final String name = et_name.getText().toString();final String pass = et_pass.getText().toString();final String path = "http://:8080/Web/servlet/Login";Thread t = new Thread(){@Overridepublic void run() {//1. 創建客戶端對象HttpClient client = new DefaultHttpClient();//2. 創建Http POST請求對象HttpPost post = new HttpPost(path);try{//通過此集合封裝要提交的數據List<NameValuePair> parameters = new ArrayList<NameValuePair>();//集合的泛型是BasicNameValuePair類型,那么由此可以推算出,要提交的數據是封裝在BasicNameValuePair對象中BasicNameValuePair bvp1 = new BasicNameValuePair("name", name);BasicNameValuePair bvp2 = new BasicNameValuePair("pass", pass);parameters.add(bvp1);parameters.add(bvp2);UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters,"utf-8");//把實體類封裝至post請求中,提交post請求時,實體中的數據就會用輸出流寫給服務器post.setEntity(entity);//客戶端發送post請求HttpResponse response = client.execute(post);//獲取狀態行StatusLine line = response.getStatusLine();//從狀態行中拿到狀態碼if(line.getStatusCode() == 200){//獲取實體,實體里存放的是服務器返回的數據的相關信息HttpEntity et = response.getEntity();//獲取服務器返回的輸入流InputStream is = et.getContent();String text = Tools.getTextFromStream(is);//發送消息,讓主線程刷新UIMessage msg = handler.obtainMessage();msg.obj = text;handler.sendMessage(msg);}} catch (Exception e) {e.printStackTrace();}}};t.start();} }點擊post登陸按鈕,運行結果:
3. 異步HttpClient框架
從github上下載android-async-http-master開源jar包,拷貝library/src/main/java目錄下的內容到我們自己的項目中。
拷貝后,發現有錯誤,這是由于Base64.java中的BuildConfig類導包問題,Ctrl+Shift+O自動導包即可修復。
使用異步HttpClient框架實現上面示例中的功能,activity.xml與上面的示例相同,修改MainActivity.java代碼。
src/cn.itcast.asynchttpclient/MainActivity.java
添加權限:
運行結果:分別點擊“get登陸”和“post登陸”按鈕。
4. 多線程下載的原理和過程
斷點續傳:上次下載到哪,這次就從哪開始下。
多線程:下載速度更快。
原理:搶占服務器資源。例如:帶寬為20M/s,3個人去下載同一部電影,那么每人分別占6.66M/s帶寬。如果有一人A開了3個線程同時下載,那么5個線程,各占4M/s帶寬,那么A所占帶寬就是4*3=12M/s,其他兩人各占4M/s帶寬。也就是說A搶占了更多的服務器資源。
多線程下載示例說明:
例如有一個10KB的文件,分成0~10,3個線程去下載,第0個線程下載0~2,也就是3KB數據,第1個線程下載3~5,也就是3KB數據,余下的6~9,4KB的數據由最后一個線程下載。
總結出公式就是:
每個線程下載的數據開始點:threadId*size,結束點:(threadId + 1) * size -1。
最后一個線程除外,下載結束點:length - 1。
計算每條線程的下載區間
多線程斷點續傳的API全部都是Java API,Java項目測試比較容易,所以,我們先創建一個Java項目。
將待下載的資源放入Tomcat服務器中。
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;import java.net.HttpURLConnection; import java.net.URL;public class Main {static int threadCount = 3;static String path = "http://localhost:8080/QQPlayer.exe";public static void main(String[] args) {URL url;try {url = new URL(path);//打開連接對象,做初始化設置HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);if(conn.getResponseCode() == 200){//獲取要下載的目標文件的總長度int length = conn.getContentLength();//計算每條線程要下載的長度int size = length / threadCount;System.out.println("size:" + size);//計算每條線程下載的開始位置和結束位置for(int threadId = 0; threadId < threadCount; threadId++){int startIndex = threadId * size;int endIndex = (threadId + 1) * size - 1;//如果是最后一條線程,那么需要把余數也一塊下載if(threadId == threadCount - 1){endIndex = length - 1;}System.out.println("線程" + threadId + ",下載區間為:" + startIndex + "-" + endIndex);}}} catch (Exception e) {e.printStackTrace();}} }運行結果:
創建臨時文件
src/cn.itcast.MultiDownLoad/Main.java
開啟多個線程下載文件
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL;public class Main {static int threadCount = 3;static String path = "http://localhost:8080/QQPlayer.exe";public static void main(String[] args) {URL url;try {url = new URL(path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);if(conn.getResponseCode() == 200){int length = conn.getContentLength();int size = length / threadCount;System.out.println("size:" + size);for(int threadId = 0; threadId < threadCount; threadId++){int startIndex = threadId * size;int endIndex = (threadId + 1) * size - 1;if(threadId == threadCount - 1){endIndex = length - 1;}System.out.println("線程" + threadId + ",下載區間為:" + startIndex + "-" + endIndex);DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);dt.start();}}} catch (Exception e) {e.printStackTrace();}}public static String getFileNameFromPath(String path){int index = path.lastIndexOf("/");return path.substring(index + 1);} }class DownLoadThread extends Thread{int threadId;int startIndex;int endIndex;public DownLoadThread(int threadId, int startIndex, int endIndex) {super();this.threadId = threadId;this.startIndex = startIndex;this.endIndex = endIndex;}public void run(){URL url;try{url = new URL(Main.path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);//Range表示指定請求的數據區間conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);//請求部分數據,返回的是206if(conn.getResponseCode() == 206){InputStream is = conn.getInputStream();//打開臨時文件的IO流File file = new File(Main.getFileNameFromPath(Main.path));RandomAccessFile raf = new RandomAccessFile(file, "rwd");//修改寫入臨時文件的開始位置raf.seek(startIndex);byte[] b = new byte[1024];int len = 0;//當前線程下載的總進度int total = 0;while((len = is.read(b)) != -1){//把讀取到的字節寫入臨時文件中raf.write(b, 0, len);total += len;System.out.println("線程" + threadId + "下載的進度為:" + total);}raf.close();}System.out.println("線程" + threadId + "下載完畢---------------------");}catch(Exception e){e.printStackTrace();}} }運行結果:刷新,即可看到文件已經下載好了
創建進度臨時文件保存下載進度
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;import java.io.File; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL;public class Main {static int threadCount = 3;static String path = "http://localhost:8080/QQPlayer.exe";public static void main(String[] args) {URL url;try {url = new URL(path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);if(conn.getResponseCode() == 200){int length = conn.getContentLength();int size = length / threadCount;System.out.println("size:" + size);for(int threadId = 0; threadId < threadCount; threadId++){int startIndex = threadId * size;int endIndex = (threadId + 1) * size - 1;if(threadId == threadCount - 1){endIndex = length - 1;}System.out.println("線程" + threadId + ",下載區間為:" + startIndex + "-" + endIndex);DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);dt.start();}}} catch (Exception e) {e.printStackTrace();}}public static String getFileNameFromPath(String path){int index = path.lastIndexOf("/");return path.substring(index + 1);} }class DownLoadThread extends Thread{int threadId;int startIndex;int endIndex;public DownLoadThread(int threadId, int startIndex, int endIndex) {super();this.threadId = threadId;this.startIndex = startIndex;this.endIndex = endIndex;}public void run(){URL url;try{url = new URL(Main.path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);if(conn.getResponseCode() == 206){InputStream is = conn.getInputStream();File file = new File(Main.getFileNameFromPath(Main.path));RandomAccessFile raf = new RandomAccessFile(file, "rwd");raf.seek(startIndex);byte[] b = new byte[1024];int len = 0;int total = 0;while((len = is.read(b)) != -1){raf.write(b, 0, len);total += len;System.out.println("線程" + threadId + "下載的進度為:" + total);//創建一個進度臨時文件,保存下載進度File fileProgress = new File(threadId + ".txt");RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");rafProgress.write((total + "").getBytes());rafProgress.close();}raf.close();}System.out.println("線程" + threadId + "下載完畢---------------------");}catch(Exception e){e.printStackTrace();}} }運行結果:執行程序,然后,在沒下載完成時,就點擊右上角的停止按鈕。
刷新,可以看到記錄文件已經產生。
完成斷點續傳下載
src/cn.itcast.MultiDownLoad/Main.java
package cn.itcast.MultiDownLoad;import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL;public class Main {static int threadCount = 3;static String path = "http://localhost:8080/QQPlayer.exe";public static void main(String[] args) {URL url;try {url = new URL(path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);if(conn.getResponseCode() == 200){int length = conn.getContentLength();int size = length / threadCount;System.out.println("size:" + size);for(int threadId = 0; threadId < threadCount; threadId++){int startIndex = threadId * size;int endIndex = (threadId + 1) * size - 1;if(threadId == threadCount - 1){endIndex = length - 1;}DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);dt.start();}}} catch (Exception e) {e.printStackTrace();}}public static String getFileNameFromPath(String path){int index = path.lastIndexOf("/");return path.substring(index + 1);} }class DownLoadThread extends Thread{int threadId;int startIndex;int endIndex;public DownLoadThread(int threadId, int startIndex, int endIndex) {super();this.threadId = threadId;this.startIndex = startIndex;this.endIndex = endIndex;}public void run(){URL url;try{int lastProgress = 0;//下載之前,先判斷進度臨時文件是否存在File fileProgress1 = new File(threadId + ".txt");if(fileProgress1.exists()){FileInputStream fis = new FileInputStream(fileProgress1);BufferedReader br = new BufferedReader(new InputStreamReader(fis));//讀取進度臨時文件中的值lastProgress = Integer.parseInt(br.readLine());//把上一次下載的進度加到下載開始位置startIndex += lastProgress;fis.close();}System.out.println("線程" + threadId + ",下載區間為:" + startIndex + "-" + endIndex);url = new URL(Main.path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);if(conn.getResponseCode() == 206){InputStream is = conn.getInputStream();File file = new File(Main.getFileNameFromPath(Main.path));RandomAccessFile raf = new RandomAccessFile(file, "rwd");raf.seek(startIndex);byte[] b = new byte[1024];int len = 0;//從之前下載的地方開始下載int total = lastProgress;while((len = is.read(b)) != -1){raf.write(b, 0, len);total += len;System.out.println("線程" + threadId + "下載的進度為:" + total);File fileProgress = new File(threadId + ".txt");RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");rafProgress.write((total + "").getBytes());rafProgress.close();}raf.close();System.out.println("線程" + threadId + "下載完畢---------------------");}}catch(Exception e){e.printStackTrace();}} }運行結果:
執行Main.java程序,然后,在還沒有下載完,停止。然后,再次執行Main.java,可以看到如下顯示,也就是實現了斷點續傳。
下載后刪除進度臨時文件
src/cn.itcast.MultiDownLoad/Main.java
運行結果:運行Main.java,斷點續傳完成后,刷新。可以看到,臨時文件已經被刪除。
5. Android版多線程斷點續傳下載
res/layout/activity.xml
<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"tools:context=".MainActivity" ><Button android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="下載"android:onClick="click" /></RelativeLayout>src/cn.itcast.androidmultidownload/MainActivity.xml
package cn.itcast.androidmultidownload;import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.view.View;public class MainActivity extends Activity {int finishedThreadCount = 0;int threadCount = 3;String path = "http://192.168.1.100:8080/QQPlayer.exe";@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void click(View v){Thread t = new Thread(){@Overridepublic void run() {URL url;try {url = new URL(path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);if(conn.getResponseCode() == 200){int length = conn.getContentLength();int size = length / threadCount;System.out.println("size:" + size);for(int threadId = 0; threadId < threadCount; threadId++){int startIndex = threadId * size;int endIndex = (threadId + 1) * size - 1;if(threadId == threadCount - 1){endIndex = length - 1;}DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);dt.start();}}} catch (Exception e) {e.printStackTrace();}}};t.start();}public String getFileNameFromPath(String path){int index = path.lastIndexOf("/");return path.substring(index + 1);}class DownLoadThread extends Thread{int threadId;int startIndex;int endIndex;public DownLoadThread(int threadId, int startIndex, int endIndex) {super();this.threadId = threadId;this.startIndex = startIndex;this.endIndex = endIndex;}public void run(){URL url;try{int lastProgress = 0;//修改文件路徑,存在外部存儲器中File fileProgress1 = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");if(fileProgress1.exists()){FileInputStream fis = new FileInputStream(fileProgress1);BufferedReader br = new BufferedReader(new InputStreamReader(fis));lastProgress = Integer.parseInt(br.readLine());startIndex += lastProgress;fis.close();}System.out.println("線程" + threadId + ",下載區間為:" + startIndex + "-" + endIndex);url = new URL(path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);if(conn.getResponseCode() == 206){InputStream is = conn.getInputStream();File file = new File(Environment.getExternalStorageDirectory(), getFileNameFromPath(path));RandomAccessFile raf = new RandomAccessFile(file, "rwd");raf.seek(startIndex);byte[] b = new byte[1024];int len = 0;int total = lastProgress;while((len = is.read(b)) != -1){raf.write(b, 0, len);total += len;System.out.println("線程" + threadId + "下載的進度為:" + total);File fileProgress = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");rafProgress.write((total + "").getBytes());rafProgress.close();}raf.close();System.out.println("線程" + threadId + "下載完畢---------------------");finishedThreadCount++;synchronized(path){if(finishedThreadCount == 3){for(int i = 0; i < finishedThreadCount; i++){File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");f.delete();}finishedThreadCount = 0;}}}}catch(Exception e){e.printStackTrace();}}} }添加權限:
運行結果:點擊“下載”按鈕,在下載完成之前,殺死線程。
可以看到臨時文件生成。
再次運行應用程序,點擊“下載”按鈕,接著下載,斷點續傳成功實現。
下載完成后,可以看到臨時文件刪除成功。
添加進度條反應下載進度,res/layout/activity.xml
<LinearLayout 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"tools:context=".MainActivity"android:orientation="vertical"><Button android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="下載"android:onClick="click" /><ProgressBar android:id="@+id/pb"style="@android:style/Widget.ProgressBar.Horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"/></LinearLayout>src/cn.itcast.androidmultidownload/MainActivity.xml
package cn.itcast.androidmultidownload;import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL;import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.view.View; import android.widget.ProgressBar;public class MainActivity extends Activity {int finishedThreadCount = 0;int threadCount = 3;String path = "http://192.168.1.100:8080/QQPlayer.exe";private ProgressBar pb;//記錄進度條的當前進度int currentProgress = 0;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//進度條pb = (ProgressBar) findViewById(R.id.pb);}public void click(View v){Thread t = new Thread(){@Overridepublic void run() {URL url;try {url = new URL(path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);if(conn.getResponseCode() == 200){int length = conn.getContentLength();//設定進度條的最大值pb.setMax(length);int size = length / threadCount;System.out.println("size:" + size);for(int threadId = 0; threadId < threadCount; threadId++){int startIndex = threadId * size;int endIndex = (threadId + 1) * size - 1;if(threadId == threadCount - 1){endIndex = length - 1;}DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);dt.start();}}} catch (Exception e) {e.printStackTrace();}}};t.start();}public String getFileNameFromPath(String path){int index = path.lastIndexOf("/");return path.substring(index + 1);}class DownLoadThread extends Thread{int threadId;int startIndex;int endIndex;public DownLoadThread(int threadId, int startIndex, int endIndex) {super();this.threadId = threadId;this.startIndex = startIndex;this.endIndex = endIndex;}public void run(){URL url;try{int lastProgress = 0;File fileProgress1 = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");if(fileProgress1.exists()){FileInputStream fis = new FileInputStream(fileProgress1);BufferedReader br = new BufferedReader(new InputStreamReader(fis));lastProgress = Integer.parseInt(br.readLine());startIndex += lastProgress;//如果開始位置大于或等于endIndex,說明上一次下載中,此線程就已經下載完了if(startIndex >= endIndex){finishedThreadCount++;}//如果上一次下載過,把上次的進度加到當前進度中currentProgress += lastProgress;pb.setProgress(currentProgress);fis.close();}System.out.println("線程" + threadId + ",下載區間為:" + startIndex + "-" + endIndex);url = new URL(path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);if(conn.getResponseCode() == 206){InputStream is = conn.getInputStream();File file = new File(Environment.getExternalStorageDirectory(), getFileNameFromPath(path));RandomAccessFile raf = new RandomAccessFile(file, "rwd");raf.seek(startIndex);byte[] b = new byte[1024];int len = 0;int total = lastProgress;while((len = is.read(b)) != -1){raf.write(b, 0, len);total += len;System.out.println("線程" + threadId + "下載的進度為:" + total);File fileProgress = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");rafProgress.write((total + "").getBytes());rafProgress.close();//每一條線程下載的數據,都應該加到全局進度里currentProgress += len;//設置進度條當前進度//進度條內部也是通過handler讓主線程刷新UI的pb.setProgress(currentProgress);}raf.close();System.out.println("線程" + threadId + "下載完畢---------------------");finishedThreadCount++;synchronized(path){if(finishedThreadCount == 3){for(int i = 0; i < finishedThreadCount; i++){File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");f.delete();}finishedThreadCount = 0;}}}}catch(Exception e){e.printStackTrace();}}} }運行結果:點擊“下載”按鈕,可以通過進度條看到下載進度。然后,殺死進程,再重新運行應用程序,點擊“下載”按鈕,可以看到進度條在原有的基礎上繼續向前移動,也就是實現了斷點續傳的進度條實現。
添加文本進度,res/layout/activity.xml
<LinearLayout 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"tools:context=".MainActivity"android:orientation="vertical"><Button android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="下載"android:onClick="click" /><ProgressBar android:id="@+id/pb"style="@android:style/Widget.ProgressBar.Horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"/><TextView android:id="@+id/tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="0%"android:layout_gravity="right"/></LinearLayout>src/cn.itcast.androidmultidownload/MainActivity.xml
package cn.itcast.androidmultidownload;import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL;import android.app.Activity; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView;public class MainActivity extends Activity {int finishedThreadCount = 0;int threadCount = 3;String path = "http://192.168.1.100:8080/QQPlayer.exe";private ProgressBar pb;int currentProgress = 0;private TextView tv;//刷新TextViewHandler handler = new Handler(){public void handleMessage(android.os.Message msg) {//當前進度除以最大進度,得到下載進度的百分比tv.setText(pb.getProgress() * 100 /pb.getMax() + "%");}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);pb = (ProgressBar) findViewById(R.id.pb);tv = (TextView)findViewById(R.id.tv);}public void click(View v){Thread t = new Thread(){@Overridepublic void run() {URL url;try {url = new URL(path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);if(conn.getResponseCode() == 200){int length = conn.getContentLength();pb.setMax(length);int size = length / threadCount;System.out.println("size:" + size);for(int threadId = 0; threadId < threadCount; threadId++){int startIndex = threadId * size;int endIndex = (threadId + 1) * size - 1;if(threadId == threadCount - 1){endIndex = length - 1;}DownLoadThread dt = new DownLoadThread(threadId, startIndex, endIndex);dt.start();}}} catch (Exception e) {e.printStackTrace();}}};t.start();}public String getFileNameFromPath(String path){int index = path.lastIndexOf("/");return path.substring(index + 1);}class DownLoadThread extends Thread{int threadId;int startIndex;int endIndex;public DownLoadThread(int threadId, int startIndex, int endIndex) {super();this.threadId = threadId;this.startIndex = startIndex;this.endIndex = endIndex;}public void run(){URL url;try{int lastProgress = 0;File fileProgress1 = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");if(fileProgress1.exists()){FileInputStream fis = new FileInputStream(fileProgress1);BufferedReader br = new BufferedReader(new InputStreamReader(fis));lastProgress = Integer.parseInt(br.readLine());startIndex += lastProgress;if(startIndex >= endIndex){finishedThreadCount++;}currentProgress += lastProgress;pb.setProgress(currentProgress);//發送消息,讓主線程刷新文本進度handler.sendEmptyMessage(1);fis.close();}System.out.println("線程" + threadId + ",下載區間為:" + startIndex + "-" + endIndex);url = new URL(path);HttpURLConnection conn = (HttpURLConnection)url.openConnection();conn.setConnectTimeout(8000);conn.setReadTimeout(8000);conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);if(conn.getResponseCode() == 206){InputStream is = conn.getInputStream();File file = new File(Environment.getExternalStorageDirectory(), getFileNameFromPath(path));RandomAccessFile raf = new RandomAccessFile(file, "rwd");raf.seek(startIndex);byte[] b = new byte[1024];int len = 0;int total = lastProgress;while((len = is.read(b)) != -1){raf.write(b, 0, len);total += len;System.out.println("線程" + threadId + "下載的進度為:" + total);File fileProgress = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");RandomAccessFile rafProgress = new RandomAccessFile(fileProgress, "rwd");rafProgress.write((total + "").getBytes());rafProgress.close();currentProgress += len;pb.setProgress(currentProgress);//發送消息,讓主線程刷新文本進度handler.sendEmptyMessage(1);}raf.close();System.out.println("線程" + threadId + "下載完畢---------------------");finishedThreadCount++;synchronized(path){if(finishedThreadCount == 3){for(int i = 0; i < finishedThreadCount; i++){File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");f.delete();}finishedThreadCount = 0;}}}}catch(Exception e){e.printStackTrace();}}} }運行結果:
文本進度計算的bug:當文件較大時,就會出現bug,文本進度計算數據變成了負數。
這是因為文件大小超出了int所能表示的最大范圍。
只需要修改代碼如下即可。
如果最終顯示為99%,那么只需要在下載完成之后,直接在程序中寫死為100%即可。
6. xUtils多線程斷點續傳下載
從github上下載xUtils,將xUtils的jar復制到libs目錄下。
如果無法關聯源碼,可以通過在libs目錄下新建一個properties文件解決。
properties文件的內容為”src=源碼目錄”,即可成功關聯源碼。
res/layout/activity.xml
<LinearLayout 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=".MainActivity"android:orientation="vertical"><Button android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="下載"android:onClick="click" /><TextView android:id="@+id/tv_success"android:layout_width="wrap_content"android:layout_height="wrap_content"/><TextView android:id="@+id/tv_failure"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="#ff0000"/><ProgressBar android:id="@+id/tv_pb"style="@android:style/Widget.ProgressBar.Horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"/><TextView android:id="@+id/tv_progress"android:layout_width="wrap_content"android:layout_height="wrap_content"/> </LinearLayout>src/cn.itcast.androidmultidownload/MainActivity.xml
package cn.itcast.xutils;import java.io.File;import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.ProgressBar; import android.widget.TextView;import com.lidroid.xutils.HttpUtils; import com.lidroid.xutils.exception.HttpException; import com.lidroid.xutils.http.HttpHandler; import com.lidroid.xutils.http.ResponseInfo; import com.lidroid.xutils.http.callback.RequestCallBack;public class MainActivity extends Activity {String path = "http://192.168.1.100:8080/QQPlayer.exe";private TextView tv;private ProgressBar pb;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);pb = (ProgressBar)findViewById(R.id.tv_pb);tv = (TextView)findViewById(R.id.tv_progress);}public void click(View v){HttpUtils utils = new HttpUtils();HttpHandler handler = utils.download(path, //請求的網址"sdcard/QQPlayer.exe", //文件保存的路徑及文件名true, // 是否支持斷點續傳true, // 如果相應頭中包含文件名,那么下載完畢后,自動以該名字重命名文件new RequestCallBack<File>() {//下載完成調用@Overridepublic void onSuccess(ResponseInfo<File> responseInfo) {TextView tv = (TextView)findViewById(R.id.tv_success);tv.setText(responseInfo.result.getPath());}//下載失敗后調用@Overridepublic void onFailure(HttpException error, String msg) {TextView tv = (TextView)findViewById(R.id.tv_success);tv.setText(msg);}//下載過程中不斷調用@Overridepublic void onLoading(long total, long current,boolean isUploading) {pb.setMax((int)total);pb.setProgress((int)current);tv.setText((current * 100)/ total + "%");}});} }添加權限:
運行結果:
1. 內容摘要
- 使用HttpURLConnection 提交數據
- 使用HttpClient 提交數據
- 使用AsyncHttpClient 框架提交數據
- Android 實現多線程下載
- 使用xUtils 框架實現多線程下載
2. 前言
移動互聯網時代哪個app 不需要跟服務器進行交互呢?Android 給服務器提交數據的方式都有哪些呢?這正是本文3、4、5節討論的話題,每一節介紹一種提交數據的方式,但是Android 提交數據的方式絕非僅僅這三種,這里給出的只是最基礎的3 中方式。將這些基礎的方式學會了,其他再高級的方式對我們來說也不過是小菜一碟了。
本文的3、4、5 三節中使用的需求和布局是一模一樣的,甚至4.2 和5.3 節的工程就是直接從3.1節中的工程拷貝過來的,唯一不同的就是使用提交數據的框架(類)不同。因此這里一次性將需求給出。
2.1 需求說明
如圖1-1 所示,界面整體采用垂直的線性布局,前兩行為兩個EditText,分別代表用戶名和密碼。第三、四兩行為兩個Button,前者點擊后采用get 方式提交數據,后者點擊后采用post 方式提交數據。數據提交成功后,服務器會有返回值,并將返回值用Toast 顯示出來。
2.2 服務器搭建
服務端采用Servlet 編寫,名為LoginServlet,并使用Tomcat 作為其服務器。LoginServlet.java 源碼見【文件1-1】,其中黃色高亮部分為核心代碼。該Servlet 在web.xml 中的配置見【文件1-2】。因為服務器不是本文的重點,因此這里只簡單介紹。
【文件1-1】LoginServlet.java
package com.itheima.servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class LoginServlet extends HttpServlet {/*** Constructor of the object.*/public LoginServlet() {super();}/*** Destruction of the servlet. <br>*/public void destroy() {super.destroy(); // Just puts "destroy" string in log// Put your code here}/*** The doGet method of the servlet. <br>** This method is called when a form has its tag value method equals to get.** @param request the request send by the client to the server* @param response the response send by the server to the client* @throws ServletException if an error occurred* @throws IOException if an error occurred*/public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {request.setCharacterEncoding("utf-8");String username = request.getParameter("username");String password = request.getParameter("password");if ("GET".equals(request.getMethod().toUpperCase())) {byte[] bytes = username.getBytes("iso-8859-1");username = new String(bytes, "utf-8");}System.out.println("usernmae===="+username);System.out.println("password==="+password);response.setCharacterEncoding("utf-8");response.getWriter().write("成功收到信息"+username+"/"+password);}/*** The doPost method of the servlet. <br>** This method is called when a form has its tag value method equals to post.** @param request the request send by the client to the server* @param response the response send by the server to the client* @throws ServletException if an error occurred* @throws IOException if an error occurred*/public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {doGet(request, response);}/*** Initialization of the servlet. <br>** @throws ServletException if an error occurs*/public void init() throws ServletException {// Put your code here} }【文件1-2】web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5"xmlns="http://java.sun.com/xml/ns/javaee"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://java.sun.com/xml/ns/javaeehttp://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"><servlet><servlet-name>LoginServlet</servlet-name><servlet-class>com.itheima.servlet.LoginServlet</servlet-class></servlet><servlet><servlet-name>FileuploadServlet</servlet-name><servlet-class>com.itheima.servlet.FileuploadServlet</servlet-class></servlet><servlet-mapping><servlet-name>LoginServlet</servlet-name><url-pattern>/servlet/LoginServlet</url-pattern></servlet-mapping><servlet-mapping><servlet-name>FileuploadServlet</servlet-name><url-pattern>/servlet/FileuploadServlet</url-pattern></servlet-mapping><welcome-file-list><welcome-file>index.jsp</welcome-file></welcome-file-list> </web-app>2.3 編寫布局
考慮到3、4、5節使用的工程布局是一模一樣的,因此在這里先將布局給出。
【文件1-3】activity_main.java
<LinearLayout 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:orientation="vertical"><EditText android:id="@+id/et_username"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="請輸入用戶名" /><EditText android:id="@+id/et_password"android:inputType="textPassword"android:layout_width="match_parent"android:layout_height="wrap_content"android:hint="請輸入密碼" /><Button android:onClick="login"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="right"android:text="get 方式登錄" /><Button android:onClick="login2"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_gravity="right"android:text="post 方式登錄" /></LinearLayout>2.4 添加權限
凡是網絡訪問的操作,必須添加如下權限。
<uses-permission android:name="android.permission.INTERNET"/>3. 使用HttpURLConnection 提交數據
3.1 代碼
MainActivity.java 代碼清單見【文件1-4】。
【文件1-4】MainActivity.java
3.2 總結
在http 協議中,get 請求協議沒有請求體,只有請求頭和請求行,因此如果想使用get 方式提交數據,只能通過在url 后面加上要提交的參數。這也意味著get 方式只能提交比較小的參數。
如果get 方式提交的參數有特殊字符或者中文字符那么必須對其進行URL 編碼,不然會導致服務器接收到的數據亂碼。
對于post 請求,提交的數據位于請求體中,因此需要設置connection.setDoOutput(true);參數,該參數設置后才允許將提交的數據通過輸出流的形式提交。不過需要說明的是雖然采用post 方式提交,依然需要將參數進行URL 編碼。
其實當我們使用瀏覽器進行form 表單提交的時候,瀏覽器內部已經幫我們實現了URL 編碼。因為我們這里是使用代碼模擬瀏覽器的提交數據過程,因此需要使用代碼特殊處理。
4. 使用HttpClient 提交數據
HttpClient 是Apache Jakarta Common 下的子項目,提供了高效的、最新的、功能豐富的支持HTTP 協議的客戶端編程工具包,并且它支持HTTP 協議最新的版本。
HttpClient 被內置到Android SDK 中,因此可以在不添加任何額外jar 包情況下,直接使用。(PS:Android 6.0 后HttpClient 已經被刪除)
4.1 get 方式提交
在1.2 節工程的基礎上,只需要修改部分代碼即可。因此這里只給出核心代碼。
【文件1-5】get 方式提交數據代碼片段
/**HttpCLient 使用get 方式完成用戶的登錄*/ public void login3(View view) {// 獲取用戶數據final String username = et_username.getText().toString().trim();final String password = et_password.getText().toString().trim();// 校驗數據if (TextUtils.isEmpty(password) || TextUtils.isEmpty(username)) {Toast.makeText(this, "用戶名或密碼不能為空!", Toast.LENGTH_SHORT).show();return;}// 開啟子線程new Thread(new Runnable() {@Overridepublic void run() {String path = "http://10.0.2.2:8080/userlogin/servlet/LoginServlet?username="+ URLEncoder.encode(username) + "&password=" + password;try {// 創建一個httpClient 對象HttpClient client = new DefaultHttpClient();// 創建一個請求方式HttpGet request = new HttpGet(path);// 執行操作HttpResponse response = client.execute(request);// 獲取放回狀態對象StatusLine statusLine = response.getStatusLine();// 獲取狀態碼int statusCode = statusLine.getStatusCode();if (200 == statusCode) {// 獲取服務器返回的對象HttpEntity entity = response.getEntity();// 獲取輸入流InputStream inputStream = entity.getContent();// 將輸入流轉化為字符串String data = StreamUtils.inputStream2String(inputStream);handler.obtainMessage(RESULT_OK, data).sendToTarget();} else {handler.obtainMessage(RESULT_CANCELED, statusCode).sendToTarget();}} catch (Exception e) {e.printStackTrace();handler.obtainMessage(RESULT_CANCELED, e).sendToTarget();}}}).start(); }4.2 post 方式提交
【文件1-6】post 方式提交數據代碼片段
/**HttpCLient 使用post 方式完成用戶的登錄*/ public void login4(View view) {// 獲取用戶數據final String username = et_username.getText().toString().trim();final String password = et_password.getText().toString().trim();// 校驗數據if (TextUtils.isEmpty(password) || TextUtils.isEmpty(username)) {Toast.makeText(this, "用戶名或密碼不能為空!", Toast.LENGTH_SHORT).show();return;}// 開啟子線程new Thread(new Runnable() {@Overridepublic void run() {String path = "http://10.0.2.2:8080/userlogin/servlet/LoginServlet";try {// 創建一個httpClient 對象HttpClient client = new DefaultHttpClient();// 創建一個post 請求方式HttpPost request = new HttpPost(path);//創建集合用于添加請求的數據List<BasicNameValuePair> parameters = new ArrayList<BasicNameValuePair>();//將請求數據添加到集合中parameters.add(new BasicNameValuePair("username", username));parameters.add(new BasicNameValuePair("password", password));/** 創建請求實體對象* UrlEncodedFormEntity 是HttpEntity 的子類,* 該類專門用于封裝字符串類型的參數* 指定數據的編碼格式*/HttpEntity requestEntity = new UrlEncodedFormEntity(parameters, "utf-8");//設置請求體request.setEntity(requestEntity);// 執行操作HttpResponse response = client.execute(request);// 獲取放回狀態對象StatusLine statusLine = response.getStatusLine();// 獲取狀態碼int statusCode = statusLine.getStatusCode();if (200 == statusCode) {// 獲取服務器返回的對象HttpEntity entity = response.getEntity();// 獲取輸入流InputStream inputStream = entity.getContent();// 將輸入流轉化為字符串String data = StreamUtils.inputStream2String(inputStream);handler.obtainMessage(RESULT_OK, data).sendToTarget();} else {handler.obtainMessage(RESULT_CANCELED, statusCode).sendToTarget();}} catch (Exception e) {e.printStackTrace();handler.obtainMessage(RESULT_CANCELED, e).sendToTarget();}}}).start(); }4.3 總結
4.3.1 get 請求方式步驟
1、創建一個httpClient 對象
HttpClient client = new DefaultHttpClient();2、創建一個請求方式
String path ="http://10.0.2.2:8080/userlogin/servlet/LoginServlet?username="+ URLEncoder.encode(username) + "&password=" + password; //因為是get 方式,因此需要在path 中添加請求參數 HttpGet request = new HttpGet(path);3、開始訪問網絡,并接收返回值
HttpResponse response = client.execute(request);4、獲取返回狀態碼
//獲取返回值的狀態行 StatusLine statusLine = response.getStatusLine(); // 獲取狀態碼 int statusCode = statusLine.getStatusCode();5、判斷狀態碼
//如果狀態碼為200 則代表請求成功 if (200 == statusCode) {//TODO}6、獲取返回數據
// 獲取服務器返回的對象 HttpEntity entity = response.getEntity(); // 獲取輸入流 InputStream inputStream = entity.getContent();4.3.2 post 請求方式步驟
1、創建一個httpClient 對象
HttpClient client = new DefaultHttpClient();2、創建一個post 請求方式
String path = "http://10.0.2.2:8080/userlogin/servlet/LoginServlet"; HttpPost request = new HttpPost(path);3、設置請求體
//創建集合用于添加請求的數據 List<BasicNameValuePair> parameters = new ArrayList<BasicNameValuePair>(); //將請求數據添加到集合中 parameters.add(new BasicNameValuePair("username", username)); parameters.add(new BasicNameValuePair("password", password));/** 創建請求實體對象* UrlEncodedFormEntity 是HttpEntity 的子類,該類專門用于封裝字符串類型的參數* 指定數據的編碼格式*/ HttpEntity requestEntity = new UrlEncodedFormEntity(parameters, "utf-8"); //設置請求體 request.setEntity(requestEntity);4、開始訪問網絡,并接收返回值
// 執行操作 HttpResponse response = client.execute(request);5、獲取返回狀態碼
//獲取返回值的狀態行 StatusLine statusLine = response.getStatusLine(); // 獲取狀態碼 int statusCode = statusLine.getStatusCode();6、判斷狀態碼
//如果狀態碼為200 則代表請求成功 if (200 == statusCode) {//TODO}7、獲取返回數據
// 獲取服務器返回的對象 HttpEntity entity = response.getEntity(); // 獲取輸入流 InputStream inputStream = entity.getContent();5. 使用AsyncHttpClient 框架提交數據
AsyncHttpClient 是開源免費的基于HttpClient 的網絡請求框架,其源碼托管在githu 上。下載地址
如圖5-1 所示為AsyncHttpClient 在github 的網頁,大家可以點擊右下角的按鈕,將源碼下載下來,然后將下載好的壓縮包解壓,把src 目錄中源碼拷貝到自己的工程的src 目錄下,比如圖5-2所示。
5.1 get 方式提交
在1.2 節工程的基礎上,只需要修改部分代碼即可。因此這里只給出核心代碼。
【文件1-7】get 方式提交數據代碼片段
5.2 post 方式提交
【文件1-8】post 方式提交數據代碼片段
/**使用AsyncHttpClient 的post 方式提交數據*/ public void login6(View view) {// 獲取用戶數據final String username = et_username.getText().toString().trim();final String password = et_password.getText().toString().trim();// 校驗數據if (TextUtils.isEmpty(password) || TextUtils.isEmpty(username)) {Toast.makeText(this, "用戶名或密碼不能為空!", Toast.LENGTH_SHORT).show();return;}String path = "http://10.0.2.2:8080/userlogin/servlet/LoginServlet";// 創建一個AsyncHttpClientAsyncHttpClient client = new AsyncHttpClient();//封裝請求參數RequestParams params = new RequestParams();params.put("username", username);params.put("password", password);/** 執行post 請求* 參數1:url 地址* 參數2:請求參數* 參數3:回調接口,在該接口中實現成功和失敗方法,該過程是異步的。*/client.post(path, params, new DataAsyncHttpResponseHandler() {@Overridepublic void onSuccess(int statusCode, Header[] headers, byte[] responseBody){Toast.makeText(MainActivity.this, new String(responseBody), Toast.LENGTH_LONG).show();}@Overridepublic void onFailure(int statusCode, Header[] headers, byte[] responseBody,Throwable error) {Toast.makeText(MainActivity.this, new String(responseBody), Toast.LENGTH_LONG).show();}}); }5.3 總結
5.3.1 get 請求方式步驟
1、創建一個AsyncHttpClient 對象
AsyncHttpClient client = new AsyncHttpClient();2、執行get 方法
/** 執行get 方式請求* 參數1:請求的url 地址* 參數2:回調接口。在該接口中實現相應成功和失敗方法,該過程是異步的。*/client.get(path, new DataAsyncHttpResponseHandler() {/** 請求網絡是在子線程中進行的,當請求成功后回調onSuccess 方法,* 該方法是在主線程中被調用了,其內部是通過Handler 實現的* 當請求成功后回調該方法* 參數1:返回的狀態碼* 參數2:響應頭* 參數3:返回的數據*/@Overridepublic void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {Log.d("tag", Thread.currentThread().getName());//將responseBody 字節數組轉化為字符串Toast.makeText(MainActivity.this, new String(responseBody), Toast.LENGTH_LONG).show();}// 當請求失敗后回調該方法,該方法依然在主線程中被執行@Overridepublic void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {Toast.makeText(MainActivity.this, new String(responseBody), Toast.LENGTH_LONG).show();} });在執行get 方法的時候需要傳遞一個ResponseHandlerInterface 接口的子類, 在上面使用了DataAsyncHttpResponseHandler 抽象類,除此之外其實ResponseHandlerInterface 還有很多子類,比如用于接收json 字符串的TextHttpResponseHandler,用于接收文件下載的TextHttpResponseHandler。因為我們只需要接收字節數組,并將字節數組轉化為字符串即可,因此我們使用DataAsyncHttpResponseHandler 抽象類。
5.3.2 post 方式請求步驟
1、創建一個AsyncHttpClient 對象
AsyncHttpClient client = new AsyncHttpClient();2、封裝請求參數
//封裝請求參數 RequestParams params = new RequestParams(); params.put("username", username); params.put("password", password);3、執行post 方法
/** 執行post 請求* 參數1:url 地址* 參數2:請求參數* 參數3:回調接口,在該接口中實現成功和失敗方法,該過程是異步的。*/ client.post(path, params, new DataAsyncHttpResponseHandler() {@Overridepublic void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {Toast.makeText(MainActivity.this, new String(responseBody), Toast.LENGTH_LONG).show();}@Overridepublic void onFailure(int statusCode, Header[] headers, byte[] responseBody,Throwable error) {Toast.makeText(MainActivity.this, new String(responseBody), Toast.LENGTH_LONG).show();} });post 方法跟get 方式不同的就是多了一個RequestParams 參數。
6. JavaSE 實現多線程下載
6.1 多線程下載原理
多線程下載就是將同一個網絡上的原始文件根據線程個數分成均等份,然后每個單獨的線程下載對應的一部分,然后再將下載好的文件按照原始文件的順序“拼接”起來就構成了完整的文件了。這樣就大大提高了文件的下載效率。
多線程下載大致可分為以下幾個步驟:
1)獲取服務器上的目標文件的大小
顯然這一步是需要先訪問一下網絡,只需要獲取到目標文件的總大小即可。目的是為了計算每個線程應該分配的下載任務。
2)計算每個線程下載的起始位置和結束位置
我們可以把原始文件當成一個字節數組,每個線程只下載該“數組”的指定起始位置和指定結束位置之間的部分。在第一步中我們已經知道了“數組”的總長度。因此只要再知道總共開啟的線程的個數就好
計算每個線程要下載的范圍了。
每個線程需要下載的字節個數(blockSize)= 總字節數(totalSize)/線程數(threadCount)
假設給線程按照0,1,2,3…n 的方式依次進行編號,那么第n 個線程下載文件的范圍為:
起始腳標 startIndex=n*blockSize
結束腳標 endIndex=(n-1)*blockSize-1
考慮到totalSize/threadCount 不一定能整除,因此對已最后一個線程應該特殊處理,最后一個線程的起始腳標計算公式不變,但是結束腳標endIndex=totalSize-1;即可。
3)在本地創建一個跟原始文件同樣大小的文件
在本地可以通過RandomAccessFile 創建一個跟目標文件同樣大小的文件,該api 支持文件任意位置的讀寫操作。這樣就給多線程下載提供了方便,每個線程只需在指定起始和結束腳標范圍內寫數據即可。
4)開啟多個子線程開始下載
5)記錄下載進度
為每一個單獨的線程創建一個臨時文件,用于記錄該線程下載的進度。對于單獨的一個線程,每下載一部分數據就在本地文件中記錄下當前下載的字節數。這樣子如果下載任務異常終止了,那么下次重新開
始下載時就可以接著上次的進度下載。
6)刪除臨時文件
當多個線程都下載完成之后,最后一個下載完的線程將所有的臨時文件刪除。
6.2 代碼實現
【文件1-9】多線程下載JavaSE 代碼
package com.itheima.se.download; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; public class MultiDownlodTest { // 實現多線程斷點續下載public static void main(String[] args) {String sourcePath = "http://localhost:8080/FeiQ.exe";String targetPath = "d://FeiQ.exe";new MultiDownloader(sourcePath,3,targetPath).download();}static class MultiDownloader {private String sourcePath;private int threadCount;private String targetPath;//未完成任務的線程private int threadRunning;public MultiDownloader(String sourcePath,int threadCount,String targetPath){this.sourcePath = sourcePath;this.threadCount = threadCount;this.targetPath = targetPath;}public void download(){try {URL url = new URL(sourcePath);HttpURLConnection connection = (HttpURLConnection) url.openConnection();//配置連接參數connection.setRequestMethod("GET");connection.setConnectTimeout(5000);//打開連接connection.connect();int responseCode = connection.getResponseCode();if (responseCode==200) {int totalSize = connection.getContentLength();//計算每個線程下載的平均字節數int avgSize = totalSize/threadCount;for(int i=0;i<threadCount;i++){final int startIndex = i*avgSize;int endIndex = 0;if (i==threadCount-1) {endIndex = totalSize-1;}else {endIndex = (i+1)*avgSize-1;}threadRunning++;//開啟子線程,實現下載new MyDownloadThread(i, startIndex, endIndex,targetPath,sourcePath).start();}}else {System.out.println("返回碼為:"+responseCode+" 請求文件長度失敗!");return;}} catch (Exception e) {e.printStackTrace();}}class MyDownloadThread extends Thread{private int id;private int startIndex;private int endIndex;private String targetPath;private String sourcePath;public MyDownloadThread(int id,int startIndex, int endIndex,String targetPath,String sourcePath){this.id = id;this.startIndex = startIndex;this.endIndex = endIndex;this.targetPath = targetPath;this.sourcePath = sourcePath;}@Overridepublic void run() {try {URL url = new URL(sourcePath);HttpURLConnection connection = (HttpURLConnection) url.openConnection();//設置斷點下載參數connection.setRequestMethod("GET");File file = new File("d://"+id+".tmp");// 該屬性設置后,返回的狀態碼就不再是200,而是206int currentIndex = -1;//讀進度if (file.exists()) {BufferedReader reader = new BufferedReader(new FileReader(file));String readLine = reader.readLine();currentIndex = Integer.valueOf(readLine);reader.close();}else {currentIndex = startIndex;}// 只有設置了該屬性才能下載指定范圍的文件connection.setRequestProperty("Range", "bytes="+currentIndex+"-"+endIndex);connection.setConnectTimeout(5000);connection.setReadTimeout(5000);connection.connect();int responseCode = connection.getResponseCode();//因為請求的是一個文件的部分,因此返回的狀體碼是206if (responseCode==206) {InputStream inputStream = connection.getInputStream();//支持隨機讀寫的文件類RandomAccessFile raf = new RandomAccessFile(targetPath, "rws");//將文件指針定位到要寫的位置raf.seek(currentIndex);byte[] buffer = new byte[1024*16];int len = -1;int totalDownloded = 0;while((len=inputStream.read(buffer))!=-1){raf.write(buffer, 0, len);totalDownloded+=len;//寫進度BufferedWriter writer = new BufferedWriter(new FileWriter(file));writer.write(currentIndex+totalDownloded+"");writer.close();System.out.println(id+"下載了:"+totalDownloded+","+(totalDownloded+currentIndex-startIndex)*100/(endIndex-startIndex)+"%");}raf.close();inputStream.close();connection.disconnect();//線程執行完了threadRunning--;if (threadRunning==0) {for(int i=0;i<threadCount;i++){File tmpFile = new File("d://"+i+".tmp");tmpFile.delete();}}}else {System.out.println("返回的狀態碼為:"+responseCode+ " 下載失敗!");}} catch (Exception e) {e.printStackTrace();}}}} }6.3 新技能:
- 多線程下載文件的請求屬性
如果我們想請求服務器上某文件的一部分,而不是將整個文件都下載下來,那么就必須設置如下屬性:
connection.setRequestProperty("Range", "bytes="+currentIndex+"-"+endIndex);- 請求部分文件返回成功的狀態碼是206
當我們請求部分文件成功后,服務器返回的狀態碼不是200,而是206。
7. Android 實現多線程下載
將上面JavaSE 實現多線程下載的代碼經過一定的改造,便可移植到Android 上。有所不同的是Android有界面可以跟用戶進行良好的交互,在界面(如圖1-4)上讓用戶輸入原文件地址、線程個數,然后點擊確定開始下載。為了讓用戶可以清晰的看到每個線程下載的進度根據線程個數動態的生成等量的進度條(ProgressBar)。
7.1 ProgressBar 的使用
ProgressBar 是一個進度條控件,用于顯示一項任務的完成進度。其有兩種樣式,一種是圓形的,該種樣式是系統默認的,由于無法顯示具體的進度值,適合用于不確定要等待多久的情形下;另一種是長條形的, ,此類進度條有兩種顏色,高亮顏色代表任務完成的總進度。對于我們下載任務來說,由于總任務(要下載的字節數)是明確的,當前已經完成的任務(已經下載的字節數)也是明確的,因此特別適合使用后者。
由于在我們的需求里ProgressBar 是需要根據線程的個數動態添加的,而且要求是長條形的。因此可以事先在布局文件中編寫好ProgressBar 的樣式。當需要用到的時候再將該布局(見【文件1-11】)填充起來。
ProgressBar 的max 屬性代表其最大刻度值,progress 屬性代表當前進度值。使用方法如下:
給ProgressBar 設置最大刻度值和修改進度值可以在子線程中操作的,其內部已經特殊處理過了,因此不需要再通過發送Message 讓主線程修改進度。
7.2 編寫布局
這里給出兩個布局,【文件1-10】是MainActivity 的布局,其顯示效果如圖1-4。【文件1-11】是ProgressBar布局。
【文件1-10】activity_main.java
<LinearLayout 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:orientation="vertical"><EditText android:id="@+id/et_url"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="http://10.0.2.2:8080/FeiQ.exe" /><LinearLayout android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><EditText android:id="@+id/et_threadCount"android:layout_width="0dp"android:layout_weight="1"android:layout_height="wrap_content"android:text="3"/><Button android:onClick="download"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="下載"/></LinearLayout><LinearLayout android:id="@+id/ll_pbs"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"></LinearLayout></LinearLayout>【文件1-11】progress_bar.xml
<?xml version="1.0" encoding="utf-8"?> <ProgressBar xmlns:android="http://schemas.android.com/apk/res/android"style="?android:attr/progressBarStyleHorizontal"android:layout_width="match_parent"android:layout_height="wrap_content" > </ProgressBar>7.3 編寫代碼
【文件1-12】MainActivity.java
package com.itheima.android.downloader; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.InputStream; import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import android.os.Bundle; import android.support.v4.app.FragmentActivity; import android.text.TextUtils; import android.view.View; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.Toast; /**Android 實現多線程斷點續傳*/ public class MainActivity extends FragmentActivity {private EditText et_url;private EditText et_threadCount;//用于添加ProgressBar 的容器private LinearLayout ll_pbs;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);//初始化控件et_url = (EditText) findViewById(R.id.et_url);et_threadCount = (EditText) findViewById(R.id.et_threadCount);ll_pbs = (LinearLayout) findViewById(R.id.ll_pbs);}/**點擊Button 綁定的方法開始下載任務*/public void download(View view) {// 獲取用戶輸入的初始值final String sourcePath = et_url.getText().toString().trim();final int threadCount = Integer.valueOf(et_threadCount.getText().toString().trim());// 校驗數據if (TextUtils.isEmpty(sourcePath) || threadCount < 1) {Toast.makeText(this, "輸入的內容不合法!", Toast.LENGTH_SHORT).show();return;}//存儲到Android 本地的位置final String targetPath = "/mnt/sdcard/FeiQ.exe";//初始化進度條//填充一個ProgressBarfor(int i=0;i<threadCount;i++){//將進度條布局填充為ProgressBarProgressBar pb = (ProgressBar) View.inflate(MainActivity.this, R.layout.progress_bar, null);//將ProgressBar 添加到LinearLayout 中ll_pbs.addView(pb);}//開啟子線程開始下載任務new Thread(new Runnable() {@Overridepublic void run() {//開啟多線程下載器new MultiDownloader(sourcePath,threadCount,targetPath).download();}}).start();}class MultiDownloader {//服務器原始文件地址private String sourcePath;//線程個數private int threadCount;//本地目標存儲文件路徑private String targetPath;//未完成任務的線程private int threadRunning;public MultiDownloader(String sourcePath,int threadCount,String targetPath){this.sourcePath = sourcePath;this.threadCount = threadCount;this.targetPath = targetPath;}public void download(){try {URL url = new URL(sourcePath);HttpURLConnection connection = (HttpURLConnection) url.openConnection();//配置連接參數connection.setRequestMethod("GET");connection.setConnectTimeout(5000);//打開連接connection.connect();int responseCode = connection.getResponseCode();if (responseCode==200) {//獲取總字節數int totalSize = connection.getContentLength();//計算每個線程下載的平均字節數int avgSize = totalSize/threadCount;//計算每個線程下載的范圍for(int i=0;i<threadCount;i++){final int startIndex = i*avgSize;int endIndex = 0;if (i==threadCount-1) {endIndex = totalSize-1;}else {endIndex = (i+1)*avgSize-1;}threadRunning++;//開啟子線程,實現下載new MyDownloadThread(i, startIndex, endIndex,targetPath,sourcePath).start();}}else {System.out.println("返回碼為:"+responseCode+" 請求文件長度失敗!");return;}} catch (Exception e) {e.printStackTrace();}}/**下載線程*/class MyDownloadThread extends Thread{private int id;private int startIndex;private int endIndex;private String targetPath;private String sourcePath;public MyDownloadThread(int id,int startIndex,int endIndex,String targetPath,String sourcePath){this.id = id;this.startIndex = startIndex;this.endIndex = endIndex;this.targetPath = targetPath;this.sourcePath = sourcePath;}@Overridepublic void run() {try {URL url = new URL(sourcePath);HttpURLConnection connection =(HttpURLConnection) url.openConnection();//設置斷點下載參數connection.setRequestMethod("GET");//根據線程的id 創建臨時文件,用于記錄下載的進度File file = new File("/mnt/sdcard/"+id+".tmp");// 該屬性設置后,返回的狀態碼就不再是200,而是206int currentIndex = -1;//讀進度if (file.exists()) {BufferedReader reader = new BufferedReader(new FileReader(file));String readLine = reader.readLine();//讀取歷史進度currentIndex = Integer.valueOf(readLine);reader.close();}else {currentIndex = startIndex;}//設置多線程下載屬性connection.setRequestProperty("Range", "bytes="+currentIndex+"-"+endIndex);connection.setConnectTimeout(5000);connection.setReadTimeout(5000);connection.connect();int responseCode = connection.getResponseCode();if (responseCode==206) {InputStream inputStream = connection.getInputStream();//支持隨機讀寫的文件類RandomAccessFile raf = new RandomAccessFile(targetPath, "rws");//將文件指針定位到要寫的位置raf.seek(currentIndex);byte[] buffer = new byte[1024*16];int len = -1;int totalDownloded = 0;//獲取線性布局的子控件(ProgressBar)ProgressBar pb = (ProgressBar) ll_pbs.getChildAt(id);//設置對打的進度值pb.setMax(endIndex-startIndex);while((len=inputStream.read(buffer))!=-1){raf.write(buffer, 0, len);totalDownloded+=len;//寫進度BufferedWriter writer = new BufferedWriter(new FileWriter(file));writer.write(currentIndex+totalDownloded+"");writer.close();//修改進度條pb.setProgress(currentIndex+totalDownloded-startIndex);System.out.println(id+"下載了:"+totalDownloded+","+(totalDownloded+currentIndex-startIndex)*100/(endIndex-startIndex)+"%");}raf.close();inputStream.close();connection.disconnect();//線程執行完了threadRunning--;//如果所有的線程都下載完了,則刪除所有的臨時文件if (threadRunning==0) {for(int i=0;i<threadCount;i++){File tmpFile = new File("/mnt/sdcard/"+i+".tmp");tmpFile.delete();}}}else {System.out.println("返回的狀態碼為:"+responseCode+ " 下載失敗!");}} catch (Exception e) {e.printStackTrace();}}}} }添加權限,在該工程中不僅用到了網絡訪問還用到了sdcard 存儲,因此需要添加兩個權限。
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>8. xUtils 實現多線程下載
8.1 xUtils 簡介
xUtils 是開源免費的Android 工具包,代碼托管在github 上。目前xUtils 主要有四大模塊:
- DbUtils 模塊:操作數據庫的框架。
- ViewUtils 模塊:通過注解的方式可以對UI,資源和事件綁定進行管理。
- HttpUtils 模塊:提供了方便的網絡訪問,斷點續傳等功能。
- BitmapUtils 模塊:提供了強大的圖片處理工具。
我們在這里只簡單實用xUtils 工具中的HttpUtils 工具。xUtils 的下載地址
8.2 xUtils 之HttpUtils 的使用
- 下載xUtils 工具如圖1-5 所示,右下角“Download ZIP”
- 將下載好的zip 包解壓,然后將src 下的源代碼拷貝在自己工程中如圖8-6
8.3 使用HttpUtils
HttpUtils 的使用非常簡單,因此這里直接給出核心代碼。【文件1-13】使用HttpUtils 完成文件下載
/**使用xUtils 中的HttpUtils 模塊進行下載*/ public void downloadHttpUtils(View view){HttpUtils httpUtils = new HttpUtils();String url="http://10.0.2.2:8080/FeiQ.exe";String target = "/mnt/sdcard/FeiQ2.exe";/** 參數1:原文件網絡地址* 參數2:本地保存的地址* 參數3:是否支持斷點續傳,true:支持,false:不支持* 參數4:回調接口,該接口中的方法都是在主線程中被調用的,* 也就是該接口中的方法都可以修改UI*/httpUtils.download(url, target, true, new RequestCallBack<File>() {//當下載任務開始時被調用@Overridepublic void onStart() {Log.d("tag", "onStart"+Thread.currentThread().getName());}/** 每下載一部分就被調用一次,通過該方法可以知道當前下載進度* 參數1:原文件總字節數* 參數2:當前已經下載好的字節數* 參數3:是否在上傳,對于下載,該值為false*/@Overridepublic void onLoading(long total, long current, boolean isUploading) {Log.d("tag", "onLoading"+Thread.currentThread().getName()+"//"+"current"+current+"//"+"total="+total);}//下載成功后調用一次@Overridepublic void onSuccess(ResponseInfo<File> responseInfo) {Toast.makeText(MainActivity.this, "下載成功", Toast.LENGTH_SHORT).show();}//失敗后調用一次@Overridepublic void onFailure(HttpException error, String msg) {Toast.makeText(MainActivity.this, "下載失敗:"+msg, Toast.LENGTH_LONG).show();}}); }1. HttpClient
1.1 發送get請求
- 創建一個客戶端對象
- 創建一個get請求對象
- 發送get請求,建立連接,返回響應頭對象
- 獲取狀態行對象,獲取狀態碼,如果為200則說明請求成功
1.2 發送post請求
//創建一個客戶端對象 HttpClient client = new DefaultHttpClient(); //創建一個post請求對象 HttpPost hp = new HttpPost(path);- 往post對象里放入要提交給服務器的數據
1.3 案例1:HttpClient框架提交數據
public class MainActivity extends Activity {Handler handler = new Handler(){@Overridepublic void handleMessage(android.os.Message msg) {Toast.makeText(MainActivity.this, (String)msg.obj, 0).show();}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void get(View v){EditText et_name = (EditText) findViewById(R.id.et_name);EditText et_pass = (EditText) findViewById(R.id.et_pass);final String name = et_name.getText().toString();final String pass = et_pass.getText().toString();Thread t = new Thread(){@Overridepublic void run() {String path = "http://192.168.13.13/Web/servlet/CheckLogin?name=" + URLEncoder.encode(name) + "&pass=" + pass;//使用httpClient框架做get方式提交//1.創建HttpClient對象HttpClient hc = new DefaultHttpClient();//2.創建httpGet對象,構造方法的參數就是網址HttpGet hg = new HttpGet(path);//3.使用客戶端對象,把get請求對象發送出去try {HttpResponse hr = hc.execute(hg);//拿到響應頭中的狀態行StatusLine sl = hr.getStatusLine();if(sl.getStatusCode() == 200){//拿到響應頭的實體HttpEntity he = hr.getEntity();//拿到實體中的內容,其實就是服務器返回的輸入流InputStream is = he.getContent();String text = Utils.getTextFromStream(is);//發送消息,讓主線程刷新ui顯示textMessage msg = handler.obtainMessage();msg.obj = text;handler.sendMessage(msg);}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}};t.start();}public void post(View v){EditText et_name = (EditText) findViewById(R.id.et_name);EditText et_pass = (EditText) findViewById(R.id.et_pass);final String name = et_name.getText().toString();final String pass = et_pass.getText().toString();Thread t = new Thread(){@Overridepublic void run() {String path = "http://192.168.13.13/Web/servlet/CheckLogin";//1.創建客戶端對象HttpClient hc = new DefaultHttpClient();//2.創建post請求對象HttpPost hp = new HttpPost(path);//封裝form表單提交的數據BasicNameValuePair bnvp = new BasicNameValuePair("name", name);BasicNameValuePair bnvp2 = new BasicNameValuePair("pass", pass);List<BasicNameValuePair> parameters = new ArrayList<BasicNameValuePair>();//把BasicNameValuePair放入集合中parameters.add(bnvp);parameters.add(bnvp2);try {//要提交的數據都已經在集合中了,把集合傳給實體對象UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "utf-8");//設置post請求對象的實體,其實就是把要提交的數據封裝至post請求的輸出流中hp.setEntity(entity);//3.使用客戶端發送post請求HttpResponse hr = hc.execute(hp);if(hr.getStatusLine().getStatusCode() == 200){InputStream is = hr.getEntity().getContent();String text = Utils.getTextFromStream(is);//發送消息,讓主線程刷新ui顯示textMessage msg = handler.obtainMessage();msg.obj = text;handler.sendMessage(msg);}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}};t.start();} } if (code == 200) {// 數據獲取成功// 獲取讀取的字節流InputStream is = conn.getInputStream();// 把字節流轉換成字符流bfr = new BufferedReader(new InputStreamReader(is));// 讀取一行信息String line = bfr.readLine();// json字符串數據的封裝StringBuilder json = new StringBuilder();while (line != null) {json.append(line);line = bfr.readLine();}parseJson = parseJson(json);//返回數據封裝信息} public class Utils {public static String getTextFromStream(InputStream is){byte[] b = new byte[1024];int len = 0;ByteArrayOutputStream bos = new ByteArrayOutputStream();try {while((len = is.read(b)) != -1){bos.write(b, 0, len);}String text = new String(bos.toByteArray());bos.close();return text;} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}return null;} }StreamUtils.java
public class StreamUtils {/*** 將輸入流讀取成String后返回** @param in* @return* @throws IOException*/public static String readFromStream(InputStream in) throws IOException {ByteArrayOutputStream out = new ByteArrayOutputStream();int len = 0;byte[] buffer = new byte[1024];while ((len = in.read(buffer)) != -1) {out.write(buffer, 0, len);}String result = out.toString();in.close();out.close();return result;} }2. 異步HttpClient框架
2.1 發送get請求
//創建異步的httpclient對象 AsyncHttpClient ahc = new AsyncHttpClient(); //發送get請求 ahc.get(path, new MyHandler());- 注意AsyncHttpResponseHandler兩個方法的調用時機
2.2 發送post請求
- 使用RequestParams對象封裝要攜帶的數據
2.3 案例2:異步HttpClient框架
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}public void get(View v){EditText et_name = (EditText) findViewById(R.id.et_name);EditText et_pass = (EditText) findViewById(R.id.et_pass);final String name = et_name.getText().toString();final String pass = et_pass.getText().toString();String url = "http://192.168.13.13/Web/servlet/CheckLogin?name=" + URLEncoder.encode(name) + "&pass=" + pass;//創建異步httpclientAsyncHttpClient ahc = new AsyncHttpClient();//發送get請求提交數據ahc.get(url, new MyResponseHandler());}public void post(View v){EditText et_name = (EditText) findViewById(R.id.et_name);EditText et_pass = (EditText) findViewById(R.id.et_pass);final String name = et_name.getText().toString();final String pass = et_pass.getText().toString();String url = "http://192.168.13.13/Web/servlet/CheckLogin";//創建異步httpclientAsyncHttpClient ahc = new AsyncHttpClient();//發送post請求提交數據//把要提交的數據封裝至RequestParams對象RequestParams params = new RequestParams();params.add("name", name);params.add("pass", pass);ahc.post(url, params, new MyResponseHandler());}class MyResponseHandler extends AsyncHttpResponseHandler{//請求服務器成功時,此方法調用@Overridepublic void onSuccess(int statusCode, Header[] headers,byte[] responseBody) {Toast.makeText(MainActivity.this, new String(responseBody), 0).show();}//請求失敗此方法調用@Overridepublic void onFailure(int statusCode, Header[] headers,byte[] responseBody, Throwable error) {Toast.makeText(MainActivity.this, "請求失敗", 0).show();}}}3. 多線程下載
原理:服務器CPU分配給每條線程的時間片相同,服務器帶寬平均分配給每條線程,所以客戶端開啟的線程越多,就能搶占到更多的服務器資源
3.1 確定每條線程下載多少數據
- 發送http請求至下載地址
- 獲取文件總長度,然后創建長度一致的臨時文件
- 確定線程下載多少數據
3.2 計算每條線程下載數據的開始位置和結束位置
for(int id = 1; id <= 3; id++){//計算每個線程下載數據的開始位置和結束位置int startIndex = (id - 1) * blockSize;int endIndex = id * blockSize - 1;if(id == THREAD_COUNT){endIndex = length;}//開啟線程,按照計算出來的開始結束位置開始下載數據new DownLoadThread(startIndex, endIndex, id).start(); }3.3 再次發送請求至下載地址,請求開始位置至結束位置的數據
String path = "http://192.168.1.102:8080/editplus.exe";URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(5000); conn.setConnectTimeout(5000); conn.setRequestMethod("GET");//向服務器請求部分數據 conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex); conn.connect();- 下載請求到的數據,存放至臨時文件中
3.4 案例3:多線程下載
public class MultiDownload {static int ThreadCount = 3;static int finishedThread = 0;//確定下載地址static String path = "http://192.168.13.13:8080/QQPlayer.exe";public static void main(String[] args) {//發送get請求,請求這個地址的資源try {URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(5000);conn.setReadTimeout(5000);if(conn.getResponseCode() == 200){//拿到所請求資源文件的長度int length = conn.getContentLength();File file = new File("QQPlayer.exe");//生成臨時文件RandomAccessFile raf = new RandomAccessFile(file, "rwd");//設置臨時文件的大小raf.setLength(length);raf.close();//計算出每個線程應該下載多少字節int size = length / ThreadCount;for (int i = 0; i < ThreadCount; i++) {//計算線程下載的開始位置和結束位置int startIndex = i * size;int endIndex = (i + 1) * size - 1;//如果是最后一個線程,那么結束位置寫死if(i == ThreadCount - 1){endIndex = length - 1;} // System.out.println("線程" + i + "的下載區間是:" + startIndex + "---" + endIndex);new DownLoadThread(startIndex, endIndex, i).start();}}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}} class DownLoadThread extends Thread{int startIndex;int endIndex;int threadId;public DownLoadThread(int startIndex, int endIndex, int threadId) {super();this.startIndex = startIndex;this.endIndex = endIndex;this.threadId = threadId;}@Overridepublic void run() {//再次發送http請求,下載原文件try {File progressFile = new File(threadId + ".txt");//判斷進度臨時文件是否存在if(progressFile.exists()){FileInputStream fis = new FileInputStream(progressFile);BufferedReader br = new BufferedReader(new InputStreamReader(fis));//從進度臨時文件中讀取出上一次下載的總進度,然后與原本的開始位置相加,得到新的開始位置startIndex += Integer.parseInt(br.readLine());fis.close();}System.out.println("線程" + threadId + "的下載區間是:" + startIndex + "---" + endIndex);HttpURLConnection conn;URL url = new URL(MultiDownload.path);conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(5000);conn.setReadTimeout(5000);//設置本次http請求所請求的數據的區間conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);//請求部分數據,相應碼是206if(conn.getResponseCode() == 206){//流里此時只有1/3原文件的數據InputStream is = conn.getInputStream();byte[] b = new byte[1024];int len = 0;int total = 0;//拿到臨時文件的輸出流File file = new File("QQPlayer.exe");RandomAccessFile raf = new RandomAccessFile(file, "rwd");//把文件的寫入位置移動至startIndexraf.seek(startIndex);while((len = is.read(b)) != -1){//每次讀取流里數據之后,同步把數據寫入臨時文件raf.write(b, 0, len);total += len; // System.out.println("線程" + threadId + "下載了" + total);//生成一個專門用來記錄下載進度的臨時文件RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");//每次讀取流里數據之后,同步把當前線程下載的總進度寫入進度臨時文件中progressRaf.write((total + "").getBytes());progressRaf.close();}System.out.println("線程" + threadId + "下載完畢-------------------小志參上!");raf.close();MultiDownload.finishedThread++;synchronized (MultiDownload.path) {if(MultiDownload.finishedThread == MultiDownload.ThreadCount){for (int i = 0; i < MultiDownload.ThreadCount; i++) {File f = new File(i + ".txt");f.delete();}MultiDownload.finishedThread = 0;}}}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}} }4. 帶斷點續傳的多線程下載
- 定義一個int變量記錄每條線程下載的數據總長度,然后加上該線程的下載開始位置,得到的結果就是下次下載時,該線程的開始位置,把得到的結果存入緩存文件
- 下次下載開始時,先讀取緩存文件中的值,得到的值就是該線程新的開始位置
- 三條線程都下載完畢之后,刪除緩存文件
5. 手機版的斷點續傳多線程下載器
- 把剛才的代碼直接粘貼過來就能用,記得在訪問文件時的路徑要改成Android的目錄,添加訪問網絡和外部存儲的路徑
5.1 用進度條顯示下載進度
- 拿到下載文件總長度時,設置進度條的最大值
- 進度條需要顯示三條線程的整體下載進度,所以三條線程每下載一次,就要把新下載的長度加入進度條
- 定義一個int全局變量,記錄三條線程的總下載長度
- 刷新進度條
- 每次斷點下載時,從新的開始位置開始下載,進度條也要從新的位置開始顯示,在讀取緩存文件獲取新的下載開始位置時,也要處理進度條進度
5.2 添加文本框顯示百分比進度
tv.setText(progress * 100 / pb.getMax() + "%");5.3 案例4:斷點續傳
public class MainActivity extends Activity {static int ThreadCount = 3;static int finishedThread = 0;int currentProgress;String fileName = "QQPlayer.exe";//確定下載地址String path = "http://192.168.13.13:8080/" + fileName;private ProgressBar pb;TextView tv;Handler handler = new Handler(){public void handleMessage(android.os.Message msg) {//把變量改成long,在long下運算tv.setText((long)pb.getProgress() * 100 / pb.getMax() + "%");}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);pb = (ProgressBar) findViewById(R.id.pb);tv = (TextView) findViewById(R.id.tv);}public void click(View v){Thread t = new Thread(){@Overridepublic void run() {//發送get請求,請求這個地址的資源try {URL url = new URL(path);HttpURLConnection conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(5000);conn.setReadTimeout(5000);if(conn.getResponseCode() == 200){//拿到所請求資源文件的長度int length = conn.getContentLength();//設置進度條的最大值就是原文件的總長度pb.setMax(length);File file = new File(Environment.getExternalStorageDirectory(), fileName);//生成臨時文件RandomAccessFile raf = new RandomAccessFile(file, "rwd");//設置臨時文件的大小raf.setLength(length);raf.close();//計算出每個線程應該下載多少字節int size = length / ThreadCount;for (int i = 0; i < ThreadCount; i++) {//計算線程下載的開始位置和結束位置int startIndex = i * size;int endIndex = (i + 1) * size - 1;//如果是最后一個線程,那么結束位置寫死if(i == ThreadCount - 1){endIndex = length - 1;} // System.out.println("線程" + i + "的下載區間是:" + startIndex + "---" + endIndex);new DownLoadThread(startIndex, endIndex, i).start();}}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}};t.start();}class DownLoadThread extends Thread{int startIndex;int endIndex;int threadId;public DownLoadThread(int startIndex, int endIndex, int threadId) {super();this.startIndex = startIndex;this.endIndex = endIndex;this.threadId = threadId;}@Overridepublic void run() {//再次發送http請求,下載原文件try {File progressFile = new File(Environment.getExternalStorageDirectory(), threadId + ".txt");//判斷進度臨時文件是否存在if(progressFile.exists()){FileInputStream fis = new FileInputStream(progressFile);BufferedReader br = new BufferedReader(new InputStreamReader(fis));//從進度臨時文件中讀取出上一次下載的總進度,然后與原本的開始位置相加,得到新的開始位置int lastProgress = Integer.parseInt(br.readLine());startIndex += lastProgress;//把上次下載的進度顯示至進度條currentProgress += lastProgress;pb.setProgress(currentProgress);//發送消息,讓主線程刷新文本進度handler.sendEmptyMessage(1);fis.close();}System.out.println("線程" + threadId + "的下載區間是:" + startIndex + "---" + endIndex);HttpURLConnection conn;URL url = new URL(path);conn = (HttpURLConnection) url.openConnection();conn.setRequestMethod("GET");conn.setConnectTimeout(5000);conn.setReadTimeout(5000);//設置本次http請求所請求的數據的區間conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);//請求部分數據,相應碼是206if(conn.getResponseCode() == 206){//流里此時只有1/3原文件的數據InputStream is = conn.getInputStream();byte[] b = new byte[1024];int len = 0;int total = 0;//拿到臨時文件的輸出流File file = new File(Environment.getExternalStorageDirectory(), fileName);RandomAccessFile raf = new RandomAccessFile(file, "rwd");//把文件的寫入位置移動至startIndexraf.seek(startIndex);while((len = is.read(b)) != -1){//每次讀取流里數據之后,同步把數據寫入臨時文件raf.write(b, 0, len);total += len;System.out.println("線程" + threadId + "下載了" + total);//每次讀取流里數據之后,把本次讀取的數據的長度顯示至進度條currentProgress += len;pb.setProgress(currentProgress);//發送消息,讓主線程刷新文本進度handler.sendEmptyMessage(1);//生成一個專門用來記錄下載進度的臨時文件RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd");//每次讀取流里數據之后,同步把當前線程下載的總進度寫入進度臨時文件中progressRaf.write((total + "").getBytes());progressRaf.close();}System.out.println("線程" + threadId + "下載完畢-------------------小志參上!");raf.close();finishedThread++;synchronized (path) {if(finishedThread == ThreadCount){for (int i = 0; i < ThreadCount; i++) {File f = new File(Environment.getExternalStorageDirectory(), i + ".txt");f.delete();}finishedThread = 0;}}}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}} }6. HttpUtils的使用
HttpUtils本身就支持多線程斷點續傳,使用起來非常的方便
創建HttpUtils對象
HttpUtils http = new HttpUtils();
- 下載文件
?
6.1 案例5:開源框架xUtils的使用
public class MainActivity extends Activity {private TextView tv_failure;private TextView tv_progress;private ProgressBar pb;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tv_failure = (TextView) findViewById(R.id.tv_failure);tv_progress = (TextView) findViewById(R.id.tv_progress);pb = (ProgressBar) findViewById(R.id.pb);}public void click(View v){HttpUtils utils = new HttpUtils();String fileName = "QQPlayer.exe";//確定下載地址String path = "http://192.168.13.13:8080/" + fileName;utils.download(path, //下載地址"sdcard/QQPlayer.exe", //文件保存路徑true,//是否支持斷點續傳true, new RequestCallBack<File>() {//下載成功后調用@Overridepublic void onSuccess(ResponseInfo<File> arg0) {Toast.makeText(MainActivity.this, arg0.result.getPath(), 0).show();}//下載失敗調用@Overridepublic void onFailure(HttpException arg0, String arg1) {// TODO Auto-generated method stubtv_failure.setText(arg1);}@Overridepublic void onLoading(long total, long current,boolean isUploading) {// TODO Auto-generated method stubsuper.onLoading(total, current, isUploading);pb.setMax((int)total);pb.setProgress((int)current);tv_progress.setText(current * 100 / total + "%");}});} }總結
以上是生活随笔為你收集整理的Android应用开发:网络编程-2的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android应用开发:网络编程-1
- 下一篇: C/C++在Android开发中的应用