해당 글에서는 AOSP 코드와 Material 코드를 보며 어떤식으로 에러를 수정하였는지 전달하고, 혹시나 AOSP 코드 등을 확인해야한다면 어떤식으로 접근할지 도움을 드리기 위해 작성하는 글입니다.
배포를 하기 위해 새로 개발된 Feature 들을 머지하고 QA 를 하던 도중 아래와 같은 에러가 발생하였습니다.
MaterialCardView 에서 발생하고 있었고, 특징으로는 Android 9 이하의 버전들에서 발생하고 있었습니다.
먼저 사용하는 쪽의 코드를 간략하게 확인하였습니다. (checked 에 들어가는 값은 viewModel 을 통해 넣어주고 있고 회사 코드가 드러날 수 있어 제거하였습니다) 이전에 문제 없이 잘 동작했기 때문에 한눈에 어떤 것이 문제 인지 알수가 없었습니다.
또한 코드가 변경된 부분은 없었기 때문에, 마침 미팅시간에 공유 드렸고 다른 팀원께서 Material component library 1.4.0 에서 1.5.0 으로 버전업(엄청난 힌트) 한것을 알게 되었습니다.
error stacktrace 가 다행히 잘 남아 있었기 때문에, 차근차근 보기로 마음 먹었습니다.
먼저 에러를 만들어내는데 결정적인 역할을 한 Material Library 1.5.0 버전의 MaterialCardViewHelper 의 getClickableForeground 메서드에서 어디 부분에서 null 이 들어와서 결과적으로 LayerDrawable 의 초기화 코드에서 setCallback 메서드를 호출하는 부분에서 크래시가 발생하는지 확인하였습니다. (* Material library 가 변경된 것을 알고 있었기 때문에 Material 과 관련있는 코드에서 크래시에 영향이 있을 것이라 생각했습니다)
그리고 디버깅을 통해 찾을 수 있었습니다. checkedIcon 에 null 이 들어가고 있었습니다. 그리고 위의 XML 중 MaterialCardView 의 app:checkedIcon="@null" 이 영향을 주고 있다는 의심을 하기 시작했습니다.
하지만 이전에 잘 동작했던 코드이기 때문에 Material Library 1.4.0 버전에서는 app:checkedIcon="@null" 의 값이 어떤식으로 동작하여 checkIcon 이 null 로 넘어가지 않는지 확인이 필요했습니다.
그리고 곧 찾을 수 있었습니다. createCheckedIconLayer() 메서드에서 Not-Null 한 drawable 을 반환하고 있었습니다.
createCheckedIconLayer() 내부 구현을 잠시 확인해보겠습니다.
내부에서 StateListDrawable 객체를 만들고 xml 에서 checkedIcon 을 넣어준 것이 있다면, 체크 상태의 checkedIcon 를 넣어주고 있습니다.
일단은 Material Library 1.5.0 을 사용하면 왜 크래시가 발생했는지에 대한 원인을 찾았습니다.
하지만 아직 해결해야할 부분이 남아있습니다.
왜? Android 9 이하(API Level 28) 버전에서만 에러가 생겼는지 확인이 필요했습니다. Material Library 1.5.0 버전을 사용해도 Android 10 이상에서는 정상적으로 동작하고 있었습니다.
따라서 Android 10 이상 코드에서 LayerDrawable 의 setCallback 하는 부분은 확인해봤습니다.
다행히 어렵지 않게 찾을 수 있었습니다. AOSP 코드에서 child 에 대한 null 체크를 하고 있었네요. 따라서 위에서 LayerDrawable 에 들어간 checkIcon 이 null 일 때는 위의 조건에서 걸리지게 됩니다.
그러면 문제가 생긴 Android 9(API Level 28) 에서의 LayerDrawable 을 확인해보겠습니다.
같은 클래스의 생성자임에도 불구하고, 정말로 null 을 체크하는 부분이 없습니다. 결과적으로 layer[2].setCallback 하는 순간 layer[2] 는 비어있는 checkedIcon 이 들어있게 되고 nullptr 일 발생하게 된 것입니다.
이제 모든 원인을 찾았으니 수정만 하면 됩니다.
원인을 명확하게 알았으니, 수정할 수 있는 방법도 명확하게 알게되었습니다.
그저 checkIcon 을 넣어주면 됩니다. 여기서 checkIcon 을 따로 사용하지 않는데 넣어야한다면 간단하게 app:checkedIcon="@android:color/transparent" 정도로 처리해주면 됩니다.
빌드하니 정상동작하고 QA 도 통과 되었습니다. :)
하지만 이런식으로 사용하지도 않는 checkIcon 을 넣어줘야 한다는 의미가 결과적으로 올바른 방법이 아닌 workaround 정도로 생각되었습니다. 따라서 해당 크래시는 버그라는 생각이 들어 Material library Github 에 들어가서 버그나 수정 로그로 올라온 것이 있는지 살펴봤습니다.
신기하게도 있었습니다!!
https://github.com/material-components/material-components-android/issues/2565
Android 9 이하에서 발생하는 버그였고, 아래 처럼 현재 최신 버전인 1.6.0-alpha03 에서 수정이 되었습니다. (alpha 를 싫어하시는 분은 일단은 transparent 를 넣어주는 workaround 방식을 사용해야할 것 같습니다)
어떤식으로 해결했는지 잠시 살펴보면, 초기화 코드에서 P 이하라면 new ColorDrawable 객체를 만들어서 checkIcon 이 null 인 경우 넣어주는 것을 볼 수 있습니다. (workaround 로 처리한 transparent 를 넣어주는 것과 큰 차이는 없네요)
요약 하자면,
Material Library 1.5.0 의 MaterialCardViewHelper 클래스가, 테스트 코드가 덜 작성된 것인지 하위 호환성에 대한 처리가 올바르게 되지 않았다고 볼수 있습니다. 호환성을 유지하는 것이 얼마나 어려운 작업인지 해당 크래시를 만나고 수정하며 몸소 깨닫았습니다.
사용 하는 라이브러리 뿐만 아니라 Android api level 이 몇인지에 따라서도 이런식으로 크래시를 만날 수 있으니 언제나 긴장하고 있어야 겠습니다.
그럼 20000
'Android > Today I Learned' 카테고리의 다른 글
주관적인 Compose 사용 후기 (4) | 2022.06.11 |
---|---|
서버 디펜던시 없이 네트워크 작업 캐시 구현하기(feat.OkHttp) (0) | 2022.04.06 |
Compose 버튼 사이즈 관련 Tips (1) | 2022.03.01 |
Compose Navigation - viewmodel 사용할 때 주의할 점 (0) | 2021.12.25 |
Android Font 고군분투기 (1) | 2021.12.06 |