Android應用開發-網路程式設計(二)
Apache HttpClient框架
GET方式請求提交資料
1. 建立一個HttpClient
HttpClient hc = new DefaultHttpClient();
2. 建立一個HttpGet,要提交給伺服器的資料已經拼接在path中
HttpGet hg = new HttpGet(path);
3. 使用HttpClient物件傳送GET請求,建立連線,返回響應頭物件
HttpResponse hr = hc.execute(hg);
4. 拿到響應頭中的狀態行,獲取狀態碼,如果為200則說明請求成功
if(hr.getStatusLine().getStatusCode() == 200){ // 拿到響應頭中實體裡的內容,其實就是伺服器返回的輸入流 InputStream is = hr.getEntity().getContent(); String text = Utils.getTextFromStream(is); }
POST方式請求提交資料
1. 建立一個HttpClient
HttpClient hc = new DefaultHttpClient();
2. 建立一個HttpPost,構造方法的引數就是網址
HttpPost hp = newHttpPost(path);
3. 往HttpPost物件裡放入要提交給伺服器的資料
// 要提交的資料以鍵值對的形式封裝在BasicNameValuePair物件中 BasicNameValuePair bnvp = new BasicNameValuePair("name", name); BasicNameValuePair bnvp2 = new BasicNameValuePair("pass", pass); List<NameValuePair> parameters = new ArrayList<NameValuePair>(); parameters.add(bnvp); parameters.add(bnvp2);// 要提交的資料都已經在集合中了,把集合傳給實體物件,並指定進行URL編碼的碼錶 UrlEncodedFormEntity entity = new UrlEncodedFormEntity(parameters, "utf-8"); // 設定POST請求物件的實體,其實就是把要提交的資料封裝至POST請求的輸出流中 hp.setEntity(entity);
4. 使用HttpClient物件傳送POST請求,建立連線,返回響應頭物件
HttpResponse hr = hc.execute(hp);
5. 拿到響應頭中的狀態行,獲取狀態碼,如果為200則說明請求成功
if(hr.getStatusLine().getStatusCode() == 200){ // 拿到響應頭中實體裡的內容,其實就是伺服器返回的輸入流 InputStream is = hr.getEntity().getContent(); String text = Utils.getTextFromStream(is); }
android-async-http框架(基於Apache HttpClient框架封裝)
android-async-http框架是一個非同步的HttpClient框架,不需要我們自己建立子執行緒,框架會為我們建立子執行緒去執行網路的互動
GET方式請求提交資料
1.建立非同步HttpClient
AsyncHttpClient ahc = new AsyncHttpClient();
2. 傳送GET請求提交資料,提交的資料拼接在path上
ahc.get(path, new MyResponseHandler());
POST方式請求提交資料
1. 建立非同步HttpClient
AsyncHttpClient ahc = new AsyncHttpClient();
2. 把要提交的資料封裝至RequestParams
RequestParams params = new RequestParams(); params.add("name", name); params.add("pass", pass);
3. 傳送POST請求提交資料
ahc.post(path, params, new MyResponseHandler());
響應處理器AsyncHttpResponseHandler
class MyResponseHandler extends AsyncHttpResponseHandler{ // 請求伺服器成功時(響應碼是200)回撥此方法 @Override public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) { // responseBody的內容就是伺服器返回的資料 Toast.makeText(MainActivity.this, new String(responseBody,"GBK"), Toast.LENGTH_SHORT).show(); } // Http請求失敗(返回碼不為200),系統回撥此方法 @Override public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) { Toast.makeText(MainActivity.this, new String(responseBody,"GBK"), Toast.LENGTH_SHORT).show(); } }
多執行緒下載
伺服器CPU分配給每個執行緒的時間片相同,伺服器頻寬平均分配給每個執行緒,所以客戶端開啟的執行緒越多,就能搶佔到更多的伺服器資源。實際上並不是客戶端併發的下載執行緒越多,程式的下載速度就越快,因為當客戶端開啟太多的併發執行緒之後,應用程式需要維護每條執行緒的開銷、執行緒同步的開銷,這些開銷反而會導致下載速度降低;並且無論開多少個執行緒搶佔伺服器資源,下載頻寬也不會超過客戶端的物理頻寬
主執行緒首先發送Http GET請求確定每個執行緒下載哪部分資料
-
獲取資原始檔總大小,然後建立大小一致的臨時檔案
String path = "http://dldir1.qq.com/music/clntupate/QQMusic.apk"; 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(); // 獲得伺服器流中資料的長度 RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd");// 建立一個臨時檔案儲存下載的資料 raf.setLength(length); // 設定臨時檔案的大小 raf.close();
-
計算每個執行緒下載多少資料
int blockSize = length / THREAD_COUNT;
-
計算每個執行緒下載資料的開始位置和結束位置,然後開啟下載子執行緒
for(int id = 0; id < threadCount; id++){ // 計算每個執行緒下載資料的開始位置和結束位置 int startIndex = id * blockSize; int endIndex = (id+1) * blockSize - 1; // 如果是最後一個執行緒,結束位置為資原始檔的結尾 if(id == threadCount - 1){ endIndex = length - 1; } // 開啟執行緒,按照計算出來的開始結束位置開始下載資料 new DownLoadThread(startIndex, endIndex, id).start(); }
每個下載執行緒再次傳送Http GET請求,請求自己負責下載的那部分資料
-
請求自己負責的那部分資料,同步寫入到臨時檔案相應的位置
String path = "http://dldir1.qq.com/music/clntupate/QQMusic.apk"; URL url = new URL(path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setConnectTimeout(5000); conn.setReadTimeout(5000); conn.setRequestProperty("Range", "bytes=" + startIndex + "-" + endIndex);// 設定本次Http請求的資料區間 conn.connect(); //請求部分資料,成功的響應碼是206 if(conn.getResponseCode() == 206){ InputStream is = conn.getInputStream(); // 流裡此時只有1/ThreadCount資原始檔裡的資料 RandomAccessFile raf = new RandomAccessFile(getFileName(path), "rwd"); raf.seek(startIndex); // 把檔案的寫入位置移動至startIndex byte[] b = new byte[1024]; int len; while((len = is.read(b)) != -1){ raf.write(b, 0, len); } raf.close(); }
帶斷點續傳的多執行緒下載
-
每個下載執行緒定義了一個threadProgress成員變數記錄當前執行緒下載的進度,執行緒在往資源臨時檔案中寫入資料的同時記錄下threadProgress,並存入進度快取檔案
while((len = is.read(b)) != -1){ raf.write(b, 0, len); threadProgress += len; RandomAccessFile progressRaf = new RandomAccessFile(progressFile, "rwd"); progressRaf.write((threadProgress + "").getBytes()); progressRaf.close(); }
-
下次下載開始時,先讀取快取檔案中的值,得到的值就是該執行緒新的開始位置
FileInputStream fis = new FileInputStream(progressFile); BufferedReader br = new BufferedReader(new InputStreamReader(fis)); threadProgress = Integer.parseInt(br.readLine());// 從進度臨時檔案中讀取出上一次下載的總進度 startIndex += threadProgress; // 與原本的開始位置相加,得到新的開始位置,完成斷點續傳 fis.close();
-
所有執行緒都下載完畢之後,刪除快取檔案
finishedThread++; if(finishedThread == threadCount){ for(int i = 0; i < threadCount; i++){ File f = new File(target, i + ".txt"); f.delete(); } }
手機版的斷點續傳多執行緒下載器
用進度條顯示下載進度
-
拿到下載檔案總大小時,設定進度條的最大值
pb.setMax(length); // 設定進度條的最大值
-
進度條需要顯示所有執行緒的整體下載進度,所以各條執行緒每下載一次,就要把新下載的長度加入進度條
-
定義一個int全域性變數,記錄三條執行緒的總下載長度
int totalProgress;
-
重新整理進度條
while((len = is.read(b)) != -1){ raf.write(b, 0, len); // 每次讀取流裡資料之後,把每次讀取資料的長度顯示至進度條 totalProgress += len; pb.setProgress(totalProgress);
-
-
每次斷點下載時,從新的開始位置開始下載,進度條也要從新的位置開始顯示,在讀取快取檔案獲取新的下載開始位置時,也要處理進度條進度
FileInputStream fis = new FileInputStream(progressFile); BufferedReader br = new BufferedReader(new InputStreamReader(fis)); // 從進度臨時檔案中讀取出上一次下載的總進度 // 然後與原本的開始位置相加,得到新的開始位置,完成斷點續傳 threadProgress = Integer.parseInt(br.readLine()); startIndex += threadProgress; // 把上次多執行緒下載的總進度顯示至進度條 totalProgress += threadProgress; pb.setProgress(totalProgress);
新增文字框顯示百分比進度
tv.setText((long) pb.getProgress() * 100 / pb.getMax() + "%");// 文字進度與進度條是同步的,轉換成long型別防止整型溢位
使用開源框架xUtils下載檔案
開源框架xUtils是基於原來的開源框架afinal開發的,主要有四大模組,其中HttpUtils模組支援斷點續傳,隨時停止下載任務,開始任務
-
建立HttpUtils物件
HttpUtils http = new HttpUtils();
-
下載檔案
http.download(path, // 下載地址 target, // 下載資料儲存的路徑和檔名 true, // 是否支援斷點續傳 true, // 如果請求地址中沒有檔名,則檔名在響應頭中,下載完成後自動重新命名 new RequestCallBack<File>() {// 偵聽下載狀態 // 下載成功後回撥 @Override public void onSuccess(ResponseInfo<File> responseInfo) { Toast.makeText(MainActivity.this, responseInfo.result.getPath(), Toast.LENGTH_SHORT).show(); } // 下載失敗時回撥,比如檔案已經下載、沒有網路許可權、檔案訪問不到,方法傳入一個字串s告知失敗原因 @Override public void onFailure(HttpException e, String s) { tv_failure.setText(s); } // 在下載過程中不斷的呼叫,用於重新整理進度條 @Override public void onLoading(long total, long current, boolean isUploading) { super.onLoading(total, current, isUploading); pb.setMax((int) total); // 設定進度條總長度 pb.setProgress((int) current);// 設定進度條當前進度 tv_progress.setText(current * 100 / total + "%");// 文字進度 } });