iT邦幫忙

2021 iThome 鐵人賽

DAY 4
0
Mobile Development

花30天做個Android小專案系列 第 4

Day04 - Parsing Ptt

在前一篇我們已經順利使用WebSocket連上Ptt了,接著要做的事情就是讀取Server回傳的內容。我的主要參考來源是@g21589PTTCrawler

首先承接前篇的內容,我需要做一個PttClient類別,用來負責接收與Ptt的連線內容,這個Class會是Singleton,因為預期上我會在多個Fragment以及將來須開啟的Service上使用到共通的內容。

class PttClient private constructor(serverUri: URI, header: MutableMap<String, String>) :
    WebSocketClient(serverUri, Draft_6455(), header) {
    // ...
    companion object {
        private var instance: PttClient? = null

        public fun getInstance(): PttClient {
            if (instance == null) {
                val hashMap = mutableMapOf<String, String>()
                hashMap["origin"] = "https://term.ptt.cc"
                instance = PttClient(URI.create("wss://ws.ptt.cc:443/bbs"), hashMap)
                instance!!.draft.reset()
                instance!!.pipedInputStream = PipedInputStream(4096)
                instance!!.pipedOutputStream = PipedOutputStream(instance!!.pipedInputStream)
            }

            return instance!!
        }
    }
    // ...
}

可以看到我有分別多使用了PipedInputStream以及PipedOutputStream,這是用來持續讀取PTT Server回傳的byte array內容,在PttClient啟動時我有開啟一條Thread來做這件事。

class PttClient private constructor(serverUri: URI, header: MutableMap<String, String>) :
    WebSocketClient(serverUri, Draft_6455(), header) {
    ...
    private lateinit var readThread: Thread
    private lateinit var pipedInputStream: PipedInputStream
    private lateinit var pipedOutputStream: PipedOutputStream
    
    public fun start() {
        connectBlocking(30, TimeUnit.SECONDS)

        readThread = Thread {
            initReader()
        }
        readThread.start()
    }
    
    private var posX = -1
    private var posY = -1
    private lateinit var currentScreen: Array<CharArray> //用來模擬Ptt一頁的畫面內容
    
    private fun initReader() {
        val inputStreamReader = InputStreamReader(pipedInputStream, Charset.forName("big5"))
        val bufferedReader = BufferedReader(inputStreamReader)
        val pushBackReader = PushbackReader(bufferedReader, 128)
        val charArray = CharArray(4096)

        posX = -1
        posY = -1
        currentScreen = Array(72) { CharArray(80) }

        while (true) {
            val read = pushBackReader.read(charArray)
            if (read == -1) {
                pushBackReader.close()
                bufferedReader.close()
                inputStreamReader.close()
                break
            } else {
                var i = 0
                while (i < read) {
                    when (charArray[i]) {
                        Char(0x08) -> { // BS, 退格
                            // ...
                        }
                        Char(0x0A) -> { // LF, 換行
                            // ...
                        }
                        Char(0x0D) -> { // CR, 回車(Enter)
                            // ...
                        }
                        Char(0x1B) -> { // ESC
                            // ...
                        }
                        else -> {
                            // ...
                    }
                    i++
                }
            }
        }
    }
    
    override fun onMessage(bytes: ByteBuffer?) {
        super.onMessage(bytes)
        Log.d(tag, "onMessage: $bytes")
        
        // 收到byte array內容後直接導入pipedOutputStream內,
        // 由initReader function中的內容進行讀取。
        bytes?.run {
            pipedOutputStream.write(array())
            pipedOutputStream.flush()
        }
    }

    
    ...
}

initReader中的內容可直接參考開頭所講的PTTCrawler,裡面主要是分別解析出Ptt的VT100控制碼以及文字內容的部分。另外InputStreamReader中有特別使用big5,是因為Ptt的WebSocket預設是使用big5的文字編碼來傳輸,根據這篇文章在登入時的ID後面加上","能夠將編碼改成UTF-8,不過因為我是懶惰鬼就沒有繼續嘗試了,先單純以big-5把功能做完為主。

解析完成後,以一開始連線成功的畫面print log的話能看到如下:

    private fun printScreen(): String {
        if (!this::currentScreen.isInitialized) {
            return ""
        }
        val stringBuilder = StringBuilder()
        for (i in 0 until 24) {
            for (j in 0 until 80) {
                if (currentScreen[i][j] != Char(0x00)) {
                    stringBuilder.append(currentScreen[i][j])
                }
            }
            stringBuilder.append("\n")
        }

        Log.d(tag, "printScreen: \n$stringBuilder")

        return stringBuilder.toString()
    }

Result:
https://ithelp.ithome.com.tw/upload/images/20210918/20124602NEb1aIbRYb.png


上一篇
Day03 - 連接Ptt WebSocket
下一篇
Day05 - Android Jetpack: Navigation
系列文
花30天做個Android小專案30

1 則留言

0
胭脂虎
iT邦新手 2 級 ‧ 2021-09-29 17:13:50

感謝您提供PTTCrawler套件,我能問問如果想知道要應用的套件應該要在哪裡找呢?
之前都用純手工慢慢刻出來… 覺得這個套件實在太棒了!

我要留言

立即登入留言