ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Android Studio - Retrofit @Multipart 이미지 & @PartMap으로 데이터 전송하기
    Android Studio 2021. 4. 5. 20:17

    졸업작품은 이제 끝났으니..! 시간이 빈 김에 까먹지 않게끔 후딱! 쓰는 이미지 전송하기...
    이미지를 전송할 땐 Multipart로 써야하는데, 이미지 뿐 아니라 다른 정보들까지 전송을 함께 해야하기 때문에 대체 뭘로 전송해야할지 감을 못 잡았다. 그리고 아래에도 서술하겠지만, 하라는거 다 했고, 서버로부터의 응답도 200인데 DB에 계속 이미지가 안 들어가길래 그걸로 한참 날렸다. 결과적으로 클라이언트 쪽에서 압축을 안하면 DB에 안 들어간다...^^ 너무 어이 없고 ㅋㅋㅋ 허무해서 눈물났었다. 쨌든 이미지 전송때문에 너무 고생했어서, 누군가에게 도움이 되었으면 하는 마음 + 다시는 이 고생 안 하리라는 다짐 겸 쓰는 포스팅이다.


    우선 백엔드 담당 팀원이 올려준 위키의 RequestBody를 가져와봤다. 이미지와 다른 데이터(String, double등등) 들까지 보내야하는 상황이다.

    저기서 이미지는 form-data(폼데이터)가 확실한데, 나머지는 대체 어떻게 보내는거지?? 하고 한참 헤맸다. 결론부터 말하자면, 사진의 나머지 데이터들(city, country등)도 request body로 만들어서 보내줘야한다.


    우선 API 인터페이스 파일은 아래와 같이 작성했다.

    @Multipart 
    @POST("/record") 
    Call<EditResponse> userEdit(@Header("token")String token, @Part MultipartBody.Part postImg, @PartMap HashMap<String, RequestBody> data);

    RequestHeader에 있던 token은 그대로 @Header 어노테이션으로 넣어준다.
    img파일은 MultipartBody.Part로 한번더 감싸서 넣어준다.
    그리고 그 외 나머지 데이터들은 @PartMap으로 넣어주었다. (HashMap자료구조 사용)


    그리고 이미지 제외한 나머지 데이터들은 RequestBody로 만들어서 map에 넣어주었다. MediaType은 그냥 text/plain으로 해주면 된다.

    RequestBody town = RequestBody.create(MediaType.parse("text/plain"),city); 
    RequestBody nation = RequestBody.create(MediaType.parse("text/plain"),country); 
    RequestBody texts = RequestBody.create(MediaType.parse("text/plain"),text); 
    RequestBody lats = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(lat)); 
    RequestBody lons = RequestBody.create(MediaType.parse("text/plain"),String.valueOf(lon)); 
    RequestBody useridx = RequestBody.create(MediaType.parse("text/plain"),String.valueOf(userIdx)); 
    RequestBody date = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(resetDate)); 
    RequestBody locate = RequestBody.create(MediaType.parse("text/plain"), detailAddress); 
    map.put("city", town); 
    map.put("country", nation); 
    map.put("text", texts); 
    map.put("lattitude", lats); 
    map.put("longtitude", lons); 
    map.put("userIdx", useridx); 
    map.put("date", date); 
    map.put("location", locate);

    근데 여기서 주의사항이 있는데, RequestBody에는 int, double같은 숫자형 데이터가 안 들어간다. 그냥 lat(double형) 넣으려하면 빨간줄 쳐지면서 안 된다고 뜬다. 그래서 String.valueOf()로 변환해서 넣어줘야 한다.

    RequestBody lats = RequestBody.create(MediaType.parse("text/plain"), String.valueOf(lat));

    그러고 나서 map에 넣어야하는데, 이때 key값은 서버 위키에 써있는 값과 동일하게 써줘야한다. (위의 위키 캡처에 써있는 key값 참고)

    map.put("서버측이 써준 키값", RequestBody);

    map은 위 방법대로 생성해 주고, 다음은 이미지 처리를 위한 코드이다.

    //filepath는 String 변수로 갤러리에서 이미지를 가져올 때 photoUri.getPath()를 통해 받아온다. 
    File file = new File(filepath); 
    InputStream inputStream = null; 
    try { 
    	inputStream = getContext().getContentResolver().openInputStream(photoUri); 
    }catch(IOException e) { 
    	e.printStackTrace(); 
    } 
    Bitmap bitmap = BitmapFactory.decodeStream(inputStream); 
    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); 
    bitmap.compress(Bitmap.CompressFormat.JPEG, 20, byteArrayOutputStream); 
    RequestBody requestBody = RequestBody.create(MediaType.parse("image/jpg"), byteArrayOutputStream.toByteArray()); 
    MultipartBody.Part uploadFile = MultipartBody.Part.createFormData("postImg", file.getName() ,requestBody);

    bitmap이미지를 jpeg포맷으로 20%압축하여 byteArrayOutputStream에 저장한다. 이를 RequestBody로 만들어주고, 한번 더 MultipartBody.Part로 감싸주었다.
    createFormData의 두 번째 변수에 file의 이름이 필요해서 file객체를 맨 위의 줄에서 만들었다.
    createFormData의 첫 번째 변수 postImg는 서버에 전달할 이름이다. (서버에서 붙여준 변수 이름대로 작성)

    * 압축하는 이유?
    압축을 처음에 진행을 안하고 했었는데 서버로부터의 응답은 200 OK 인데 DB에 안 들어가는 일이 있었다. 서버 측에서는 postman으로 넣었을 때는 된다고 해서 왜 안돼지???하다가 압축하니까 바로 들어갔었다. 혹시 다 맞춰썼는데 DB에 안 들어간다면 이 이유일수도 있다.


    마지막으로, 최종 서버와 통신하는 코드는 다음과 같다.

    serviceApi.userEdit(token, uploadFile, map).enqueue(new Callback<EditResponse>() { 
    	@Override 
        public void onResponse(Call<EditResponse> call, Response<EditResponse> response) 
    	{ 
        	EditResponse result = response.body(); 
            if(result.getStatus() == 200) { 
            	Toast.makeText(save_button.getContext(),"저장이 완료되었습니다.",Toast.LENGTH_SHORT).show(); 
            } 
        } 
        @Override 
        public void onFailure(Call<EditResponse> call, Throwable t) 
        { 
        	Toast.makeText(save_button.getContext(), "통신에러",Toast.LENGTH_SHORT).show(); 
        } 
     }
    );


    막상 포스팅하니까 별로 내용이 많진 않은데, 정말 며칠동안 헤맸다. 다시는 헤매지 않기 위해 작성하는 글 겸, 혹시 서버로부터 200떴는데 왜 DB에 안들어가는지 구글링하는 분들을 위해 쓴다. 도움이 되었으면 좋겠다... :D

Designed by Tistory.