1. 程式人生 > >OkHttp基本使用(三)上傳下載功能實現

OkHttp基本使用(三)上傳下載功能實現

  • 前言

本篇將使用OkHttp實現檔案的上傳和下載,以及下載實現斷點續傳功能。因為是基本的使用,此係列文章主要是以大家會用為主而寫的。當然,只要會用了,後面的優化、封裝等等就不難了。

  • 下載

使用OkHttp完成下載功能,實現斷點續傳,並附帶進度條顯示下載進度。
檔案下載的互動過程:


 
下載的流程:

  • 檔案下載的程式碼:
  1. public class DownloadActivity extends AppCompatActivity {
  2.     private ProgressBar mProgressBar;
  3.     //準備下載
  4.     public static final int BEGIN = 0;
  5.     //正在下載
  6.     public static final int DOWNLOADING = 1;
  7.     //結束下載
  8.     public static final int END = 2;
  9.     //下載的進度
  10.     private static int progress;
  11.     //是否停止下載
  12.     private boolean cancel ;
  13.     OkHttpClient okHttpClient = new OkHttpClient();
  14.     MyHandler mHandler = new MyHandler(this);
  15.     private ImageView mShowImage;
  16.     ByteArrayOutputStream baos = new ByteArrayOutputStream();
  17.     @Override
  18.     protected void onCreate(Bundle savedInstanceState) {
  19.         super.onCreate(savedInstanceState);
  20.         setContentView(R.layout.activity_download);
  21.         mProgressBar = (ProgressBar) findViewById(R.id.down_progress_bar);
  22.         mShowImage = (ImageView) findViewById(R.id.down_image);
  23.     }
  24.     public void click2(View view) {
  25.         cancel = true;
  26.     }
  27.     public void click(View view) {
  28.         cancel = false;
  29.         new Thread(new Runnable() {
  30.             @Override
  31.             public void run() {
  32.                 //例項化Builder物件
  33.                 Request.Builder builder = new Request.Builder();
  34.                 //設定Url
  35.                 builder.url(Config.IMAGE_URL);
  36.                 //獲取已經下載的大小
  37.                 int size = baos.size();
  38.                 //size表示已經下載的大小。如果不為0,則進行斷點續傳。
  39.                 if (size > 0) {
  40.                     //設定斷點續傳的開始位置,格式bytes=123456-
  41.                     builder.header("Range", "bytes=" + size + "-");
  42.                     //設定ProgressBar的當前進度從停止位置開始
  43.                     progress = size;
  44.                 }
  45.                 //建立Request物件
  46.                 Request request = builder.build();
  47.                 try {
  48.                     //執行下載請求,並獲得Response物件
  49.                     Response response = okHttpClient.newCall(request).execute();
  50.                     //請求成功
  51.                     if (response.isSuccessful()) {
  52.                         //從Response物件中獲取輸入流物件
  53.                         InputStream inputStream = response.body().byteStream();
  54.                         //size==0表示第一次下載,非斷點續傳
  55.                         if (size == 0) {
  56.                             //獲取檔案的大小
  57.                             int contentLength = (int) response.body().contentLength();
  58.                             //將檔案總大小通過Handler傳遞到UI執行緒,設定ProgressBar的總進度值
  59.                             mHandler.obtainMessage(BEGIN,contentLength,0).sendToTarget();
  60.                         }
  61.                         int len = 0;
  62.                         byte[] buffer = new byte[1024];
  63.                         //迴圈讀取檔案流,開始下載
  64.                         while((len = inputStream.read(buffer)) != -1) {
  65.                             if (cancel) {
  66.                                 //如果點選了停止按鈕,cancel為true。則結束迴圈
  67.                                 break;
  68.                             }
  69.                             //將流寫入快取
  70.                             baos.write(buffer,0,len);
  71.                             baos.flush();
  72.                             //傳送下載進度
  73.                             mHandler.obtainMessage(DOWNLOADING,len,0).sendToTarget();
  74.                         }
  75.                         //下載完成,結束請求,關閉body
  76.                         response.body().close();
  77.                         //將位元組轉成Bitmap物件
  78.                         byte[] bytes = baos.toByteArray();
  79.                         Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
  80.                         //下載完成通知更新試圖
  81.                         mHandler.obtainMessage(END,bitmap).sendToTarget();
  82.                     }
  83.                 } catch (IOException e) {
  84.                     e.printStackTrace();
  85.                 }
  86.             }
  87.         }).start();
  88.     }
  89.     static class MyHandler extends Handler {
  90.         private WeakReference<DownloadActivity> activityWeakReference;
  91.         public MyHandler(DownloadActivity activity) {
  92.             this.activityWeakReference = new WeakReference<DownloadActivity>(activity);
  93.         }
  94.         @Override
  95.         public void handleMessage(Message msg) {
  96.             switch (msg.what) {
  97.                 case BEGIN:
  98.                     activityWeakReference.get().mProgressBar.setMax(msg.arg1);
  99.                     break;
  100.                 case DOWNLOADING:
  101.                     progress += msg.arg1;
  102.                     activityWeakReference.get().mProgressBar.setProgress(progress);
  103.                     break;
  104.                 case END:
  105.                     progress = 0;
  106.                     activityWeakReference.get().mShowImage.setImageBitmap((Bitmap)msg.obj);
  107.                     break;
  108.             }
  109.         }
  110.     }
  111. }

