Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)

简介: Android App开发即时通信中通过SocketIO在客户端与服务端间传输文本和图片的讲解及实战(超详细 附源码)

需要源码和服务端代码请点赞关注收藏后评论区留下QQ~~~

一、通过SocketIO传输文本消息

虽然HTTP协议能够满足多数常见的接口交互,但是他属于短连接,每次调用完就自动断开连接,并且HTTP协议区分了服务端和客户端,双方的通信过程是单向的,只有客户端可以请求服务端,服务端无法主动向客户端推送信息,所以它不适合点对点的即时通信功能

即时通信技术需要满足两方面的要求。一是长连接,以便在两台设备之间持续通信,避免频繁的连接断开操作,这样非常浪费资源。二是支持双向交流,既允许A设备主动向B设备发送消息,又允许B设备主动向A设备发送消息。

可是Java 的Socket编程比较繁琐,不仅要自行编写线程通信与IO处理的代码,还要自己定义数据包的内部格式以及解编码,为此出现了第三方的Socket通信框架SocketIO,该框架提供了服务端和客户端的依赖包,大大简化了Socket通信的开发工作量

客户端集成SocketIO添加下面一行依赖即可

implementation 'io.socket:socket.io-client:1.0.0'

接着使用SocketIO提供的Socket工具完成消息的收发操作 常用方法如下

connect 建立Socket连接

connected 判断是否连接上Socket

emit 向服务器提交指定事件消息

on  开始监听服务端推送的事件消息

off 取消监听消息

disconnect 断开Socket连接

close 关闭Socket连接

双向Socket流程如下

图中实线表示代码的调用顺序,虚线表示异步的事件触发

然后确保服务端的SocketServer正在允许(IDEA)环境

服务端会做出应答

代码如下

Java类

package com.example.network;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.network.constant.NetConst;
import com.example.network.util.DateUtil;
import com.example.network.util.SocketUtil;
import java.net.URISyntaxException;
import io.socket.client.IO;
import io.socket.client.Socket;
public class SocketioTextActivity extends AppCompatActivity {
    private static final String TAG = "SocketioTextActivity";
    private EditText et_input; // 声明一个编辑框对象
    private TextView tv_response; // 声明一个文本视图对象
    private Socket mSocket; // 声明一个套接字对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_socketio_text);
        et_input = findViewById(R.id.et_input);
        tv_response = findViewById(R.id.tv_response);
        findViewById(R.id.btn_send).setOnClickListener(v -> {
            String content = et_input.getText().toString();
            if (TextUtils.isEmpty(content)) {
                Toast.makeText(this, "请输入聊天消息", Toast.LENGTH_SHORT).show();
                return;
            }
            mSocket.emit("send_text", content); // 往Socket服务器发送文本消息
        });
        initSocket(); // 初始化套接字
    }
    // 初始化套接字
    private void initSocket() {
        // 检查能否连上Socket服务器
        SocketUtil.checkSocketAvailable(this, NetConst.BASE_IP, NetConst.BASE_PORT);
        try {
            String uri = String.format("http://%s:%d/", NetConst.BASE_IP, NetConst.BASE_PORT);
            mSocket = IO.socket(uri); // 创建指定地址和端口的套接字实例
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        mSocket.connect(); // 建立Socket连接
        // 等待接收传来的文本消息
        mSocket.on("receive_text", (args) -> {
            String desc = String.format("%s 收到服务端消息:%s",
                    DateUtil.getNowTime(), (String) args[0]);
            runOnUiThread(() -> tv_response.setText(desc));
        });
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSocket.off("receive_text"); // 取消接收传来的文本消息
        if (mSocket.connected()) { // 已经连上Socket服务器
            mSocket.disconnect(); // 断开Socket连接
        }
        mSocket.close(); // 关闭Socket连接
    }
}

XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="5dp" >
    <EditText
        android:id="@+id/et_input"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:background="@drawable/editext_selector"
        android:hint="请输入聊天内容"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <Button
        android:id="@+id/btn_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送文本消息"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <TextView
        android:id="@+id/tv_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/black"
        android:textSize="17sp" />
</LinearLayout>

二、通过SocketIO传输图片消息

倘若让SocketIO实时传输图片比较困难,因为SocketIO不支持直接传输二进制数据,使得位图对象的字节数据无法作为emit方法的输入参数。所以可以考虑利用JSON对象封装图像信息,把图像的字节数据编码成字符串保存起来

鉴于JSON格式允许容纳多个 字段,同时图片有可能很大,因此建议将图片拆开分段运输,每段标明本次的分段序号、分段长度以及分段数据,由接收方在收到后重新拼接成完整的图像

效果如下

代码如下

Java类

package com.example.network;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import android.os.Bundle;
import android.util.Base64;
import android.util.Log;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import com.example.network.bean.ImagePart;
import com.example.network.constant.NetConst;
import com.example.network.util.BitmapUtil;
import com.example.network.util.DateUtil;
import com.example.network.util.SocketUtil;
import com.google.gson.Gson;
import org.json.JSONObject;
import java.io.ByteArrayOutputStream;
import java.net.URISyntaxException;
import io.socket.client.IO;
import io.socket.client.Socket;
public class SocketioImageActivity extends AppCompatActivity {
    private static final String TAG = "SocketioImageActivity";
    private ImageView iv_request; // 声明一个图像视图对象
    private ImageView iv_response; // 声明一个图像视图对象
    private TextView tv_response; // 声明一个文本视图对象
    private int CHOOSE_CODE = 3; // 只在相册挑选图片的请求码
    private String mFileName; // 图片名称
    private Bitmap mBitmap; // 位图对象
    private Socket mSocket; // 声明一个套接字对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_socketio_image);
        iv_request = findViewById(R.id.iv_request);
        iv_response = findViewById(R.id.iv_response);
        tv_response = findViewById(R.id.tv_response);
        findViewById(R.id.btn_choose).setOnClickListener(v -> {
            // 创建一个内容获取动作的意图(准备跳到系统相册)
            Intent albumIntent = new Intent(Intent.ACTION_GET_CONTENT);
            albumIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); // 是否允许多选
            albumIntent.setType("image/*"); // 类型为图像
            startActivityForResult(albumIntent, CHOOSE_CODE); // 打开系统相册
        });
        findViewById(R.id.btn_send).setOnClickListener(v -> {
            if (mBitmap == null) {
                Toast.makeText(this, "请先选择图片文件", Toast.LENGTH_SHORT).show();
                return;
            }
            sendImage(); // 分段传输图片数据
        });
        initSocket(); // 初始化套接字
    }
    // 初始化套接字
    private void initSocket() {
        // 检查能否连上Socket服务器
        SocketUtil.checkSocketAvailable(this, NetConst.BASE_IP, NetConst.BASE_PORT);
        try {
            String uri = String.format("http://%s:%d/", NetConst.BASE_IP, NetConst.BASE_PORT);
            mSocket = IO.socket(uri); // 创建指定地址和端口的套接字实例
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
        mSocket.connect(); // 建立Socket连接
        // 等待接收传来的图片数据
        mSocket.on("receive_image", (args) -> receiveImage(args));
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        if (resultCode == RESULT_OK && requestCode == CHOOSE_CODE) { // 从相册返回
            if (intent.getData() != null) { // 从相册选择一张照片
                Uri uri = intent.getData(); // 获得已选择照片的路径对象
                String path = uri.toString();
                mFileName = path.substring(path.lastIndexOf("/")+1);
                // 根据指定图片的uri,获得自动缩小后的位图对象
                mBitmap = BitmapUtil.getAutoZoomImage(this, uri);
                iv_request.setImageBitmap(mBitmap); // 设置图像视图的位图对象
            }
        }
    }
    private int mBlock = 50*1024; // 每段的数据包大小
    // 分段传输图片数据
    private void sendImage() {
        Log.d(TAG, "sendImage");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        // 把位图数据压缩到字节数组输出流
        mBitmap.compress(Bitmap.CompressFormat.JPEG, 80, baos);
        byte[] bytes = baos.toByteArray();
        int count = bytes.length/mBlock + 1;
        Log.d(TAG, "sendImage length="+bytes.length+", count="+count);
        // 下面把图片数据经过BASE64编码后发给Socket服务器
        for (int i=0; i<count; i++) {
            Log.d(TAG, "sendImage i="+i);
            String encodeData = "";
            if (i == count-1) { // 是最后一段图像数据
                int remain = bytes.length % mBlock;
                byte[] temp = new byte[remain];
                System.arraycopy(bytes, i*mBlock, temp, 0, remain);
                encodeData = Base64.encodeToString(temp, Base64.DEFAULT);
            } else { // 不是最后一段图像数据
                byte[] temp = new byte[mBlock];
                System.arraycopy(bytes, i*mBlock, temp, 0, mBlock);
                encodeData = Base64.encodeToString(temp, Base64.DEFAULT);
            }
            // 往Socket服务器发送本段的图片数据
            ImagePart part = new ImagePart(mFileName, encodeData, i, bytes.length);
            SocketUtil.emit(mSocket, "send_image", part); // 向服务器提交图像数据
        }
    }
    private String mLastFile; // 上次的文件名
    private int mReceiveCount; // 接收包的数量
    private byte[] mReceiveData; // 收到的字节数组
    // 接收对方传来的图片数据
    private void receiveImage(Object... args) {
        JSONObject json = (JSONObject) args[0];
        ImagePart part = new Gson().fromJson(json.toString(), ImagePart.class);
        if (!part.getName().equals(mLastFile)) { // 与上次文件名不同,表示开始接收新文件
            mLastFile = part.getName();
            mReceiveCount = 0;
            mReceiveData = new byte[part.getLength()];
        }
        mReceiveCount++;
        // 把接收到的图片数据通过BASE64解码为字节数组
        byte[] temp = Base64.decode(part.getData(), Base64.DEFAULT);
        System.arraycopy(temp, 0, mReceiveData, part.getSeq()*mBlock, temp.length);
        // 所有数据包都接收完毕
        if (mReceiveCount >= part.getLength()/mBlock+1) {
            // 从字节数组中解码得到位图对象
            Bitmap bitmap = BitmapFactory.decodeByteArray(mReceiveData, 0, mReceiveData.length);
            String desc = String.format("%s 收到服务端消息:%s", DateUtil.getNowTime(), part.getName());
            runOnUiThread(() -> { // 回到主线程展示图片与描述文字
                tv_response.setText(desc);
                iv_response.setImageBitmap(bitmap);
            });
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mSocket.off("receive_image"); // 取消接收传来的图片数据
        if (mSocket.connected()) { // 已经连上Socket服务器
            mSocket.disconnect(); // 断开Socket连接
        }
        mSocket.close(); // 关闭Socket连接
    }
}

XML文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <Button
        android:id="@+id/btn_choose"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="请选择待发送的图片"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <ImageView
        android:id="@+id/iv_request"
        android:layout_width="match_parent"
        android:layout_height="180dp" />
    <Button
        android:id="@+id/btn_send"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="发送图片消息"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <TextView
        android:id="@+id/tv_response"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="5dp"
        android:textColor="@color/black"
        android:textSize="17sp" />
    <ImageView
        android:id="@+id/iv_response"
        android:layout_width="match_parent"
        android:layout_height="180dp" />
</LinearLayout>

创作不易 觉得有帮助请点赞关注收藏~~~

相关文章
|
3月前
|
JavaScript 前端开发 Android开发
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
109 13
【03】仿站技术之python技术,看完学会再也不用去购买收费工具了-修改整体页面做好安卓下载发给客户-并且开始提交网站公安备案-作为APP下载落地页文娱产品一定要备案-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
9天前
|
数据采集 JSON 网络安全
移动端数据抓取:Android App的TLS流量解密方案
本文介绍了一种通过TLS流量解密技术抓取知乎App热榜数据的方法。利用Charles Proxy解密HTTPS流量,分析App与服务器通信内容;结合Python Requests库模拟请求,配置特定请求头以绕过反爬机制。同时使用代理IP隐藏真实IP地址,确保抓取稳定。最终成功提取热榜标题、内容简介、链接等信息,为分析热点话题和用户趋势提供数据支持。此方法也可应用于其他Android App的数据采集,但需注意选择可靠的代理服务。
65 11
移动端数据抓取:Android App的TLS流量解密方案
|
2月前
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
307 76
|
3月前
|
JavaScript 搜索推荐 Android开发
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
86 8
【01】仿站技术之python技术,看完学会再也不用去购买收费工具了-用python扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-客户的麻将软件需要下载落地页并且要做搜索引擎推广-本文用python语言快速开发爬取落地页下载-优雅草卓伊凡
|
3月前
|
数据采集 JavaScript Android开发
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
95 7
【02】仿站技术之python技术,看完学会再也不用去购买收费工具了-本次找了小影-感觉页面很好看-本次是爬取vue需要用到Puppeteer库用node.js扒一个app下载落地页-包括安卓android下载(简单)-ios苹果plist下载(稍微麻烦一丢丢)-优雅草卓伊凡
|
3月前
|
Android开发 开发者 Kotlin
Android实战经验之Kotlin中快速实现MVI架构
MVI架构通过单向数据流和不可变状态,提供了一种清晰、可预测的状态管理方式。在Kotlin中实现MVI架构,不仅提高了代码的可维护性和可测试性,还能更好地应对复杂的UI交互和状态管理。通过本文的介绍,希望开发者能够掌握MVI架构的核心思想,并在实际项目中灵活应用。
81 8
|
8天前
|
人工智能 JSON 小程序
【一步步开发AI运动APP】七、自定义姿态动作识别检测——之规则配置检测
本文介绍了如何通过【一步步开发AI运动APP】系列博文,利用自定义姿态识别检测技术开发高性能的AI运动应用。核心内容包括:1) 自定义姿态识别检测,满足人像入镜、动作开始/停止等需求;2) Pose-Calc引擎详解,支持角度匹配、逻辑运算等多种人体分析规则;3) 姿态检测规则编写与执行方法;4) 完整示例展示左右手平举姿态检测。通过这些技术,开发者可轻松实现定制化运动分析功能。
|
2月前
|
JSON 自然语言处理 前端开发
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
149 72
【01】对APP进行语言包功能开发-APP自动识别地区ip后分配对应的语言功能复杂吗?-成熟app项目语言包功能定制开发-前端以uniapp-基于vue.js后端以laravel基于php为例项目实战-优雅草卓伊凡
|
2月前
|
安全 API Swift
如何在苹果内购开发中获取App Store Connect API密钥-共享密钥理解内购安全-优雅草卓伊凡
如何在苹果内购开发中获取App Store Connect API密钥-共享密钥理解内购安全-优雅草卓伊凡
117 15
如何在苹果内购开发中获取App Store Connect API密钥-共享密钥理解内购安全-优雅草卓伊凡
|
2月前
|
Web App开发 编解码 算法
布谷一对一直播源码开发:阿里云视频语音通话社交交友App的必备功能
在当今移动社交领域,一对一视频和语音通话功能已成为用户期待的基础配置。从熟人社交到陌生人交友,从专业咨询到情感陪伴,实时音视频互动能力直接决定了社交App的用户留存和市场竞争力。山东布谷科技将深入探讨一对一直播源码开发高质量一对一视频和语音通话功能的关键要素和技术实现方案。
布谷一对一直播源码开发:阿里云视频语音通话社交交友App的必备功能

热门文章

最新文章