1. 程式人生 > >Android應用開發-網路程式設計(二)

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 = new
HttpPost(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模組支援斷點續傳,隨時停止下載任務,開始任務

  1. 建立HttpUtils物件

    HttpUtils http = new HttpUtils();
  2. 下載檔案

    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 + "%");// 文字進度
        }
    });