Android
 Computer >> コンピューター >  >> プログラミング >> Android

Androidでソケットを使用したデータの送受信を実装する方法

このチュートリアルでは、Androidアプリでソケット通信を使用してデータを送受信する方法を解説します。サーバー側とクライアント側の2つのプロジェクトを作成し、ローカルネットワーク上でメッセージのやり取りを行うサンプルを作成します。

サーバー側の実装

ステップ 1: プロジェクトの作成

Android Studioで「File」→「New Project」を選択し、必要な情報を入力して新しいプロジェクトを作成します。

ステップ 2: レイアウトファイルの作成

res/layout/activity_main.xml に以下のコードを追加します。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvIP"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <TextView
        android:id="@+id/tvPort"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tvIP"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <TextView
        android:id="@+id/tvConnectionStatus"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tvPort"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <TextView
        android:id="@+id/tvMessages"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/etMessage"
        android:layout_below="@+id/tvConnectionStatus"
        android:inputType="textMultiLine"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/btnSend"
        android:hint="Enter Message"
        android:inputType="text" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="SEND" />
</RelativeLayout>

ステップ 3: メインアクティビティの実装

src/MainActivity.java に以下のコードを追加します。

package com.server.myapplication.server;

import android.annotation.SuppressLint;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

@SuppressLint("SetTextI18n")
public class MainActivity extends AppCompatActivity {
    private ServerSocket serverSocket;
    private Thread thread1 = null;
    private TextView tvIP, tvPort, tvMessages;
    private EditText etMessage;
    private Button btnSend;
    public static String SERVER_IP = "";
    public static final int SERVER_PORT = 8080;
    private String message;
    private PrintWriter output;
    private BufferedReader input;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvIP = findViewById(R.id.tvIP);
        tvPort = findViewById(R.id.tvPort);
        tvMessages = findViewById(R.id.tvMessages);
        etMessage = findViewById(R.id.etMessage);
        btnSend = findViewById(R.id.btnSend);

        try {
            SERVER_IP = getLocalIpAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }

        thread1 = new Thread(new ServerThread());
        thread1.start();

