OkHttp基本使用(三)上傳下載功能實現
- 前言
本篇將使用OkHttp實現檔案的上傳和下載,以及下載實現斷點續傳功能。因為是基本的使用,此係列文章主要是以大家會用為主而寫的。當然,只要會用了,後面的優化、封裝等等就不難了。
- 下載
使用OkHttp完成下載功能,實現斷點續傳,並附帶進度條顯示下載進度。
檔案下載的互動過程:
下載的流程:
- 檔案下載的程式碼:
- public class DownloadActivity extends AppCompatActivity {
- private ProgressBar mProgressBar;
- //準備下載
- public static final int BEGIN = 0;
- //正在下載
- public static final int DOWNLOADING = 1;
- //結束下載
- public static final int END = 2;
- //下載的進度
- private static int progress;
- //是否停止下載
- private boolean cancel ;
- OkHttpClient okHttpClient = new OkHttpClient();
- MyHandler mHandler = new MyHandler(this);
- private ImageView mShowImage;
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_download);
- mProgressBar = (ProgressBar) findViewById(R.id.down_progress_bar);
- mShowImage = (ImageView) findViewById(R.id.down_image);
- }
- public void click2(View view) {
- cancel = true;
- }
- public void click(View view) {
- cancel = false;
- new Thread(new Runnable() {
- @Override
- public void run() {
- //例項化Builder物件
- Request.Builder builder = new Request.Builder();
- //設定Url
- builder.url(Config.IMAGE_URL);
- //獲取已經下載的大小
- int size = baos.size();
- //size表示已經下載的大小。如果不為0,則進行斷點續傳。
- if (size > 0) {
- //設定斷點續傳的開始位置,格式bytes=123456-
- builder.header("Range", "bytes=" + size + "-");
- //設定ProgressBar的當前進度從停止位置開始
- progress = size;
- }
- //建立Request物件
- Request request = builder.build();
- try {
- //執行下載請求,並獲得Response物件
- Response response = okHttpClient.newCall(request).execute();
- //請求成功
- if (response.isSuccessful()) {
- //從Response物件中獲取輸入流物件
- InputStream inputStream = response.body().byteStream();
- //size==0表示第一次下載,非斷點續傳
- if (size == 0) {
- //獲取檔案的大小
- int contentLength = (int) response.body().contentLength();
- //將檔案總大小通過Handler傳遞到UI執行緒,設定ProgressBar的總進度值
- mHandler.obtainMessage(BEGIN,contentLength,0).sendToTarget();
- }
- int len = 0;
- byte[] buffer = new byte[1024];
- //迴圈讀取檔案流,開始下載
- while((len = inputStream.read(buffer)) != -1) {
- if (cancel) {
- //如果點選了停止按鈕,cancel為true。則結束迴圈
- break;
- }
- //將流寫入快取
- baos.write(buffer,0,len);
- baos.flush();
- //傳送下載進度
- mHandler.obtainMessage(DOWNLOADING,len,0).sendToTarget();
- }
- //下載完成,結束請求,關閉body
- response.body().close();
- //將位元組轉成Bitmap物件
- byte[] bytes = baos.toByteArray();
- Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
- //下載完成通知更新試圖
- mHandler.obtainMessage(END,bitmap).sendToTarget();
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }).start();
- }
- static class MyHandler extends Handler {
- private WeakReference<DownloadActivity> activityWeakReference;
- public MyHandler(DownloadActivity activity) {
- this.activityWeakReference = new WeakReference<DownloadActivity>(activity);
- }
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case BEGIN:
- activityWeakReference.get().mProgressBar.setMax(msg.arg1);
- break;
- case DOWNLOADING:
- progress += msg.arg1;
- activityWeakReference.get().mProgressBar.setProgress(progress);
- break;
- case END:
- progress = 0;
- activityWeakReference.get().mShowImage.setImageBitmap((Bitmap)msg.obj);
- break;
- }
- }
- }
- }
複製程式碼
以上是檔案下的程式碼。實現了斷點續傳,其中能進行斷點續傳的關鍵程式碼為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
最後附上下載效果(注意點選按鈕開始和停止後下載進度條的變化,實現斷點續傳)。