Android Socket通信

前言

在原生安卓开发中,socket通信虽然并不是特别常用,但是在Android智能设备比如说工控板或者是在户外工作场景里的某些需求方面,Socket通信仍然是非常重要的一个环节。

本文旨在通过一个简单的demo展示如何使用Netty来实现Android Socket通信的功能,文末附源码

同样的,先来看效果吧

上面呢,就是把socket的连接、通信、断开连接,再次连接等功能都测试了一遍,原谅我的英文,也基本上就是能够把会的单词组一遍,别挑我的语法错误就好 。

关于为什么全部发送的是英文,是因为这个工具我不知道如何解决编码不匹配的问题,我的代码里面全部设置UTF-8的编码,但是我尝试过发送中文,不管是接收还是发送,都是乱码,但是英文和数字是没问题的,我想这应该只是因为编码不匹配导致的,毕竟工具上没有相关的设置,所以我就忽略这个问题了,等大家实际使用的时候,这个问题应该很好解决。

正文

跟上一篇讲MQTT通信一样,我整理这部分内容的时候,如何找一个稳定好用的socket测试软件是一个最大的痛点,在网上找了一圈,发现别人评价的Sokit是一个不错的工具,然后我会把这个工具以及demo的源代码放在文末链接,大家可以根据需要获取。

接下来,正式开始本文的讲解

1.引用依赖

  implementation 'io.netty:netty-all:4.1.68.Final'

2.申请网络权限

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

3.布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="20dp"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn_connect"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="建立连接" />

    <Button
        android:id="@+id/btn_disconnect"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="断开连接" />

    <EditText
        android:id="@+id/et_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="输入要发送的内容"
        android:minHeight="60dp" />

    <Button
        android:id="@+id/btn_publish"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="发送消息"
        android:textAllCaps="false" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="vertical">

        <ScrollView
            android:id="@+id/scroll_deal"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1">

            <TextView
                android:id="@+id/tv_deal"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="10dp"
                android:text="操作:\n"
                android:textColor="@color/black" />

        </ScrollView>

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#dcdcdc" />

        <ScrollView
            android:id="@+id/scroll_record"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginTop="5dp"
            android:layout_weight="1">

            <TextView
                android:id="@+id/tv_record"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="记录:\n"
                android:textColor="@color/black" />

        </ScrollView>

    </LinearLayout>

    <Button
        android:id="@+id/btn_clear"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:text="清空" />

</LinearLayout>

说明,本文中同样使用了viewBinding

build.gradle文件

android {
    ...
    defaultConfig {
        ...
        viewBinding{
            enabled true
        }
    }
}

4.MainActivity

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.swy.socketdemo.databinding.ActivityMainBinding;

import java.text.SimpleDateFormat;
import java.util.Date;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class MainActivity extends AppCompatActivity {
    private ActivityMainBinding binding;
    private Channel channel;
    private EventLoopGroup group;
    private String socketIp = "192.168.79.236";
    private int port = 8996;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        binding.btnConnect.setOnClickListener(v -> {
            connect();
        });

        binding.btnDisconnect.setOnClickListener(v -> {
            disconnect();
        });

        binding.btnPublish.setOnClickListener(v -> {
            String message = binding.etContent.getText().toString().trim();
            if (null == message || message.equals("")) {
                Toast.makeText(this, "输入内容不能为空", Toast.LENGTH_SHORT).show();
                return;
            }
            sendMessage(message);
        });

        binding.btnClear.setOnClickListener(v -> {
            binding.etContent.setText("");
            binding.tvDeal.setText("操作:\n");
            binding.tvRecord.setText("记录:\n");
        });

    }

    private void connect() {
        Thread clientThread = new Thread("client-Netty") {
            @Override
            public void run() {
                super.run();
                group = new NioEventLoopGroup();
                try {
                    Bootstrap bootstrap = new Bootstrap();
                    bootstrap.group(group)
                            .option(ChannelOption.TCP_NODELAY, true)
                            .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                            .channel(NioSocketChannel.class)
                            .handler(new ChannelInitializer<SocketChannel>() {
                                @Override
                                protected void initChannel(SocketChannel ch) {
                                    ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));//解码
                                    ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));//解码
                                    ch.pipeline().addLast(new NettyClientHandler());
                                }
                            });
                    ChannelFuture future = bootstrap.connect(socketIp, port);
                    future.addListener((ChannelFutureListener) future1 -> {
                        if (future1.isSuccess()) {
                            appendDeal("连接成功");
                            channel = future1.channel();
                        } else {
                            appendDeal("连接失败");
                            Throwable cause = future1.cause();
                            cause.printStackTrace();
                        }
                    }).sync();
                    // 等待连接关闭
                    future.channel().closeFuture().sync();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
        clientThread.start();
    }

    private void sendMessage(String message) {
        ByteBuf byteBuf = Unpooled.copiedBuffer(message, CharsetUtil.UTF_8);
        channel.writeAndFlush(byteBuf);
        appendDeal("发送消息成功");
        appendMessage(0, message);
    }

    private void disconnect() {
        if (channel != null) {
            channel.close();
        }
        if (group != null) {
            group.shutdownGracefully();
        }
        appendDeal("断开连接");
    }

    public class NettyClientHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) {
            appendDeal("接收消息成功");
            appendMessage(1, (String) msg);
        }
    }

    private void appendDeal(String msg) {
        runOnUiThread(() -> {
            binding.tvDeal.append(getCurrentTime() + " " + msg + "\n");
            binding.scrollDeal.post(() -> binding.scrollDeal.fullScroll(View.FOCUS_DOWN));
            binding.scrollRecord.post(() -> binding.scrollRecord.fullScroll(View.FOCUS_DOWN));
        });

    }

    //0:发送
    //1:接收
    private void appendMessage(int type, String msg) {
        runOnUiThread(() -> {
            if (0 == type) {
                binding.tvRecord.append("发送消息:" + msg + "\n");
            } else {
                binding.tvRecord.append("接收消息:" + msg + "\n");
            }
            binding.scrollDeal.post(() -> binding.scrollDeal.fullScroll(View.FOCUS_DOWN));
            binding.scrollRecord.post(() -> binding.scrollRecord.fullScroll(View.FOCUS_DOWN));
        });
    }

    private String getCurrentTime() {
        Date currentTime = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("MM-dd HH:mm:ss");
        return sdf.format(currentTime);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        disconnect();
    }
}

写在最后

联调时一定要确保两个客户端在同一网段下,也就是确保IP地址可以访问,然后这个功能的测试最好是不要在AndroidStudio自带的模拟器上测试,因为我刚开始就是在自带的模拟器上测试的,老是报“io.netty.channel.ChannelException: Unable to create Channel from class class io.netty.channel.socket.nio.NioSocketChannel”这个错误,然后一通找原因,说是权限的问题,我看了网络权限申请的没问题,也不需要申请运行时权限,然后我就怀疑是不是模拟器的问题,我就用自己的手机测试,果然,错误就没有了。

demo源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

邵旺运

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值