複製程式碼

以上是檔案下的程式碼。實現了斷點續傳,其中能進行斷點續傳的關鍵程式碼為builder.header("Range", "bytes=" + size + "-");程式碼中都有註釋,結合上面的流程圖應該不難理解。
程式碼解釋:原始碼的效果是點選開始按鈕出發click事件開始下載,點選停止按鈕觸發click2事件中斷下載。下載的進度監聽是在while迴圈中,通過Handler進行的進度更新。

  • 檔案上傳

本案例中,實現帶引數的檔案上傳功能-----同時完成引數傳遞和檔案上傳。
程式碼如下:

public class UploadActivity extends AppCompatActivity {

    public static final int GET_PIC = 1;

    private ImageView mShowImage;

    private Uri uri;

    OkHttpClient okHttpClient = new OkHttpClient();

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_upload);

        mShowImage = (ImageView) findViewById(R.id.upload_show_image);

    }

    public void click(View view) {

        switch (view.getId()) {

            case R.id.upload_choose_file:

                choosePic();//選擇圖片

                break;

            case R.id.upload_start:

                upload();//上傳

                break;

        }

    }

    /**

     * 上傳

     */

    private void upload() {

        if (uri == null) {

            Toast.makeText(UploadActivity.this, "請先選擇檔案", Toast.LENGTH_SHORT).show();

            return;

        }

        //設定檔案的媒體型別,image/*表示匹配所有的圖片檔案

        MediaType mediaType = MediaType.parse("image/*");

        MultipartBody.Builder builder = new MultipartBody.Builder();

        //檔案上傳,此處是關鍵,設定媒體型別為multipart/form-data,表示多種格式的表單資料上傳

      builder.setType(MultipartBody.FORM);

        //新增上傳的引數username=androidxx

        builder.addFormDataPart("username","androidxx");

        //新增上傳的檔案。檔案是從相簿讀取的檔案流。

        try {

            //獲得需要上傳的檔案流

            InputStream inputStream = getContentResolver().openInputStream(uri);

            int len = 0;

            byte[] buffer = new byte[1024];

            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            while((len = inputStream.read(buffer)) != -1) {

                baos.write(buffer, 0, len);

            }

            inputStream.close();

            /*

             * 新增檔案到Builder中。如果要實現多檔案同時上傳,可以新增多個addFormDataPart。

             * 注意:

             * 引數一:上傳的檔案的標示,同username。也就是可以在伺服器端通過upload找到對應的檔案流

             * 引數二:檔案的名稱。上傳到伺服器之後以此名稱命名檔案

             * 引數三:需要上傳的檔案。包含在RequestBody中

             * RequestBody.create方法有多個過載的方法,可以選擇不同的資料來源。此處選擇的是位元組形式(baos.toByteArray())的資料眼。

             */

builder.addFormDataPart("upload", "test.jpg", RequestBody.create(mediaType, baos.toByteArray()));

        } catch (IOException e) {

            e.printStackTrace();

        }

        //建立MultipartBody物件,MultipartBody是RequestBody的子類,用於檔案上傳。

        MultipartBody multipartBody = builder.build();

        Request request = new Request.Builder()

                .url("http://192.168.3.4:8080/WebServer/upload.do")//上傳的伺服器地址

                .post(multipartBody)

                .build();

        //開始上傳。採用Post非同步請求的方式

        okHttpClient.newCall(request).enqueue(new Callback() {

            @Override

            public void onFailure(Call call, IOException e) {

                Log.d("androidxx.cn","--" + e.getMessage());

                e.printStackTrace();

            }

            @Override

            public void onResponse(Call call, Response response) throws IOException {

                //接受到成功的返回結果

                if (response.isSuccessful()) {

                    Log.d("androidxx.cn","-上傳成功-");

                } else {

                    Log.d("androidxx.cn","-失敗--" + response.body().string());

                }

            }

        });

    }

    /**

     * 開啟相簿,選擇檔案後返回

     */

    private void choosePic() {

        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);

        intent.setType("image/*");

        startActivityForResult(intent,GET_PIC);

    }

    /**

     * 接收選擇的圖片

     * @param requestCode

     * @param resultCode

     * @param data

     */

    @Override

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (resultCode == RESULT_CANCELED) {

            return;

        }

        //獲得圖片的URI

        uri = data.getData();

        //通過ContentResolver獲得圖片物件

        ContentResolver contentResolver = getContentResolver();

        InputStream inputStream = null;

        try {

            inputStream = contentResolver.openInputStream(uri);

        } catch (FileNotFoundException e) {

            e.printStackTrace();

        }

        //將流轉換成圖片,顯示到ImageView中

        Bitmap bitmap = BitmapFactory.decodeStream(inputStream);

        mShowImage.setImageBitmap(bitmap);

    }

}

