그동안 하이브리드 앱을 만들다가 작년에 영상처리를 해주는 앱을 만들게 되었는 데, 이때 영상처리 파트 쪽에서 고화질의 이미지를 넘겨주길 원했습니다. 문제는 앱의 프로세스 상 한번에 수십장을 처리해야하는 부분까지 존재했다는 것이었죠. 상황이 이러하다보니 이론이 소홀히 했던 저는 OutofMemoryError를 자주 접했습니다. 따라서 저와같은 상황에 처한 주니어 개발자분들을 위해 제가 극복한 방법을 리마인드겸 모두 적어볼까합니다.



1. Bitmap.recycle의 호출 시점


제가 넘겨주는 이미지의 해상도는 디바이스의 성능 및 지원 해상도에 따라 최소 1920x1080에서 최대 4032x2268 정도였습니다. 당연히 최대 해상도를 가진 이미지의 용량이 어마어마했고 Asynctask로 다섯장만 돌려도 OutofMemoryError가 뜨며 죽는 현상이 일어났죠. 이를 해결하기 위해 열심히 구글링해본결과 onDestroy나 onPostExecute 등 더이상 비트맵을 사용하지 않는 구간에서 bitmap.recycle을 하라는 것이었습니다. 


여러장을 돌리는 저에겐 당연스럽게도 onPostExecute에 들어가기도 전에 죽어버렸고, Bitmap.recycle을 여기저기 들쑤시며 넣어보다가 recycle 후에 bitmap을 사용하려해서 생기는 에러에 맞닥들이게 되었습니다.



 

java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap




@Override
protected void doInBackground(Void... params) {
   Bitmap bitmap = null;
   for (int i = 0; i < imageItems.size(); i++) {
       bitmap = BitmapFactory.decodeFile(imageItems.get(i).getFilePath());
       Bitmap result = jniLib.jniFunction(bitmap);

       bitmapSaveFile(filePath, bitmap, 100);
       
       if (!bitmap.isRecycled()) {
          bitmap.recycle();
       }
   }
}


얼마나 멍청하게 짰는 지 아시는 분은 다 아시겠지만... 학창시절에 이론공부를 소홀히 하고 비전공으로 대학원을 진학하면서 2년을 쉰 저로선 파악이 제대로 안됐었습니다. (이론 공부 열심히 합시다;;)


무엇이 문제인지 제대로 파악이 안되는 저와 같은 처지의(?) 주니어 개발자분들을 위해 설명해드리자면, 어느 변수든 마찬가지지만 초기화를 해주지 않으면 새로운 변수값이 계속 들어간다고 해도 주소값은 계속 남아있습니다. 


따라서 Bitmap 선언을 for문 밖에서 해버리면 처음 프로세스가 진행될 때 recycle을 해버린 상태에서 다시 bitmap을 사용하려고 해서 위와 같은 에러와 함께 앱이 죽어버리죠. 일반적인 변수들은 모두 용량이 적기 때문에 잘 모르고 지나칠 수도 있지만 bitmap은 워낙 용량이 크기 때문에 제대로 초기화를 안해주면 Recycle의 여부를 떠나서 메모리가 쌓이므로 결국 죽어버립니다. 


이를 고려하여 수정한 코드는 아래와 같습니다.


@Override
protected void doInBackground(Void... params) {
   for (int i = 0; i < imageItems.size(); i++) {
       Bitmap bitmap;
       bitmap = BitmapFactory.decodeFile(imageItems.get(i).getFilePath());
       Bitmap result = jniLib.jniFunction(bitmap);

       bitmapSaveFile(filePath, bitmap, 100);
       
       if (!bitmap.isRecycled()) {
          bitmap.recycle();
       }
   }
}


.. 참 쉽죠? Bitmap 선언만 for문 안으로 내렸습니다. 이렇게 하면 한 번 돌릴 때마다 recycle을 실행하더라도 bitmap을 초기화 하기 때문에 다시 recycle을 실행해도 기존 bitmap을 재사용하지 않습니다. 따라서 메모리가 쌓이는 일은 더이상 발생하지 않기 때문에 이제는 bitmap 크기 따위 신경 안쓰고 마음껏 돌리셔도 됩니다!


하나 추가하자면, 모든 bitmap 관련 프로세스들은 외장메모리에 파일로 저장 후 주소값만 넘겨서 다시 불러오는 방식으로 작업해야합니다. bitmap을 그대로 넘겨버리면 아주 쉽게 OutofMemoryError를 접하실 수 있을겁니다. 에러가 안뜨더라도 디바이스가 무거워지니 이왕이면 파일주소로 주고받아서 관리해주시는 게 좋습니다.


다음엔 Heap으로 인해 발생했던 메모리 이슈와 그 원인에 대해 알아보겠습니다.



- 저도 주니어 개발자이고 경험을 통해 작성하는 것이기 때문에 틀린 부분이 있을 수 있습니다. 만약 다른 의견이 있으시거나 틀린 부분이 있으면 댓글로 남겨주시면 감사하겠습니다.


- 오늘도 주니어 개발자분들 가시는 길에 에러가 없길 빕니다.


+ Recent posts