前言
今天学习了android socket 两个app间的通信,相信很多人在学习的过程中遇到了很多问题,比如程序闪退是最为常见的,还有就是网络请求失败,聊天窗口不显示消息记录等,下面就让我与大家分享我所遇到的吧
先来看我的运行结果
android socket 实现简单聊天室
如何实现呢?
开始我是以菜鸟教程来学习的,然后我就准备去实现两个app通信的小实例,然后发现程序运行闪退,后来我发现是我忘了加网络请求的权限(注意,现在的android版本和以前的是有较大区别的,所以在以一些较旧的学习资料来学习时,不要忘了你现在使用的android版本的一些新特性),但是加了权限后还是会闪退,于是我又去查找资料发现是从Honeycomb SDK(3.0)开始,google不再允许网络请求(HTTP、Socket)等相关操作直接在Main Thread类中,所以一个APP如果在主线程中请求网络操作,将会抛出异常。Android这个设计是为了防止网络请求时间过长而导致界面假死的情况发生。那么如何解决呢?我知道的就三种方法
第一种方法
在MainActivity文件的setContentView(R.layout.activity_main)下面加上如下代码添加以下代码实现的(不推荐)
if (android.os.Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
第二种方法
换一个低版本的模拟器,比如我用API 30的就会闪退,我换成API 22的就不会闪退,正常运行(不推荐,还是得尝试解决问题)
第三种方法
在子线程中进行网络请求实现
看我项目结构
其中MainActivity是第三种方法实现,MainActivity2是第一种方法实现(这里我就不介绍了,很简单,就是加上面那几行代码)
注意要先运行服务端得代码,再运行客户端代码,我是在IDEA中运行的服务端代码,Android Studio中分别用两个模拟器来运行客户端代码,还有就是两个模拟器要连接同一个网络才能实现通信
服务端
Server代码如下
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Server {
//定义相关的参数,端口,存储Socket连接的集合,ServerSocket对象
//以及线程池
private static final int PORT = 12345;
private List<Socket> mList = new ArrayList<Socket>();
private ServerSocket server = null;
private ExecutorService myExecutorService = null;
public static void main(String[] args) {
new Server();
}
public Server()
{
try
{
server = new ServerSocket(PORT);
//创建线程池
myExecutorService = Executors.newCachedThreadPool();
System.out.println("服务端运行中...\n");
Socket client = null;
while(true)
{
client = server.accept();
mList.add(client);
myExecutorService.execute(new Service(client));
}
}catch(Exception e){e.printStackTrace();}
}
class Service implements Runnable
{
private Socket socket;
private BufferedReader in = null;
private String msg = "";
public Service(Socket socket) {
this.socket = socket;
try
{
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
msg = "用户:" +this.socket.getInetAddress() + "~加入了聊天室"
+"当前在线人数:" +mList.size();
this.sendmsg();
}catch(IOException e){e.printStackTrace();}
}
@Override
public void run() {
try{
while(true)
{
if((msg = in.readLine()) != null)
{
if(msg.equals("bye"))
{
System.out.println("~~~~~~~~~~~~~");
mList.remove(socket);
in.close();
msg = "用户:" + socket.getInetAddress()
+ "退出:" +"当前在线人数:"+mList.size();
socket.close();
this.sendmsg();
break;
}else{
msg = socket.getInetAddress() + " 说: " + msg;
this.sendmsg();
}
}
}
}catch(Exception e){e.printStackTrace();}
}
//为连接上服务端的每个客户端发送信息
public void sendmsg()
{
System.out.println(msg);
int num = mList.size();
for(int index = 0;index < num;index++)
{
Socket mSocket = mList.get(index);
PrintWriter pout = null;
try {
pout = new PrintWriter(new BufferedWriter(
new OutputStreamWriter(mSocket.getOutputStream(),"UTF-8")),true);
pout.println(msg);
}catch (IOException e) {e.printStackTrace();}
}
}
}
}
客户端
MainActivity代码如下
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
public class MainActivity extends AppCompatActivity{
//定义相关变量,完成初始化
private TextView txtshow;
private EditText editsend;
private Button btnsend;
private static final String HOST = "10.128.126.170";
private static final int PORT = 12345;
private Socket socket = null;
private BufferedReader in = null;
private PrintWriter out = null;
private String content = "";
private StringBuilder sb = null;
private String msg;
//定义一个handler对象,用来刷新界面
public Handler handler2 = new Handler() {
public void handleMessage(Message msg) {
if (msg.what == 0x123) {
sb.append(content);
txtshow.setText(sb.toString());
}
}
};
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_main);
sb = new StringBuilder();
txtshow = (TextView) findViewById(R.id.txtshow);
editsend = (EditText) findViewById(R.id.editsend);
btnsend = (Button) findViewById(R.id.btnsend);
final LooperThread looperThread = new LooperThread();
looperThread.start();
btnsend.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Message message = Message.obtain();
String msg = editsend.getText().toString();
message.obj = msg;
looperThread.handler.sendMessage(message);
new Thread() {
//覆写Run方法
@Override
public void run() {
try {
while (true) {
if (socket.isConnected()) {
if (!socket.isInputShutdown()) {
if ((content = in.readLine()) != null) {
content += "\n";
handler2.sendEmptyMessage(0x123);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}.start();
}
});
}
class LooperThread extends Thread {
public Handler handler;
@Override
public void run() {
super.run();
try {
socket = new Socket(HOST, PORT);
in = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));
Log.i("TAG", "run: "+in.readLine());
out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
socket.getOutputStream())), true);
} catch (IOException e) {
e.printStackTrace();
}
Looper.prepare();
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
out.println(msg.obj);
}
};
Looper.loop();//loop()会调用到handler的handleMessage(Message msg)方法,所以,写在下面;
}
}
}
MainActivity的布局文件activity.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="小猪简易聊天室" />
<TextView
android:id="@+id/txtshow"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<EditText
android:id="@+id/editsend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<Button
android:id="@+id/btnsend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="发送"
/>
</LinearLayout>
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myappserver2">
<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/Theme.MyApplication1">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
参考文献
菜鸟教程:7.6.2 基于TCP协议的Socket通信(1) | 菜鸟教程 (runoob.com)
大佬博客:(18条消息) Android之NetworkOnMainThreadException异常_其实并不难,是你太悲观-CSDN博客_networkonmainthreadexception
(18条消息) [Android开发那点破事]解决android.os.NetworkOnMainThreadException_llixiangjian的博客-CSDN博客