AndroidでSocket通信するサンプル

最近Androidアプリの開発について勉強しているのですが、IPアドレスを指定して電文のやりとりをするプログラムを書いているため、備忘録を兼ねて記事にしておきます。

ソケット通信するためにはパーミッションが必要

まず大前提として、ソケット通信をするAndroidアプリを作るためには AndroidManifest.xml に以下の記述が必要です。

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

この記述がないとSocketクラスを使おうとした際に

java.net.SocketException: socket failed: EACCES (Permission denied)

みたいなエラーが発生します。

接続先がインターネットではなく、LAN内だったとしても同様。忘れずに上述のパーミッションを設定しましょう。

ソケット通信はスレッド内で実行する必要がある

ソケットを開くコード自体は単純で、下記のようにSocketクラスを使うだけなのですが…

Socket socket = new Socket("192.168.0.1", 80);

通常の関数、例えばボタンのonclickに紐付けられた関数内などで実行しても NetworkOnMainThreadException というエラーが発生して実行してくれません。

ボタンなどのUIを操作しているスレッドはUIThreadと呼ばれるのですが、その中でネットワーク通信をしちゃいかんよ、ということなんです。

つまり、下記のように新しいスレッドを作って非同期で処理すれば良いというわけ。

new Thread (new Runnable() {
	public void run() {
		try {
			Socket socket = new Socket("192.168.0.1", 80);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}).start();

接続時のタイムアウト処理

UIでIPアドレスやポート番号を指定させる場合はもちろんのこと、IP決め打ちのプログラムだったとしてもネットワーク障害などの可能性もあるため、接続のタイムアウト処理は必須でしょう。

正確な時間はわかりませんが、軽くテストした限り、new Socket();で存在しないIPアドレスを指定した場合、数十秒から数分単位で処理が戻ってこなかったので、これはちょっと長すぎます。

ということで、ソケット通信での接続時のタイムアウトを指定する方法がこちら。

new Thread (new Runnable() {
	public void run() {
		String ip = "192.168.0.1";
		int port = 80;
		InetSocketAddress address = new InetSocketAddress(ip,  port);
		Socket socket = new Socket();
		try {
			socket.connect(address, 3000);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}).start();

最初の例ではSocketクラス生成時にIPとポート番号をそのまま指定していましたが、それだとタイムアウトの時間指定ができないため、オプションなしでSocketクラスを生成した後、connect関数にアドレスとタイムアウト時間(ミリ秒)を指定します。

上の例では3000ミリ秒(=3秒)ですね。

接続のタイムアウト時にToastでメッセージを表示する方法

ToastとはAndroidによくある画面下にふんわり現れて、ふんわり消えていくメッセージボックスのことです。(これで通じるだろうかw)

まぁ別にToastにこだわらずとも、画面上に配置したTextViewなどでも良いのですが、いずれにせよ、接続のタイムアウトが発生したら普通はUI上にエラーメッセージを出してユーザー側へ伝えたいですよね。

ところが、非同期実行されているコード内からUIスレッドは触れません。WebプログラミングのJavaScriptなんかに慣れているとこのへんややこしく感じるかも知れませんね。逆にWindowsアプリの開発経験などがあるなら、特に違和感はないでしょう。

コードを見たほうがてっとり早いと思うので、さっさと例を出します。

new Thread (new Runnable() {
	public void run() {
		String ip = "192.168.0.1";
		int port = 80;
		InetSocketAddress address = new InetSocketAddress(ip,  port);
		Socket socket = new Socket();
		try {
			socket.connect(address, 3000);
		} catch (Exception e) {
			MainActivity.this.runOnUiThread(new Runnable() {
				@Override
				public void run () {
					Toast.makeText(MainActivity.this, "接続に失敗しました。", Toast.LENGTH_LONG).show();
				}
			});
		}
	}
}).start();

先述のソースからの変更点は try~catch のcatch以降の部分です。

Toast.makeText(MainActivity.this, "接続に失敗しました。", Toast.LENGTH_LONG).show();

というのがトースト通知を表示するコードですが、そのまま実行してもエラーになるため、runOnUiThreadを使ってUIThreadの中で動作させているのです。

UIスレッドの中ではソケット通信できないから非同期処理を入れたのに、今度はUIにアクセスするためにUIスレッドを作る、という。慣れないと難しいかも知れませんが、これはこういうモノとして覚えてしまったほうがてっとり早いかも。

データの送受信

接続したからにはデータの送受信をしたいわけですが、こちらは簡単。

■データの送信例

PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
pw.println("Hello!");

■データの受信例

BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String data = reader.readLine();

もちろん、それぞれのコードの前後にtry~catchなどのエラー処理は必要ですが、長くなってしまうため省略しました。

まとめ

  • ソケット通信するためにはmanifestでINTERNETパーミッションが必要。
  • UIスレッド内でソケット通信は出来ないため別スレッドを作成する必要がある。
  • 接続時のタイムアウトはconnect関数で指定可能。
  • 通信中の非同期スレッドからUIを操作するためにはrunOnUiThreadを使うと便利。
  • データの送受信には getOutputStream() / getInputStream() を使う。

といったところでしょうか。

まだまだ勉強中のため、後で加筆訂正するかも知れません。

adsbygoogle

フォロー