마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

앱설계에 관한 질문입니다. 통신을 담당하는 부분들 큐와 스레드등을 다른 클래스에서 돌려야 합니다.

0 추천

안드로이드 초보자 입니다.  아래 코드 BaseSprayActivity 는 USB 시리얼 포트로 드론과 연결합니다.  
결론적으로 드론과 통신하여 제어하게 됩니다.
문제는 윗분들이 드론과 통신하는 부분을 따로 MavLinkService 라는 클래스를 만들어서 거기다 옮기라고 하네요.
어떻게 할줄 몰라서 우선 개발할때 편의상 BaseSprayActivity 에다가 다 때려 박았습니다.  
드론과 통신을 하기위해 usb-serial-for-android 라는 라이브러리를 사용하였습니다.
라이브러리 내부적으로 스레드가 장착되어 있어서 SerialInputOutputManager.Listener 만 장착하면 주기적으로 
onNewData(data: ByteArray) 가 호출이되어 데이터를 보내줍니다.
큐에다가 데이터를 차례로 입력하고 큐에 있는 데이터를 처리하기 위해 readPaketThread()라는 스레드를 하나 만들었구요.
명령을 날릴때는 sendCmd(cmd: ByteArray?) 함수를 사용합니다.
근데 이모든걸 따로 다른 독립된 클래스로 옮겨야 하는 어떻게 하는지 초보라서 잘 모르겠습니다.
Connect() 접속하는 부분도 다른 클래스에서 구현할 수 있을까요?  
또 스레드부분은 다른 클래스에서 서비스로 구현하나요 아니면 스레드로 구현하나요?
MavLinkService 이름보니까 서비스로 구현하라고 하는것 같기도하고.
지금까지 개발한 코드를 아주필수적인 부분만 추려서 붙여봤습니다.

고수님들의 도움이 필요합니다.   정말 감사합니다.

 

open class BaseSprayActivity : BaseActivity() , SerialInputOutputManager.Listener {

    private var mavlinkpacketQueue = ArrayBlockingQueue<Byte>(4000)  
    private var isRunning = false

    private val INTENT_ACTION_GRANT_USB: String = BuildConfig.APPLICATION_ID.toString() + ".GRANT_USB"

    private val baudRate = 57600
    private val withIoManager = true

    private var broadcastReceiver: BroadcastReceiver? = null
    private var mainLooper: Handler? = null

    private var usbIoManager: SerialInputOutputManager? = null
    private var usbSerialPort: UsbSerialPort? = null
    private var usbPermission = UsbPermission.Unknown
    private var connected = false

    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        isRunning = true
        val thread = readPaketThread()
        thread.start()
    }

    public fun init_SerialPort() {
        broadcastReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context, intent: Intent) {
                if (INTENT_ACTION_GRANT_USB == intent.action) {
                    usbPermission = if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) UsbPermission.Granted else UsbPermission.Denied
                    connect()
                }
            }
        }
        mainLooper = Handler(Looper.getMainLooper())

    }

    private fun connect() { // usb 시리얼 포트와 연결 
        ....
        usbSerialPort = driver.ports[0]
        ....
        if (withIoManager) {
            usbIoManager = SerialInputOutputManager(usbSerialPort, this)
            usbIoManager!!.start()
        }
        connected = true

    }

    override fun onNewData(data: ByteArray) {
        mainLooper!!.post {
            receive(data)
        }
    }

    private fun receive(data: ByteArray) {

        val bufflen = data.size;

        for(i in 0..bufflen-1) {
            var ldata = data.get(i)
            mavlinkpacketQueue.offer(ldata) 
        }
    }

    inner class readPaketThread: Thread() {

        override fun run() {

            var payload_len : Int = 0
            var pack_size: Int = 0

            while(isRunning) {

                if(mavlinkpacketQueue.size < 1) continue

                if(mavlinkpacketQueue.peek() == 0xFE.toByte())
                {
                    pack_size = mavlinkpacketQueue.size

                    if(mavlinkpacketQueue.size < 2) continue
                    payload_len = mavlinkpacketQueue.elementAt(1).toInt() 

                    if(payload_len < 0 || payload_len > 255) { 
                        if(mavlinkpacketQueue.isNotEmpty()) {
                            mavlinkpacketQueue.poll()  
                            continue
                        }


                        if(mavlinkpacketQueue.size < payload_len+8) continue

                        var data = ByteArray(payload_len+8)

                        for(j in 0..payload_len+8-1) {
                            if(mavlinkpacketQueue.isNotEmpty()) {
                                data[j] = mavlinkpacketQueue.poll()
                            }
                        }

                        parsingMavLinkTelemetry(data, payload_len, pack_size)  // 파싱 하고 데이터 처리

                    } else {
                        if(mavlinkpacketQueue.isNotEmpty()) {
                            mavlinkpacketQueue.poll() 
                        }
                    }
                }

                super.run()
            }
        }

        fun sendCmd(cmd: ByteArray?) {

            usbSerialPort!!.write(cmd, WRITE_WAIT_MILLIS)

        }

        override fun onResume() {

            if (usbPermission == UsbPermission.Unknown || usbPermission == UsbPermission.Granted) mainLooper!!.post { connect() }

        }

        override fun onPause() {
            if (connected) {
                disconnect()
            }
        }

    }
quantumy (350 포인트) 님이 2021년 6월 16일 질문
클래스가 라이프 사이클과 상관없이 앱이 백그라운드일 때 등도 동작하길 원한다면 서비스가 적합하구요, 그렇지 않으면 그냥 쓰레드만 쓰셔도 될 것 같습니다. 서비스는 앱이 죽은 다음에도 생존할 수 있습니다. 그리고 디바이스가 잠드는 doze모드 같은 데에서도 동작해야 한다면, 스케쥴링같은 작업이 필요하실 수 있을 것 같습니다. 스케쥴링은 최근 안드로이드 버전에 따른 API 변화가 많으니 버전별로 변경사항을 체크하셔야 합니다.
코틀린을 사용하고 계시므로, concurrency 핸들링에 Coroutine의 Channel이나 Flow를 사용하시면 훨씬 간단해 집니다.

답변 달기

· 글에 소스 코드 보기 좋게 넣는 법
· 질문에 대해 추가적인 질문이나 의견이 있으면 답변이 아니라 댓글로 달아주시기 바랍니다.
표시할 이름 (옵션):
개인정보: 당신의 이메일은 이 알림을 보내는데만 사용됩니다.
스팸 차단 검사:
스팸 검사를 다시 받지 않으려면 로그인하거나 혹은 가입 하세요.
...