Android前端RxJava2+Retrofit2;後端SpringMvc實現圖片上傳
阿新 • • 發佈:2019-01-04
前言
因為前端使用的rxjava+retrofit+mvp的架構進行實現,因此考慮著圖片上傳的功能也直接用rxjava+retrofit去實現,結果在使用過程中,發現始終有問題,圖片上不去;測試了幾天,嘗試更新成rxjava2+retrofit2進行測試,結果成功了;因此這裡做一下記錄;如果不知道rxjava+retrofit使用的朋友可以參考之前寫的部落格淺談Android MVP設計模式(簡單結合RxJava+Retrofit)
Android前段實現
資源引入
在build.gradle檔案中新增rxjava2+retrofit2相關的引入;我這邊是以下相關的配置
compile 'com.squareup.retrofit2:retrofit:2.3.0'
compile 'com.squareup.retrofit2:converter-gson:2.3.0'
compile 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
compile 'com.squareup.okhttp3:okhttp:3.8.1'
compile 'com.squareup.okhttp3:logging-interceptor:3.8.1'
compile "io.reactivex.rxjava2:rxjava:2.1.1"
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
service的編寫
@Multipart
@POST("/imgs/upload")
Observable<String> uploadImgs(@Part List<MultipartBody.Part> file, @Query("data") String data);
這裡傳遞的是list,目的是方便傳輸多張圖片,如果只有一張,往list裡面塞一個值即可
基於Retrofit2自定義Subscriber
在retrofit中我們常用的Subscriber在retrofit2中竟然不見了,替換他的是Observer,為了減少程式碼的調整,我們自定義一個Subscriber,程式碼如下,裡面異常處理相關的程式碼,可以根據自己專案情況酌情調整:
public abstract class Subscriber<T> implements Observer<T> {
@Override
public void onError(Throwable e) {
Throwable throwable = e;
//獲取最根源的異常
while (throwable.getCause() != null) {
e = throwable;
throwable = throwable.getCause();
}
NetWorkException ex;
if (e instanceof HttpException) { //HTTP錯誤
HttpException httpException = (HttpException) e;
NetWorkCodeEnum netWorkCode = NetWorkCodeEnum.getNetWorkCode(httpException.code() + "");
if (null != netWorkCode)
ex = new NetWorkException(netWorkCode.getHttpCode() + "", netWorkCode.getMessage());
else
ex = new NetWorkException(NetWorkCodeEnum.UNKNOWN.getHttpCode() + "", NetWorkCodeEnum.UNKNOWN.getMessage());
} else if (e instanceof ConnectException) {
ex = new NetWorkException(NetWorkCodeEnum.CODE_503.getHttpCode() + "", NetWorkCodeEnum.CODE_503.getMessage());
} else if (e instanceof ResultException) { //伺服器返回的錯誤
ResultException resultException = (ResultException) e;
ex = new NetWorkException(resultException.getmErrorCode(), resultException.getMessage());
} else if (e instanceof JsonParseException
|| e instanceof JSONException
|| e instanceof ParseException) {
ex = new NetWorkException(NetWorkCodeEnum.CODE_1001.getHttpCode() + "", NetWorkCodeEnum.CODE_1001.getMessage());//均視為解析錯誤
} else if (e instanceof NetWorkException) {
ex = (NetWorkException) e;
} else {
if (null != e.getMessage() && e.getMessage().length() > 0) {
ex = new NetWorkException(NetWorkCodeEnum.OTHER.getHttpCode() + "", e.getMessage());//未知錯誤
} else {
ex = new NetWorkException(NetWorkCodeEnum.UNKNOWN.getHttpCode() + "", NetWorkCodeEnum.UNKNOWN.getMessage());//未知錯誤
}
}
onError(ex);
}
/**
* 錯誤回撥
*/
protected abstract void onError(BaseException e);
}
Model層編寫
public void uploadImg(List<File> files) {
MultipartBody.Builder builder = new MultipartBody.Builder()
.setType(MultipartBody.FORM);//表單型別
for (int i = 0; i < files.size(); i++) {
File file = files.get(i);
RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/jpg"), file);
builder.addFormDataPart("file", file.getName(), photoRequestBody);
}
List<MultipartBody.Part> parts = builder.build().parts();
managerService.uploadImgs(parts, "")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<String>() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull String s) {
}
@Override
public void onComplete() {
}
@Override
protected void onError(BaseException e) {
}
});
}
Android呼叫相機拍照
android呼叫相機拍照有2種模式,一種是拍照後獲取壓縮圖;一種是拍照之後將圖片儲存到指定路徑,然後根據程式碼獲取原圖顯示上傳
private static int REQUEST_THUMBNAIL = 10;// 請求縮圖訊號標識
private static int REQUEST_ORIGINAL = 9;// 請求原圖訊號標識
第一種方式
Intent intent1 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); // 啟動相機 startActivityForResult(intent1, REQUEST_THUMBNAIL);
第二種方式
//SD卡路徑 String sdPath = Environment.getExternalStorageDirectory().getPath(); //照片儲存的路徑及名稱 String picPath = sdPath + "/" + "test.png"; Intent intent2 = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); Uri uri = null; if (Build.VERSION.SDK_INT <= 23) { //為拍攝的圖片指定一個儲存的路徑 uri = Uri.fromFile(new File(picPath)); } else { ContentValues contentValues = new ContentValues(1); contentValues.put(MediaStore.Images.Media.DATA, picPath); uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues); } if (null != uri) { intent2.putExtra(MediaStore.EXTRA_OUTPUT, uri); startActivityForResult(intent2, REQUEST_ORIGINAL); }
接受圖片
/** * 返回應用時回撥方法 */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { if (requestCode == REQUEST_THUMBNAIL) {//對應第一種方法 /** * 通過這種方法取出的拍攝會預設壓縮,因為如果相機的畫素比較高拍攝出來的圖會比較高清, * 如果圖太大會造成記憶體溢位(OOM),因此此種方法會預設給圖片進行壓縮 */ Bundle bundle = data.getExtras(); Bitmap bitmap = (Bitmap) bundle.get("data"); img.setImageBitmap(bitmap); //這裡可以再將這個bitmap儲存到sd卡中,然後得帶一個file物件,即可進行上傳;這裡就不寫了 } else if (requestCode == REQUEST_ORIGINAL) {//對應第二種方法 /** * 這種方法是通過記憶體卡的路徑進行讀取圖片,所以的到的圖片是拍攝的原圖 */ FileInputStream fis = null; try { Log.e("sdPath2", picPath); //把圖片轉化為位元組流 fis = new FileInputStream(picPath); //把流轉化圖片 Bitmap bitmap = BitmapFactory.decodeStream(fis); img.setImageBitmap(bitmap); //測試上傳 File file = new File(picPath); List<File> files = new ArrayList<>(); //如果有多張如片,通過系統照片選擇取到相應的路徑,然後新增到list中即可 files.add(file); //這裡為了方便測試,直接只用了model的程式碼進行測試了,實際開發的時候,這裡需要呼叫presenter的程式碼測試 uploadImageModel.uploadImg(files); } catch (FileNotFoundException e) { e.printStackTrace(); } finally { try { fis.close();//關閉流 } catch (IOException e) { e.printStackTrace(); } } } } }
後端實現(SpringMvc)
springmvc.xml配置
<!-- SpringMVC上傳檔案時,需要配置MultipartResolver處理器 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8" /> <!-- 指定所上傳檔案的總大小,單位位元組。注意maxUploadSize屬性的限制不是針對單個檔案,而是所有檔案的容量之和 20M,可根據情況調整 --> <property name="maxUploadSize" value="20971520" /> </bean>
controller層程式碼編寫
@RequestMapping(value = "/upload", method = RequestMethod.POST) @ResponseBody public String imageUpload(@RequestParam("file") MultipartFile[] partFiles, String data) { try { if (null != partFiles && partFiles.length > 0) { for (int i = 0; i < partFiles.length; i++) { MultipartFile partFile = partFiles[i]; //伺服器圖片儲存的路徑 String imgPath = "E:/test/imgs/img" + i + ".png"; File imgFile = new File(imgPath); //將圖片寫到指定的檔案下 partFile.transferTo(imgFile); } return "{\"status\":0,\"desc\":\"成功\"}"; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } return "{\"status\":-1,\"desc\":\"失敗\"}"; }
這裡只是寫了一個範例,後臺如何接受的操作,但是實際開發過程中邏輯相關的程式碼是應該在service中去寫;為了直觀的達到效果;這裡就直接寫在controller裡面了。
可能存在的問題
- 檔案大小
如果說檔案太大;可以在stringmvc.xml中將maxUploadSize屬性調大一點 - nginx檔案大小
nginx預設請求最大的檔案包為2M,如果上傳的圖片過大,那麼nginx就會報“413 Request Entity Too Large”錯誤;原因就是因為圖片大於2M了,可以在ngnix.conf配置檔案中的http{}下新增或者修改client_max_body_size 20m配置,即允許最大的包為20M,大小可根據情況調整;使用nginx -t測試配置是否符合要求;然後通過nginx -s reload重啟nginx即可
到這裡,單張圖片和多張圖片即可完成上傳了。