ABOUT ME

-

  • Android) ImageURL -> Bitmap 으로 변경하기 feat) HttpURLConnection, Coroutines
    Android 2021. 6. 24. 16:26


    String to Bitmap

    • ※조건※: retrofit이나 외부 라이브러리를 사용하지 않고 통신을 구현해야 합니다. 

     

    HttpURLConnection

    • 안드로이드는 서버와 통신하기 위한 방법으로 HTTP 통신Socket 통신이 있습니다.
    • HTTP 통신은 소켓을 이용한 TCP/IP 통신을 기반으로 수행되지만, 거기에 HTTP 규약이 추가된 형태로 수행됩니다.
    • HTTP 통신URL 접속을 통해 데이터를 읽어오는 방법입니다. 
    • 안드로이드의 특성상 외부 DB에 직접 접근할 수가 없도록 되어 있어 중간 매개체인 WEB을 활용해야 합니다.

     

     

     

    Caution

    • 그런 다음 바로 위의 loadImage 함수를 사용하여 bitmap을 적용하면 아래와 같은 에러가 발생합니다.
    • 이유는 IO 작업을 한 동일한 스레드에서 바로 UI 작업을 할 수가 없기 때문입니다.
    • UI 작업은 오직 메인 스레드에서 해야 합니다.

     

     

    왜 UI 작업은 Main Thread에서 해야 할까?

    • 비동기 작업과 마찬가지로 UI 작업비동기적으로 처리한다면 반드시 동기화 문제에 마주치게 됩니다.
    • 안드로이드는 이러한 UI 자원Main ThreadSub Thread가 동시에 접근하는 문제를 예방하기 위해 병렬로 동작하는 메인 스레드워커 스레드 사이에 핸들러를 두고 UI 작업을 모두 메인 스레드로 전달하도록 합니다.
    • 아래의 (a)처럼 하나의 textView에 메인 스레드와 워커 스레드가 동시에 setText를 하려고 하면 문제가 발생합니다.
    • 그래서 (a)의 문제를 해결하기 위해 (b)처럼 워커 스레드는 핸들러를 사용합니다. 핸들러는 메시지를 전달하는 역할을 합니다. 즉 핸들러는 워커 스레드에서 메시지를 수신해 메인 스레드로 메시지를 전달합니다.

     

    (a)

     

    (b)

     

    Coroutines

    • 그럼 loadImage 함수를 메인 스레드에서 사용하려면 Handler, runOnUiThread(현재 스레드가 UI 스레드라면 UI 자원을 사용하는 행동에 대해서 즉시 실행), Coroutines 등등 여러 방법들이 있습니다.
    • 코루틴의 CoroutineScopewithContext를 사용하면 해결할 수 있습니다. 중단 블록으로 동작하기 때문에 스레드를 차단하지 않습니다.
    • runBlocking을 사용해야 하는지 고민할 수도 있는데, runBlocking은 새로운 코루틴을 실행하고 완료될 때까지 현재 스레드를 차단합니다. 코루틴의 장점은 suspend로 일시 중단인데, runBlocking은 차단이기 때문에 어느 스레드에서 사용하던 코루틴의 장점이 사라지게 되므로 사용하지 않는 게 좋습니다. 사용은 테스트 코드 정도?

     

    전체 코드

    • CoroutineScopeMain Dispatcher를 지정하고 비동기 작업이 있는 부분의 contextwithContext로 바꿔줬습니다.
    • android.graphics.Bitmap 클래스는 생성자를 지원하지 않기 때문에 저장된 이미지를 불러와서 객체로 만들려면 createBitmap() 메소드를 호출해서 비드맵 객체를 생성하거나 BitmapFactory 클래스의 메소드를 사용해서 객체를 생성해야 합니다.
    • 이미 외부에 존재하는 이미지 파일을 가져와서 객체를 생성할 때는 BitmapFactory 클래스를 사용합니다.
    fun loadImage(imageUrl: String): Bitmap? {
            var bitmap: Bitmap? = null
            val connection: HttpURLConnection?
    
            try {
                val url = URL(imageUrl)
                connection = url.openConnection() as HttpURLConnection
    
                connection.requestMethod = "GET" // request 방식 설정
                connection.connectTimeout = 10000 // 10초의 타임아웃
                connection.doOutput = true // OutPutStream으로 데이터를 넘겨주겠다고 설정
                connection.doInput = true // InputStream으로 데이터를 읽겠다는 설정
                connection.useCaches = true // 캐싱 여부
                connection.connect()
    
                val resCode = connection.responseCode // 연결 상태 확인
    
                if (resCode == HttpURLConnection.HTTP_OK) { // 200일때 bitmap으로 변경
                    val input = connection.inputStream
                    bitmap = BitmapFactory.decodeStream(input) // BitmapFactory의 메소드를 통해 InputStream으로부터 Bitmap을 만들어 준다.
                    connection.disconnect()
                }
            } catch (e: IOException) {
                e.printStackTrace()
            }
            return bitmap
        }
    CoroutineScope(Dispatchers.Main).launch {
                    val bitmap = withContext(Dispatchers.IO) {
                        ImageLoader.loadImage(lecture.courseImage)
                    }
                    binding.lectureImage.setImageBitmap(bitmap)
                }

     

    BitmapFactory 주요 메소드

    • decodeByteArray() : 바이트 배열로 넘겨받은 이미지 파일들을 디코딩할때 사용.
    • decodeStream() : InputStream으로 읽어들인 이미지를 Bitmap으로 만들어줌.
    • decodeResource() : Resource폴더에 저장된 그림파일을 Bitmap으로 만들어서 리턴.                              
    • decodeFile() : 로컬에 존재하는 파일을 그대로 읽을때 사용. 파일 경로를 매개인자로 넘겨주면 FileInputStream을 만들어서 decodeStream을 함.

    결론 

    retrofit, Glide 뿐만 아니라, 대부분 많은 라이브러리를 필수로 사용하여 개발을 하고 있는데, 이런 것을 사용하지 않고 구현해야 한다면 굉장히 당황스럽고 불편해진다..

    반응형

    댓글

Designed by Me.