본문 바로가기
Mobile2026년 9월 18일9분 읽기

안드로이드 파편화 대응 — 기능 감지와 폴백으로 버티는 코드

YS
김영삼
조회 156
안드로이드 파편화 대응 — 기능 감지와 폴백으로 버티는 코드

안드로이드 앱이 특정 기기에서만 죽는 경험은 흔하다. 원인은 대개 같다. API가 존재할 거라고 가정했는데 그 기기에는 없는 것. 제조사 커스터마이징, 선행 출시된 기능, 제거된 구성요소 때문에 같은 API 레벨이라도 실제 가용성이 다르다.

해법도 단순하다. 버전이나 제조사로 분기하지 말고 기능의 존재 여부로 분기한다. 그리고 없을 때의 경로를 반드시 만들고, 그 경로도 QA한다.

제조사 이름으로 분기하는 코드를 한 번이라도 써 본 사람은 안다. 그게 어떻게 늘어나는지. 처음엔 한 줄이었다가, 모델명 예외가 붙고, 지역별 변종이 추가되고, 1년 뒤에는 아무도 손대지 못하는 조건문 덩어리가 된다. 그리고 새 기기가 나올 때마다 또 터진다.

분기 방식 비교

방식예시문제
제조사·모델명Build.MANUFACTURER == "..."무한히 늘어남, 새 기기마다 실패
API 레벨SDK_INT >= 34같은 레벨에서도 가용성이 다를 수 있음
기능 존재 확인Class.forName(...), PackageManager.hasSystemFeature()권장 — 실제 상태를 본다
실제 호출 + 예외 처리try/catch로 감싸기보조 수단 — 성능·가독성 주의
API 레벨이 보증하지 않는 것 API 레벨은 "이 SDK로 컴파일된다"를 의미하지, "이 기기에 그 기능이 있다"를 의미하지 않는다. 특정 제조사 기기에만 먼저 들어간 API, AOSP에 없는 API, 제조사가 제거한 구성요소 때문에 가정이 깨진다.

기능 감지 패턴

// 1) 클래스 존재 확인 (가장 일반적)
private val hasNewApi: Boolean by lazy {
    try {
        Class.forName("android.hardware.example.NewManager")
        true
    } catch (e: ClassNotFoundException) {
        false
    }
}

// 2) 시스템 기능 확인 (하드웨어·플랫폼 기능)
val hasNfc = packageManager.hasSystemFeature(PackageManager.FEATURE_NFC)
val hasCamera = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)

// 3) 인텐트 처리 가능 여부 (다른 앱 호출 전 확인)
fun canHandle(intent: Intent): Boolean =
    packageManager.queryIntentActivities(intent, 0).isNotEmpty()

// 4) 사용
if (hasNewApi) {
    useNewPath()
} else {
    useLegacyPath()        // 반드시 존재해야 하고, QA 대상이어야 한다
}

네 번째 항목을 특히 강조하고 싶다. 다른 앱으로 인텐트를 보내기 전에 처리 가능한 앱이 있는지 확인하지 않으면 ActivityNotFoundException으로 죽는다. 기본 브라우저나 이메일 앱이 없는 기기가 실제로 존재한다.

폴백 경로가 진짜 있는가

기능 감지를 넣어도 폴백이 형식적이면 소용없다. "예전 방식으로 돌아간다"고 써 놓고 그 예전 방식을 아무도 테스트하지 않으면, 결국 그 경로에서 죽는다.

폴백 점검
폴백 경로가 실제 기기에서 테스트되는가 (에뮬레이터만으로는 부족)
기능 플래그로 신규 경로를 강제로 끌 수 있는가 — 폴백 테스트의 가장 쉬운 방법
폴백 상태에서 UI가 어색하지 않은가 (버튼만 사라지고 설명이 없는 경우)
폴백 사용 비율을 로깅하는가 — 예상보다 높으면 조사 대상
두 경로가 같은 결과를 내는지 검증하는 테스트가 있는가
// 폴백을 강제로 테스트하기 위한 오버라이드
object FeatureGate {
    var forceLegacy = BuildConfig.DEBUG && devPrefs.forceLegacy

    fun useNewApi(): Boolean = !forceLegacy && hasNewApi
}

// QA 빌드에 개발자 설정 화면을 두고 토글할 수 있게 하면
// 실제 기기에서 폴백 경로를 그대로 검증할 수 있다.

크래시 대시보드에서 기기별로 보기

