Android で WebSocketサーバー 【Kotlin/アプリ開発】

AndroidでWebSocketサーバーを立ち上げ、macOSのPythonクライアントからテキストを受信する仕組みを作ってみました。

Android WebSocketサーバー (Kotlin)

WebSocketサーバーの実装には Java-WebSocket ライブラリを使用できます。

build.gradle に依存関係を追加

./app/build.gradle.ktsdependencies に以下を追加します。
gradle
implementation("org.java-websocket:Java-WebSocket:1.5.2")

WebSocketサーバークラス (Kotlin)

WebSocketServer クラスを継承させて、次のようなサーバークラスを用意します。メッセージを受信したらActivityで処理させるように、コールバックインタフェースも実装してます。
kotlin
package com.example.protowebsocket

import org.java_websocket.WebSocket
import org.java_websocket.handshake.ClientHandshake
import org.java_websocket.server.WebSocketServer
import java.net.InetSocketAddress

class MyWebSocketServer(address: InetSocketAddress) : WebSocketServer(address) {

    // コールバックインターフェースの定義
    interface WebSocketCallback {
        fun onMessageReceived(message: String)
    }

    // コールバックのインスタンス
    private var callback: WebSocketCallback? = null

    // コールバックを設定するメソッド
    fun setWebSocketCallback(callback: WebSocketCallback?) {
        this.callback = callback
    }

    override fun onOpen(conn: WebSocket, handshake: ClientHandshake) {
        println("New connection: ${conn.remoteSocketAddress}")
    }

    override fun onClose(conn: WebSocket, code: Int, reason: String, remote: Boolean) {
        println("Connection closed: ${conn.remoteSocketAddress}")
    }

    override fun onMessage(conn: WebSocket, message: String) {
        println("Received: $message")
        // コールバックでActivityにメッセージを通知
        callback?.onMessageReceived(message)
    }

    override fun onError(conn: WebSocket?, ex: Exception) {
        println("Error: ${ex.message}")
    }

    override fun onStart() {
        println("WebSocket server started on port: $port")
    }
}

ボタンやテキストViewなどのレイアウトは、適宜合わせて用意してください。

WebSocketサーバーの起動

先ほどのクラスを Activity で呼び出し、サーバーを起動させます。

MainActivity.kt
package com.example.protowebsocket

import android.os.Bundle
import android.widget.Button
import android.widget.TextView
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.net.InetSocketAddress
import java.net.NetworkInterface
import kotlin.concurrent.thread

class MainActivity : AppCompatActivity() {
    private var server: MyWebSocketServer? = null
    private lateinit var messageTextView: TextView
    private var isWsServerStarted = false

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        enableEdgeToEdge()
        setContentView(R.layout.activity_main)
        messageTextView = findViewById(R.id.messageTextView)

        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
            val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
            insets
        }

        val wsServerButton: Button = findViewById(R.id.wsServerButton)

        wsServerButton.setOnClickListener {
            if (!isWsServerStarted) {
                startWsServer()
                wsServerButton.text = "Stop"
                isWsServerStarted = true
                messageTextView.text = "メッセージの受信待ち。"
            } else {
                stopWsServer()
                wsServerButton.text = "Start"
                isWsServerStarted = false
                messageTextView.text = ""
            }
        }

    }

    // Activityが破棄される際にリソースを解放
    override fun onDestroy() {
        super.onDestroy()
        println("onDestory()が呼ばれた")

        stopWsServer()

    }

    private fun startWsServer() {
        // 非同期でIPアドレスを取得してWebSocketサーバーを起動
        CoroutineScope(Dispatchers.Main).launch {
            val ipAddress = getIPAddress()
            println("IP Address: $ipAddress")

            // WebSocketサーバーを起動
            server = MyWebSocketServer(InetSocketAddress(ipAddress, 8765))
            // WebSocketサーバーのコールバックを設定
            server?.setWebSocketCallback(object : MyWebSocketServer.WebSocketCallback {
                override fun onMessageReceived(message: String) {
                    // UIスレッドでTextViewを更新
                    runOnUiThread {
                        messageTextView.text = message
                    }
                }
            })
            println("Begin server... ws://${ipAddress}:8765")

            thread {
                // TODO 「Error: Address already in use」 だった場合にstopさせてからもう一度処理する
                server?.start()
            }
        }
    }

    private fun stopWsServer() {
        // サーバーが起動している場合、停止
        server?.stop()
        // コールバックの解放
        server?.setWebSocketCallback(null)
        println("WebSocket server stopped.")
    }

    // TODO serviceクラスへ移す
    private fun getIPAddress(): String {
        try {
            val interfaces = NetworkInterface.getNetworkInterfaces()
            for (iface in interfaces) {
                print("iface.name: ")
                println(iface.name)
                // Wi-Fiインターフェースかどうか確認 (通常 "wlan0" がWi-Fiのインターフェース名)
                if (iface.name.equals("wlan2", ignoreCase = true) && iface.isUp) {
                    val addresses = iface.inetAddresses
                    for (addr in addresses) {
                        if (!addr.isLoopbackAddress && addr.hostAddress.indexOf(':') == -1) {
                            return addr.hostAddress ?: "127.0.0.1"
                        }
                    }
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return "127.0.0.1" // IPが取得できなかった場合のフォールバック
    }
}

インターネットの使用を許可する

AndroidManifest.xml に以下を追加して、インターネットの使用を許可します。
xml
    <uses-permission android:name="android.permission.INTERNET" />

Pythonクライアント

AndroidアプリへWebSocketでメッセージを送信するために、macOSなどにPythonクライアントを用意します。PythonのWebSocketクライアントには websocket-client ライブラリを使用します。

ライブラリをインストール

bash
pip install websockets  

Pythonクライアントコード

python
import asyncio
import websockets

async def send_message():
    uri = "ws://192.168.27.211:8765"  # 環境に合わせてアドレスを変更してください
    async with websockets.connect(uri) as websocket:
        message = "こんにちは、Websocket通信でメッセージを送ってます。"
        await websocket.send(message)
        print(f"Sent message: {message}")
        response = await websocket.recv()
        print(f"Received message: {response}")

asyncio.get_event_loop().run_until_complete(send_message())

これで、Androidデバイス上のWebSocketサーバーが起動し、macOSのPythonクライアントからテキストメッセージを送信して受信できるはずです。

作成したAndroidプロジェクトは、GitHubで公開中です。 こちらのページ のTryWebSocketServerをご参考ください。

関連記事

最後までご覧いただきありがとうございます!

▼ 記事に関するご質問やお仕事のご相談は以下よりお願いいたします。
お問い合わせフォーム