Read byte array from binary data of retrofit response

현재 서버에서 이미지를 binary data로 내려주면 클라이언트에서는 데이터를 읽은 후 byte array로 변환하고 있다. 이전에는 DEPRECATEDvolley 라이브러리를 이용해 Request를 커스텀하게 수정해서 사용했다.

public class ByteArrayRequest extends Request<byte[]> {
  private Response.Listener<byte[]> listener;
  …
  @Override
  protected Response<byte[]> parseNetworkResponse(NetworkResponse response) {
     return Response.success(response.data, HttpHeaderParser.parseCacheHeaders(response));
}

그리고 요즘에는 volley에서 Retrofit + RxJava 라이브러리로 변경하는 작업을 진행하는 중이다.

@GET(“books”)
Observable<Result<ResponseBody>> getImage(@Query(“id”) String id);

Retrofit으로 byte[]로 데이터 가져오기

Retrofit은 프로덕션에 적용한 지 얼마 안되었지만 Result에서 result.response()를 통해 얻은 객체 Response에는 크게 body()errorBody() 메소드가 있다. 여기에서 body는 ResponseBody가 되고 errorBody의 경우, 예를 들어 서버에서 404 코드와 에러 메시지가 포함된 에러 JSON 객체를 내려주면 String으로 받을 수 있다.

ResponseBody

public final InputStream byteStream() {
  return source().inputStream();
}

byte[] bytes() { 
  … 
  BufferedSource source = source();
  byte[] bytes;
  try {
    bytes = source.readByteArray();
  ...
}

String string() { 
  …
  BufferedSource source = source();
  try {
    Charset charset = Util.bomAwareCharset(source, charset());
    return source.readString(charset); 
  …
}

ReponseBody에서 InputStream을 가져와서 버퍼로 읽는 방법이나 byte[] 또는 String으로 서버로 받은 body를 가져올 수 있는데 String으로 binary 데이터를 읽게 되면 어떻게 될까?

String: ����JFIF��C��C��� ��    
�����+�}Yϭ�F39M>���������>���;��ˋ��uXʽ�w�ڤx\-[2g��k�S���H���m
[�V?[_W����#��v��}6�[��F�F�%����n�...

정상적인 이미지를 만들 수 없으므로 byte[]로 스트림에 있는 데이터를 가져와야 한다. 참고로 body.bytes()는 구현 코드를 살펴보면 body.source().readByteArray() 와 같은 동작이다.

body.source().readByteArray() : [-1, -40, -1, -32, 0, 16, 74, 70, 73, 70, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, -1, -37, 0, 67, 0, 16, 11, 12, 14, 12, 10, 16, 14, 13, 14, 18, 17, 16, 19, 24, 40, 26, 24, 2 … ]

이미지 Bitmap으로 변환하기

BitmapFactory의 decodeStream

InputStream input = body.byteStream();
Bitmap bitmap = BitmapFactory.decodeStream(input);

BitmapFactory의 decodeByteArray

Bitmap bitmap = BitmapFactory.decodeByteArray(body.bytes(), 0, body.bytes().length);

InputStream이나 byte array로 사용해서 쉽게 비트맵으로 이미지를 가져올 수 있다. 그리고 ResponseBody에 있는 BufferedSource에서 스트림으로 데이터를 읽기 때문에 주의해야 한다.

An elegant part of the java.io design is how streams can be layered for transformations like encryption and compression. Okio includes its own stream types called Source and Sink that work like InputStream and OutputStream – okio wiki 중에서

사실 위의 2번째 코드에서는 정상적으로 이미지를 그릴 수 없다.

byte[] bytes = body.bytes();
bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

그 이유는 body.byte()를 호출하게 되면 스트림에 있는 데이터를 모두 읽기 때문에 다음 두 번째 호출 body.byte().length는 0이 되어 정상적인 Bitmap이 생성되지 않는다.