파편화 문제는 전체 지표로는 안 보인다. 특정 제조사·특정 OS 빌드에서만 터지기 때문에, 전체 크래시율로는 묻힌다. 태깅이 필요하다.

// 크래시 리포트에 진단 키를 붙인다
crashlytics.setCustomKey("manufacturer", Build.MANUFACTURER)
crashlytics.setCustomKey("model", Build.MODEL)
crashlytics.setCustomKey("os_build", Build.DISPLAY)        // 제조사 빌드 지문
crashlytics.setCustomKey("api_level", Build.VERSION.SDK_INT)
crashlytics.setCustomKey("feature_new_api", hasNewApi)
crashlytics.setCustomKey("path", if (useNew) "new" else "legacy")

// 이렇게 해 두면 "특정 빌드 + legacy 경로"에서만 나는 크래시를 필터로 찾을 수 있다.

보안 패치 격차에 대한 대비

기기군에 따라 플랫폼 보안 패치가 수 개월 늦게 전달되는 경우가 있다. 앱 입장에서는 통제할 수 없는 영역이지만, 그 전제 위에서 앱 자체 방어를 유지하는 것은 가능하다.

  • 민감 데이터는 안드로이드 키스토어 기반으로 보호하되, 하드웨어 지원 여부를 확인해 정책을 달리한다.
  • 루팅·변조 탐지는 완벽하지 않지만, 고위험 기능(결제·인증)에서 추가 확인을 요구하는 근거로 쓸 수 있다.
  • 서버 측 검증을 최종 방어선으로 둔다. 클라이언트 검사만으로 결정하지 않는다.
  • 중요한 통신에는 인증서 고정(pinning)을 고려하되, 갱신 실패 시 서비스가 멈추지 않도록 백업 핀과 만료 계획을 함께 둔다.

테스트 매트릭스를 현실적으로 짜기

모든 기기를 테스트할 수는 없다. 우선순위는 사용자 데이터가 정한다.

1
상위 기기 파악
활성 사용자 기준 상위 10~15개 기기가 대개 트래픽의 대부분을 차지한다.
2
제조사 다양성 확보
같은 제조사 기기 5대보다 서로 다른 제조사 3대가 낫다.
3
OS 버전 경계 포함
최소 지원 버전, 가장 많이 쓰는 버전, 최신 버전 세 가지는 반드시.
4
클라우드 기기 팜 활용
보유하지 못한 조합은 원격 기기 서비스로 보완한다.
5
자동화는 스모크 위주
기기별 자동화 유지비가 크므로, 핵심 흐름만 자동화하고 나머지는 수동 점검.

자주 묻는 질문

제조사나 모델명으로 분기해도 되나요?

권하지 않습니다. 새 기기가 나올 때마다 예외가 늘어나고 유지보수가 불가능해집니다. 클래스 존재 확인이나 시스템 기능 조회처럼 실제 가용성을 확인하는 방식으로 분기하세요.

API 레벨만 확인하면 충분하지 않나요?

충분하지 않습니다. 같은 API 레벨이라도 제조사 선행 탑재나 구성요소 제거로 실제 가용성이 다를 수 있습니다. 컴파일 가능 여부와 런타임 존재 여부는 별개의 문제입니다.

폴백 경로는 어떻게 테스트하나요?

기능 플래그로 신규 경로를 강제로 끄는 스위치를 개발자 설정에 두면 실제 기기에서 폴백을 그대로 검증할 수 있습니다. 폴백 사용 비율을 로깅해 예상보다 높은지도 함께 확인하세요.

다른 앱을 호출할 때 주의할 점은요?

인텐트를 보내기 전에 처리 가능한 앱이 있는지 확인해야 합니다. 기본 브라우저나 이메일 앱이 없는 기기가 실제로 존재하며, 확인 없이 호출하면 예외로 앱이 종료됩니다.

기기별 크래시를 어떻게 추적하나요?

크래시 리포트에 제조사, 모델, 제조사 빌드 지문, API 레벨, 그리고 어떤 코드 경로를 탔는지를 커스텀 키로 붙이세요. 전체 지표로는 묻히는 특정 조합의 문제를 필터로 찾아낼 수 있습니다.

테스트 기기는 어떻게 고르나요?

활성 사용자 기준 상위 기기를 우선하되, 같은 제조사를 여러 대 두기보다 서로 다른 제조사를 확보하는 편이 효과적입니다. 최소 지원 버전·최다 사용 버전·최신 버전을 포함하고, 부족한 조합은 클라우드 기기 팜으로 보완하세요.

댓글 0

아직 댓글이 없습니다.
Ctrl+Enter로 등록