        btnSend.setOnClickListener(v -> {
            message = etMessage.getText().toString().trim();
            if (!message.isEmpty()) {
                new Thread(new SendThread(message)).start();
            }
        });
    }

    private String getLocalIpAddress() throws UnknownHostException {
        WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        int ipInt = wifiInfo.getIpAddress();
        return InetAddress.getByAddress(
                ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ipInt).array()
        ).getHostAddress();
    }

    // サーバーソケットで接続を待機するスレッド
    class ServerThread implements Runnable {
        @Override
        public void run() {
            Socket socket;
            try {
                serverSocket = new ServerSocket(SERVER_PORT);
                runOnUiThread(() -> {
                    tvMessages.setText("Not connected");
                    tvIP.setText("IP: " + SERVER_IP);
                    tvPort.setText("Port: " + SERVER_PORT);
                });

                socket = serverSocket.accept();
                output = new PrintWriter(socket.getOutputStream());
                input = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                runOnUiThread(() -> tvMessages.setText("Connected\n"));
                new Thread(new ReceiveThread()).start();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // クライアントからのメッセージを受信するスレッド
    class ReceiveThread implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    final String receivedMessage = input.readLine();
                    if (receivedMessage != null) {
                        runOnUiThread(() -> tvMessages.append("client: " + receivedMessage + "\n"));
                    } else {
                        // 接続が切れた場合、再度待機
                        thread1 = new Thread(new ServerThread());
                        thread1.start();
                        return;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // メッセージを送信するスレッド
    class SendThread implements Runnable {
        private final String message;

        SendThread(String message) {
            this.message = message;
        }

        @Override
        public void run() {
            output.write(message);
            output.flush();
            runOnUiThread(() -> {
                tvMessages.append("server: " + message + "\n");
                etMessage.setText("");
            });
        }
    }
}

ステップ 4: マニフェストファイルの設定

androidManifest.xml に必要な権限を追加します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.example.myapplication">

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity"
            android:label="Server">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

クライアント側の実装

ステップ 1: プロジェクトの作成

Android Studioで新しいプロジェクトを作成します(サーバー側と同様の手順)。

ステップ 2: レイアウトファイルの作成

res/layout/activity_main.xml に以下のコードを追加します。

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/etIP"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="IP-Address"
        android:inputType="text" />

    <EditText
        android:id="@+id/etPort"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/etIP"
        android:hint="Port No"
        android:inputType="number" />

    <Button
        android:id="@+id/btnConnect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/etPort"
        android:layout_gravity="center"
        android:layout_marginTop="16dp"
        android:text="Connect To Server" />

    <TextView
        android:id="@+id/tvMessages"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/etMessage"
        android:layout_below="@+id/btnConnect"
        android:inputType="textMultiLine"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/btnSend"
        android:hint="Enter Message"
        android:inputType="text" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="SEND" />
</RelativeLayout>

ステップ 3: メインアクティビティの実装

src/MainActivity.java に以下のコードを追加します。

package com.client.myapplication.client;

import android.annotation.SuppressLint;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

@SuppressLint("SetTextI18n")
public class MainActivity extends AppCompatActivity {
    private Thread connectThread = null;
    private EditText etIP, etPort, etMessage;
    private TextView tvMessages;
    private Button btnSend;
    private String SERVER_IP;
    private int SERVER_PORT;
    private PrintWriter output;
    private BufferedReader input;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        etIP = findViewById(R.id.etIP);
        etPort = findViewById(R.id.etPort);
        tvMessages = findViewById(R.id.tvMessages);
        etMessage = findViewById(R.id.etMessage);
        btnSend = findViewById(R.id.btnSend);
        Button btnConnect = findViewById(R.id.btnConnect);

        btnConnect.setOnClickListener(v -> {
            tvMessages.setText("");
            SERVER_IP = etIP.getText().toString().trim();
            SERVER_PORT = Integer.parseInt(etPort.getText().toString().trim());
            connectThread = new Thread(new ConnectThread());
            connectThread.start();
        });

        btnSend.setOnClickListener(v -> {
            String message = etMessage.getText().toString().trim();
            if (!message.isEmpty()) {
                new Thread(new SendThread(message)).start();
            }
        });
    }

    // サーバーへ接続するスレッド
    class ConnectThread implements Runnable {
        @Override
        public void run() {
            try {
                Socket socket = new Socket(SERVER_IP, SERVER_PORT);
                output = new PrintWriter(socket.getOutputStream());
                input = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                runOnUiThread(() -> tvMessages.setText("Connected\n"));
                new Thread(new ReceiveThread()).start();

            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // サーバーからのメッセージを受信するスレッド
    class ReceiveThread implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    final String receivedMessage = input.readLine();
                    if (receivedMessage != null) {
                        runOnUiThread(() -> tvMessages.append("server: " + receivedMessage + "\n"));
                    } else {
                        // 接続が切れた場合、再接続を試みる
                        connectThread = new Thread(new ConnectThread());
                        connectThread.start();
                        return;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // メッセージを送信するスレッド
    class SendThread implements Runnable {
        private final String message;

        SendThread(String message) {
            this.message = message;
        }

        @Override
        public void run() {
            output.write(message);
            output.flush();
            runOnUiThread(() -> {
                tvMessages.append("client: " + message + "\n");
                etMessage.setText("");
            });
        }
    }
}

ステップ 4: マニフェストファイルの設定

androidManifest.xml に必要な権限を追加します。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.client.myapplication.client">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name="com.client.myapplication.client.MainActivity"
            android:label="Client">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

動作確認

両方のアプリケーションを実機デバイスにインストールして動作確認を行います。サーバーアプリを起動すると、デバイスのIPアドレスとポート番号が表示されます。クライアントアプリでそのIPアドレスとポート番号を入力して「Connect To Server」ボタンを押すと接続が確立され、双方向のメッセージ送受信が可能になります。

注意点:

  • 両デバイスが同じWi-Fiネットワークに接続されている必要があります
  • ファイアウォールやセキュリティ設定によってポート8080がブロックされていないか確認してください
  • Android 9 (API 28) 以降では、デフォルトでクリアテキスト通信が制限されているため、android:usesCleartextTraffic="true" をマニフェストの <application> タグに追加する必要がある場合があります
  • 本番環境では、セキュリティのためにTLS/SSLを使用した通信を検討してください
  1. AndroidでSMS(テキストメッセージ)の送受信ができない問題を解決する18の対処法

    現代において、SMSは少し古臭く感じられるかもしれませんが、実はテキストによるコミュニケーションの中で最も確実な手段の一つです。しかし、他の技術と同じように、SMSにも固有の問題が存在し、快適に利用するためにはそれらを解決する必要があります。Android端末でメッセージを送受信できなくなるトラブルは、昔からよく報告されている問題であり、ブランドや機種、OSバージョンを問わず、ほぼすべてのAndroidデバイスで発生し得ることで知られています。 メッセージが届かない、あるいは遅延するという問題は厄介です。多くの場合、ユーザー自身が異常に気づくのが遅れてしまうからです。特に多いのが、認証コード(

  2. Androidのデータ使用量を管理・追跡する方法|「Check Data Usage」で通信量を賢く節約

    何か調べものをしたいとき、私たちはスマートフォンのロックを解除してGoogleに答えを求めます。それだけではありません。オンライン決済、友人とのビデオ通話、タクシーの配車予約、ネットショッピング、お気に入りの動画視聴など、スマホは生活のあらゆる場面で活躍しています。しかし、こうした活動はすべてモバイルデータを大量に消費し、気づけば携帯電話の請求額が膨らんでいることも少なくありません。だからこそ、自分のデータ使用量をしっかりと把握しておくことが非常に重要なのです。 そのためには、データ管理アプリを活用するのがおすすめです。Google Playには、データ使用量を管理・監視して上限を守り、使いす