ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Android 14(API 34)에서 USB Serial 장치 연결 시 권한에 의한 Crash 트러블슈팅
    Java-Kotlin/Android 2026. 2. 11. 23:29
    반응형

    2025.12.03 - [Java-Kotlin/Android] - Android에서 USB Serial 장치 연결 시 USB 권한 문제 해결기

     

    Android에서 USB Serial 장치 연결 시 USB 권한 문제 해결기

    최근 안드로이드 프로젝트에서 POS장비에 USB Serial 포트 통신 기능을 구현하는 중에 예상치 못한 권한 문제를 마주쳤습니다.저는 이미 AndroidManifest.xml에 USB 관련 Permission을 다 줘놨는데도 권한 문

    parkstate.tistory.com

     

    저는 이전에 Android에서 USB Serial 장치를 연결 한적이 있습니다. 

     

    해당 코드를 이용하여 RFID Reader를 잘 사용 하고 있었습니다. 그러나 키오스크 타겟 장비가 기존보다 좀 더 높은 Android 버전을 가지게 되어 테스트 해보니 Reader를 연결할 때 크래시가 발생하며 앱이 죽는 이슈가 발생했습니다.

     

    이 글은 그 해결 과정을 기록한 트러블슈팅기 입니다.

     


     

     

    배경

    POS 장비와 연동되는 RFID Reader를 usb-serial-for-android 라이브러리를 사용해 연결하였습니다.

    기존의 Android target 버전은 Android 12 였습니다. (API 31)

     

    새로운 장비가 타겟 장비가되어 Android target 버전을 Android 14 (API 34)까지 올렸습니다.

    Android 버전을 올리자 RFID Reader를 연결시 앱이 죽는 이슈가 발생했습니다.

     

     

    Trouble 1: PendingIntent security 정책 위반

    앱을 실행하고 RFID Reader를 연결하려 권한을 요청하면 앱이 죽는다는 사실을 발견했습니다.

     

    Error Log

    java.lang.IllegalArgumentException: org.imtsoft.pos.android: Targeting U+ (version 34 and above) disallows creating or retrieving a PendingIntent with FLAG_MUTABLE, an implicit Intent within and without FLAG_NO_CREATE and FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT for security reasons. ...

     

     

    원인 분석

    Android 12 버전부터 PendingIntent 생성시 Mutable, Immutable 등의 가변성을 명시해주었어야 했다는 것을 알고 있었습니다.

    하지만 Android 14 버전부터는 보안 정책이 더 강화되었습니다.

     

    • Implicit Intent(암시 인텐트)를 사용 하는 PendingIntent는 반드시 FLAG_IMMUTABLE 플래그로 설정해야 합니다.

    저의 코드는 ACTION_USB_PERMISSION 만 넣은 Implicit Intent였는데, 플래그를 FLAG_MUTABLE 로 설정해놓았었습니다. 

     

    그래서 보안 정책 위반으로 앱에서 크래시를 띄운것이 앱이 계속 죽는 원인이었습니다.

     

     

    해결

    USB 권한 요청 Intent는 내용이 수정될 일이 없으므로, FLAG_MUTABLE  -> FLAG_IMMUTABLE 로 변경하여 PendingIntent 정책 위반으로 인한 크래시를 해결하였습니다.

     

     

     


     

     

    Trouble 2: 권한 비동기 처리 미흡

    Trouble 1을 해결하니 권한 팝업이 뜨긴 했습니다. 그러나 허용 버튼을 누르기 전에 앱이 죽었습니다.

     

    Error Log

    java.lang.IllegalArgumentException: Connection is null at com.hoho.android.usbserial.driver.CommonUsbSerialPort.open(CommonUsbSerialPort.java:119) ...

     

     

    원인 분석

    앱이 죽는 원인은 코드의 실행 순서였습니다.

     

    requestPermission은 비동기로 동작합니다. 그러니까, 사용자가 권한 허용 팝업에서 허용 버튼을 누르기 전까지 코드가 멈춰있는게 아니고, 다음 줄을 바로 실행합니다.

     

    문제가 된 코드 흐름

    1. connect() 함수 호출
    2. 권한 확인 -> 없음 -> requestPermission() 호출
    3. 팝업 뜸
    4. (사용자가 권한 허용 버튼을 누르기 전에) 바로 그 다음 줄 코드 실행
    5. manager.openDevice(device) 호출 -> 권한이 없어서 null 반환
    6. port.open(null) 실행 -> 앱 죽음

     

    해결

    연결 로직을 별도 함수로 분리하고, 권한이 있을 때와 권한을 획득했을 때의 Callback만 해당 함수를 호출하도록 구조를 수정했습니다.

     

     

     


     

     

    최종 코드

    이 문제들을 해결하고, 안정적으로 동작하는 최종 코드는 이렇게 되었습니다.

    /**
     * RFID Reader 연결 어댑터
     */
    fun connect() {
        // ... (드라이버 찾기 로직) ...
    
        rfidDrivers.forEach { driver ->
            if (manager.hasPermission(driver.device)) {
                // 이미 권한이 있는 경우: 즉시 연결
                val conn = manager.openDevice(driver.device)
                if (conn != null) {
                    connectToPort(driver, conn)
                }
            } else {
                // 권한이 없는 경우: 요청 후 대기 (비동기)
                requestPermission(driver.device) { device ->
                    // 사용자가 허용 버튼을 누르면 이 Callback이 실행
                    val conn = manager.openDevice(device)
                    if (conn != null) {
                        connectToPort(driver, conn)
                    }
                }
                // 여기서 함수를 종료하여 권한 없이 port.open을 시도하지 않도록 함
                return 
            }
        }
    }
    
    // 실제 연결 로직 수행하는 함수 (Connection이 확실히 있을 때만 호출됨)
    private fun connectToPort(driver: UsbSerialDriver, conn: UsbDeviceConnection) {
        try {
            val port = driver.ports[0]
            port.open(conn) // 여기서 conn은 절대 null이 아님
            port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
            ports.add(port)
            logger.info("Connected successfully")
        } catch (e: Exception) {
            logger.error("Connection error", e)
        }
    }

     

     

    최종 요약

    1. Android 14에서 암시 인텐트 사용시 FLAG_IMMUTABLE 플래그를 꼭! 설정 해야 함.
    2. 비동기 처리에 주의.
    3. Null 일때 포트를 오픈할 시 앱이 죽을 수 있으니 꼭 방어 코드를 작성.

    댓글

Designed by Tistory.