android app之間使用socket做溝通

最近在製作一個兩個app之間能夠互相溝通並且傳遞一些指令的專案,這種專案適合用socket而不是URLconnection,原因如下。

A socket can implement almost a duplex kind of functionality, whereas a url connection can connect to a given URI and read its content.



經過一些研究之後了解到,要建立一個 Socket 連線,我們必須寫兩支程式,分別代表 Client 端以及 Server 端。在網路上搜尋到一個多人聊天室的範例程式,如下:

Server(Server可以在能夠執行java的環境下直接執行)

1
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class ChatServer {
// クライアント接続Socketリスト
public static ArrayList<Socket> socketList = new ArrayList<Socket>();
public static void main(String[] args) throws IOException {
// ポート番号31234でサーバ起動
ServerSocket server = new ServerSocket(31234);
System.out.println("start");
while(true) {
// 接続があった際
Socket socket = server.accept();
// リストに追加
socketList.add(socket);
// クライアントから接続があった場合、別スレッドを起動する
new Thread(new ServerTread(socket)).start();
}
}
}
view raw ChatServer.java hosted with ❤ by GitHub
2
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.Socket;
import java.net.SocketException;
import java.util.Iterator;
public class ServerTread implements Runnable{
Socket socket = null;
BufferedReader br = null;
public ServerTread(Socket socket) throws UnsupportedEncodingException, IOException {
this.socket = socket;
// INPUTストリーム初期化
br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "utf-8"));
}
@Override
public void run() {
try{
String content = null;
while ((content = readFromClient()) != null) {
// サーバのソケットリストを繰り返す
for (Iterator<Socket> it = ChatServer.socketList.iterator(); it.hasNext();) {
Socket s = it.next();
try {
OutputStream os = s.getOutputStream();
os.write((content + "\n").getBytes("utf-8"));
} catch (SocketException e) {
e.printStackTrace();
// サーバのSocketリストから削除
it.remove();
}
}
}
} catch(Exception e) {
e.printStackTrace();
}
}
private String readFromClient() {
try {
return br.readLine();
} catch (Exception e) {
e.printStackTrace();
// サーバのSocketリストから削除
ChatServer.socketList.remove(socket);
}
return null;
}
}


client(android studio)

