본문 바로가기
Android/기타

안드로이드 리플렉션 이슈

by 안스 인민군 2022. 12. 10.

안드로이드 개념을 공부하던 중 직렬화라는 개념에 대해서 공부를 했다.  직렬화란 메모리에 올라가 있는 정보를 byte 단위의 코드로 나열하는 것이다. 이를 통해서 객체와 같은 정보를 전달할 수 있게 하는 것이다.

직렬화를 가능하게 하는 방법 중에는 Serializable과 Parcelable을 구현하는 2가지 방법이 존재한다. 그 중 Serializable은 구현은 상당히 쉬우나 속도가 느리다는 단점이 있다.

속도가 느린 이유는 내부적으로 Reflection을 사용하기 때문에 필요없는 쓰레기 객체들을 만들어내고 이를 제거하기 위해 GC가 동작해서 비용이 발생하게 된다.

그렇다면 여기서 말하는 Reflection은 무엇일까??

Reflection

객체를 통해 클래스의 정보를 분석해 내는 프로그래밍 기법을 말한다.
리플렉션은 구체적인 클래스 타입을 알지 못해도 컴파일된 바이트 코드를 통해 그 클래스의 메소드, 타입, 변수들을 접근할 수 있도록 해주는 자바 API를 말한다. 이게 무슨 의미일까?

public class Car{
    public void drive(){
        // Do anything.
    }
}

public class Main{
    public static void main(String[] args){
        Object car = new Car();
        car.drive(); // 컴파일 에러
    }
}

위에 코드 블록에서 컴파일 에러가 나는 이유는 모든 클래스의 조상 클래스인 Object라는 타입으로 Car 클래스의 인스턴스를 담을 수는 있지만 사용 가능한 메소드는 Object의 메소드와 변수들 뿐이기 때문이다. 그러니까 car 인스턴스의 메소드는 사용하지 못하는 것이다.

 

이런 식으로 구체적인 타입의 클래스를 모를 때 사용하는게 리플렉션이다. 그렇다면 또 의문이 생긴다.

내가 만드는 프로그램의 코드 흐름인데, 내가 사용할 클래스의 타입과 이름을 모르는 경우가 있을까?

당연하게도 코드를 작성할 시점에는 어떤 타입의 클래스를 사용할지 모르는 경우가 있다. 내가 지금 작성하는 코드가 나중에 어떤 기능이 추가되어서 어떻게 필요에 따라 사용될지 모르는 경우 같은 것이다. 이럴 때는 실행할 시점, 그러니까 런타임에 지금 실행되고 있는 클래스를 가져와서 실행을 해야 되는 것이다.

즉, 설계할 때는 사용될 클래스가 어떤 타입인지 모르지만 리플렉션을 이용해서 코드를 일단 작성하고 실행 시점에 확인해서 활용할 수 있도록 하는 메커니즘이다.

 

즉, 설계할 때는 사용될 클래스가 어떤 타입인지 모르지만 리플렉션을 이용해서 코드를 일단 작성하고 실행 시점에 확인해서 활용할 수 있도록 하는 메커니즘이다.

 

그렇다면 어떻게 이게 가능한 것일까??

 

자바 클래스 파일은 바이트 코드로 컴파일 되어 static한 영역에 위치하게 된다. 때문에 클래스 이름만 알고 있다면 언제든, 이 영역을 뒤져서 클래스에 대한 정보를 가져올 수 있는 것이다. 아래는 가져올 수 있는 정보들이다.

  • ClassName
  • Clas Modifiers(public, private, synchronized 등)
  • Package Info
  • Superclass
  • Implemented Interfaces
  • Constructors
  • MethodsFields
  • Annotations

그런데 Reflection은 속도를 저하시키기에 구글에서는 권장을 하지 않는다.

 

fun setCursorColor(@ColorInt color: Int) {
        try {
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.Q) {
                val cursorDrawable = editText.textCursorDrawable
                cursorDrawable?.setTint(color)
            } else {
                var field: Field = TextView::class.java.getDeclaredField("mCursorDrawableRes")
                field.isAccessible = true
                val drawableResId: Int = field.getInt(editText)

                field = TextView::class.java.getDeclaredField("mEditor")
                field.isAccessible = true
                val editor: Any = field.get(editText)

                val drawable = ContextCompat.getDrawable(context, drawableResId)
                drawable?.setTint(color)

                field = editor.javaClass.getDeclaredField("mCursorDrawable")
                field.isAccessible = true
                field.set(editor, drawable)
            }

        } catch (ignored: Exception) {
        }
    }