複製程式碼如上程式碼,第37行和第59行是上傳能成功的重點。

  • 多檔案長傳

實現多檔案上傳,只需將上面單檔案上傳中的builder.addFormDataPart("upload", "test.jpg", RequestBody.create(mediaType, baos.toByteArray()));這一句執行多次,即使用多個addFormDataPart方法新增多個檔案,然後就可以同時上傳多個檔案了。

  • 總結

1、檔案上傳和下載的過程其實就是一種特殊的Post和Get請求。總體的過程與Post請求和Get請求方式一樣。
2、下載相當於一個特殊的Get請求,只是伺服器返回的資料格式是檔案流。我們也只能通過讀取流來獲得資料。
3、上傳相當於一個特殊的Post請求,前面我們說過,Post請求就是傳引數比較特殊和多樣化。檔案上傳就是一種特殊的引數傳遞----引數是一個檔案。
大家在看如上程式碼的時候,不要覺得陌生,其實程式碼的流程和邏輯同Post和Get請求一樣,只是多了幾行程式碼。

本案例的android端原始碼:Github
本案例的上傳伺服器配置方式【點選檢視】,上傳服務端原始碼:Github     伺服器端程式碼請閱讀Github中的readme.md檔案。伺服器端程式碼匯入工程沒有錯之後,可以將程式碼載入到伺服器,之後啟動伺服器就可以執行。本案例使用的是Tomcat伺服器。
備註:對於android程式設計師如果想執行伺服器程式碼,按照readme.md文件。然後有不懂的,可以留言。
轉載請註明:androidxx.cn
最後附上下載效果(注意點選按鈕開始和停止後下載進度條的變化,實現斷點續傳)。