1
<uses-permission android:name="android.permission.INTERNET"/>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<!-- チャット内容入力 -->
<EditText
android:id="@+id/input"
android:layout_width="280dp"
android:layout_height="wrap_content" />
<Button
android:id="@+id/send"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:text="送信"/>
</LinearLayout>
<!-- サーバからのメッセージ受信 -->
<TextView
android:id="@+id/show"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top"
android:background="#ffff"
android:textSize="14dp"
android:textColor="#f000"/>
</LinearLayout>
view raw client.xml hosted with ❤ by GitHub
2
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.view.View.OnClickListener;
import android.os.Handler;
public class MainActivity extends AppCompatActivity {
EditText input;
TextView show;
Button send;
Handler handler;
// サーバと通信するスレッド
ClientThread clientThread;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// view取得
input = (EditText) findViewById(R.id.input);
send = (Button) findViewById(R.id.send);
show = (TextView) findViewById(R.id.show);
handler = new Handler()
{
@Override
public void handleMessage(Message msg) {
// サブスレッドからのメッセージ
if (msg.what == 0x123) {
// 表示する
show.append("\n" + msg.obj.toString());
}
}
};
clientThread = new ClientThread(handler);
// サーバ接続スレッド開始
new Thread(clientThread).start();
send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
try {
// メッセージ送信
Message msg = new Message();
msg.what = 0x345;
msg.obj = input.getText().toString();
clientThread.revHandler.sendMessage(msg);
// テキストクリア
input.setText("");
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
view raw client1.java hosted with ❤ by GitHub
3
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.net.SocketTimeoutException;
public class ClientThread implements Runnable {
private Socket s;
private Handler handler;
public Handler revHandler;
BufferedReader br = null;
OutputStream os = null;
public ClientThread(Handler handler) {
this.handler = handler;
}
public void run() {
try {
s = new Socket("192.168.0.7", 31234);
br = new BufferedReader(new InputStreamReader(
s.getInputStream()));
os = s.getOutputStream();
// スレッド起動
new Thread() {
@Override
public void run() {
String content = null;
// Socketのinputストリーム読み取り
try {
while ((content = br.readLine()) != null) {
// Mainスレッドに通知
Message msg = new Message();
msg.what = 0x123;
msg.obj = content;
handler.sendMessage(msg);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}.start();
// Lopper初期化
Looper.prepare();
revHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// UIスレッドメッセージ取得
if (msg.what == 0x345) {
// サーバにチャット内容送信
try {
os.write((msg.obj.toString() + "\r\n")
.getBytes("utf-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
// Looper起動
Looper.loop();
} catch (SocketTimeoutException e1) {
System.out.println("TIME OUT!!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
view raw client2.java hosted with ❤ by GitHub


這邊值得一提的是,開Socket Server的主機ip要如何獲得,port可以隨意指定,但是ip要如何獲得呢?這邊提供一個方式
1
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.Enumeration;
public class Server {
MainActivity activity;
ServerSocket serverSocket;
String message = "";
static final int socketServerPORT = 8080;
public Server(MainActivity activity) {
this.activity = activity;
Thread socketServerThread = new Thread(new SocketServerThread());
socketServerThread.start();
}
public int getPort() {
return socketServerPORT;
}
public void onDestroy() {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private class SocketServerThread extends Thread {
int count = 0;
@Override
public void run() {
try {
serverSocket = new ServerSocket(socketServerPORT);
while (true) {
Socket socket = serverSocket.accept();
count++;
message += "#" + count + " from "
+ socket.getInetAddress() + ":"
+ socket.getPort() + "\n";
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.msg.setText(message);
}
});
SocketServerReplyThread socketServerReplyThread = new SocketServerReplyThread(
socket, count);
socketServerReplyThread.run();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
private class SocketServerReplyThread extends Thread {
private Socket hostThreadSocket;
int cnt;
SocketServerReplyThread(Socket socket, int c) {
hostThreadSocket = socket;
cnt = c;
}
@Override
public void run() {
OutputStream outputStream;
String msgReply = "Hello from Server, you are #" + cnt;
try {
outputStream = hostThreadSocket.getOutputStream();
PrintStream printStream = new PrintStream(outputStream);
printStream.print(msgReply);
printStream.close();
message += "replayed: " + msgReply + "\n";
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.msg.setText(message);
}
});
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
message += "Something wrong! " + e.toString() + "\n";
}
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
activity.msg.setText(message);
}
});
}
}
public String getIpAddress() {
String ip = "";
try {
Enumeration<NetworkInterface> enumNetworkInterfaces = NetworkInterface
.getNetworkInterfaces();
while (enumNetworkInterfaces.hasMoreElements()) {
NetworkInterface networkInterface = enumNetworkInterfaces
.nextElement();
Enumeration<InetAddress> enumInetAddress = networkInterface
.getInetAddresses();
while (enumInetAddress.hasMoreElements()) {
InetAddress inetAddress = enumInetAddress
.nextElement();
if (inetAddress.isSiteLocalAddress()) {
ip += "Server running at : "
+ inetAddress.getHostAddress();
}
}
}
} catch (SocketException e) {
// TODO Auto-generated catch block
e.printStackTrace();
ip += "Something Wrong! " + e.toString() + "\n";
}
return ip;
}
}
view raw Server.java hosted with ❤ by GitHub

new Thread(new Runnable() {
    @Override    public void run() {
        ChatServer server = new ChatServer();        
        server.setContext(MainActivity.this);        
        server.start();    
        }
    }).start();



搭配Server.java在MainAvticity加上上面這段,可以印出IP。順帶一提的是,使用Android Studio的模擬器的話,模擬器server的IP為10.0.2.15。兩台模擬器之間要互相連線的話可以參照其他網路上設定虛擬IP的方法。



在別得class創建一個新的activity
public void methodStartActivity(Context context,String s) {
Intent intent = new Intent();
intent.setClass(context,MakeCafeLoading.class);
Bundle bundle = new Bundle();
bundle.putString("name",s);
intent.putExtras(bundle); // 記得put進去,不然資料不會帶過去哦
context.startActivity(intent);
}
view raw newAci.java hosted with ❤ by GitHub
最後提一下小心得,Socket的應用於app的小心得,由於創建clinet thread的activity跟發送資料與收到資料時的activity通常不會一樣,我們必須要所有的activity收到資料都要能夠透過那個資料,做一些處理UI的事情,例如創建新的activity或是更改UI的值。要如何做到呢?必須要獲得thread中的變數。


我們可以在thread的類中使用static去宣告需要跨activity的變數來使用。以上。或許不好的方法,但解決了我現在的問題。








留言

這個網誌中的熱門文章

android service作法

html css & bootstrap心得