前言,今天寫一寫就離題了QQ,前面用ktor架websocket,在手機app接起來,複習一下channel的特性,後面離題講了手機怎麼接到localhost
簡單介紹,webSocket是一個客戶端和伺服器之間進行雙向持續對話,server也可以發訊息給client,不像restful api要由client主動發出請求。
其他關於websocket的介紹,自己上網找,網上資源很多,我就直接帶code
首先,網上大多會告訴你,這是免費的,但是他的連線極其不穩,有時還連不上
//剛剛測還是連不上
ws://echo.websocket.org
那android本身其實能用MockWebServer去模擬server,但我偏不,我要用ktor自己架,我不只要自己架,我還會告訴你怎麼從實機連線到電腦的localhost
MockWebServer,好像原本是測試用途,我不喜歡這樣混用,所以用ktor架了
那用ktor要怎麼架websocket呢?
看文檔,對的喔我也都是看文檔的哈哈哈哈哈
//intelliJ
fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)
@Suppress("unused")
fun Application.module() {
val scope = CoroutineScope(Job())
install(WebSockets)
routing {
webSocket("/chat") {
send("You are connected!")
randomResponse(scope, this)
for(frame in incoming) {
frame as? Frame.Text ?: continue
val receivedText = frame.readText()
send("You said: $receivedText")
}
}
}
}
fun randomResponse(scope: CoroutineScope, socket:DefaultWebSocketServerSession){
val randomSample = arrayOf(
"I am hungry",
"Harry Potter",
"ciao, mon amigo",
"To be or not to be",
"Android developer"
)
scope.launch {
while(isActive){
delay(1500)
socket.send(randomSample[(0..4).random()])
yield()
}
}
}
除了randomResponse以外,其他都跟文檔一樣,這整串就是,幫我開啟一個websocket,建立一個"/chat"的路徑(url path),當有人連上這個路徑時,先告訴她"You are connected!", 接著透過coroutine建立一個randomResponse方法,一直傳訊息給client,最後再用迴圈針對收到的訊息做系統回復
簡單,好懂
執行後,建議先用網頁別人寫好的websocket測試,隨便找個測試網站,給他ws://localhost:8080/chat`試試,如果收發都沒問題就能進下一步
fragment我用一個textview來接
//Android studio
class SocketFragment : Fragment() {
private lateinit var binding:FragmentSocketBinding
private val mAdapter = ChatAdapter()
private val viewModel by viewModels<SocketViewModel>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_socket, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding = FragmentSocketBinding.bind(view)
lifecycleScope.launch {
viewModel.messageChannel.consumeEach {
Timber.d("in fragment $it")
binding.allMsg.text = StringBuilder(binding.allMsg.text).append(it)
binding.allMsg.invalidate()
}
}
binding.sentMsg.setOnClickListener {
viewModel.sentMessage( binding.ed.text.toString() )
binding.ed.setText("")
}
}
}
socket在viewModel實例,比較好控制生命週期
class SocketViewModel: ViewModel() {
private var mWebSocket: WebSocket? = null
private val mWbSocketUrl = "ws://127.0.0.1:8080/chat"
val messageChannel = Channel<String>()
init {
initSocket()
}
fun initSocket() {
val mClient = OkHttpClient.Builder()
.pingInterval(10, TimeUnit.SECONDS)
.build()
val request: Request = Request.Builder()
.url(mWbSocketUrl)
.build()
mWebSocket = mClient.newWebSocket(request, object : WebSocketListener(){
override fun onMessage( webSocket: WebSocket, text: String) {
super.onMessage(webSocket, text)
viewModelScope.launch {
messageChannel.send(text + "\n")
Timber.d("receive message $text")
}
}
override fun onOpen(webSocket: WebSocket, response: Response) {
super.onOpen(webSocket, response)
Timber.d("success connect")
}
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
super.onClosing(webSocket, code, reason)
mWebSocket?.close(code, reason)
mWebSocket = null
}
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
super.onFailure(webSocket, t, response)
Timber.e("fail connect")
Timber.e(response?.message)
}
})
}
fun sentMessage(s:String){
mWebSocket?.send(s)
}
override fun onCleared() {
super.onCleared()
messageChannel.cancel()
mWebSocket?.cancel()
mWebSocket = null
}
}
可以看到,我用channel在viewModel和fragment之間傳訊息,記得channel的特性嗎?
不知道的,可以先看這篇
先給效果,大概長這樣
private val mWbSocketUrl = "ws://10.0.2.2:8080/chat"
在chrome的網址列輸入chrome://inspect/#devices
確定手機usb偵錯有打開
確定Remote Target有設備,可打開手機瀏覽器確定網頁也有被抓到
大概長這樣,我是把chrome調成暗色,所以你們畫面可能會是白底的
設置port, ip address
其實我也有些不懂,目前測試
直播流接Discover USB devices
就好
websocket要兩個都接
接法是
Discover USB devices
Discover network targets
兩個都是接8080的就可以了,其他的是我之前接直播流用的port
private val mWbSocketUrl = "ws://127.0.0.1:8080/chat"
private val mWbSocketUrl = "ws://電腦ip:8080/chat"
大家應該都知道網路有七層吧
不知道的,也能做,在這裡講網路分層就離題太遠了
anyway, 我有時開發會忘記帶usb線,這時我就會用無線偵錯(跳過不講無線偵錯部分),那方法二是透過chrome的開發功能連接,無線時要怎麼辦呢?
從網域連線~~~~
首先把電腦和手機連到同一個wifi,請確定wifi是可信任的,因為等等要開防火牆的port
在cmd下ipconfig拿到電腦的ip位址
window控制台
進階設置>輸入規則>新增規則
選擇
post
連線設置
設置,如果建議取消公用,然後將wifi加入至家用或工作
建立好就會這樣
電腦的ip每次都會更改,可以參考這篇設置ip,方法三我也是參考這篇的