首页 > 其他分享 >Android开发使用WebSocket时如何构建数据通讯框架

Android开发使用WebSocket时如何构建数据通讯框架

时间:2024-09-14 10:52:41浏览次数:3  
标签:WebSocket String void 数据通讯 webSocket Android data public

前言

之前我们介绍过服务端使用WebSocket如何设计数据框架,现在我们看看客户端如何与它通讯。
如果光说要用WebSocket做一个例子,相信很多小伙伴都能搞通,网上这么多资料。随便拿一个过来,调通就行了。不过,做出来与把它做好是两码事。我们的目标是,不但要把数据调通,还要把它梳理完善,加入设计元素把它整理成一个程序框架,这样,你不仅积累了经验,也拥有了自己的知识资产,将来扩展功能时,或者再开一个新项目,可以花较少的精力,以最快的速度完成任务。
这就是框架的作用。

本文假定读者已经具有一定Android基础知识,重点介绍怎么样搭建数据通讯框架。

基本数据结构

之前我们讲过,基础的数据结构我们这么定:

action$$txNo&$$data

action是这个发送的指令名称,txNo是交易号,data就是系列化后的json文本。

比如登录请求,客户端发送的数据是这样的:

LOGIN$$1725876358993$${"username":"003","password":"123456"}

服务端验证通过后会返回这样的信息

LOGIN$$1725876358993$​​​​​​​${"code":200,"msg":"Success","data":{"userId":4, "username":"003", "realName":"王老五"}}

如果没通过,服务端的回应会是这样:

LOGIN$​​​​​​​$1725876358993$​​​​​​​${"code":500, "msg":"用户名或密码错误"}

数据结构是不是很简单。(因为CSDN显示问题,分隔符美元符号这里用全角的符号)

WebSocket通用类

WebSocket的协议比较简单,很容易理解,除了连接关闭,主要就是收和发。这里我们讨论文本的收发。


早些时候,Android程序大都采用RESTful与服务端交互,现在如果服务端提供的是WebSocket了,现在怎么搞呢?


老规矩,我们先找一个轮子,即底层通讯框架,注意不是造,是找。我们不想花精力在这些基础的东西上,费时费力还一时半会儿做不出稳定的东西。


Android开发 的小伙伴都知道,之前使用RESTful时我们一般用的ok.http这个框架与服务端通讯
实际上,它也可以用于WebSocket。虽然也有很多其它的框架,但是大家既然对ok.http比较习惯了,我们还是用它。


我们先利用ok.http编写一个WSClient的类。注意,这个类必须设计成一个通用的工具类,不能包含任何业务逻辑。

package com.bob.app.common;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.MutableLiveData;

import com.bob.app.C;

import java.util.concurrent.TimeUnit;

import okhttp3.ConnectionPool;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.WebSocket;
import okhttp3.WebSocketListener;
import okio.ByteString;

public class WSClient {
    private static MutableLiveData<String> mld;
    private static WebSocket socket;

    public static void init() {
        if (socket != null) {
            return;
        }
        OkHttpClient.Builder cb = new OkHttpClient.Builder();
        cb.readTimeout(C.SOCKET_TIMEOUT, TimeUnit.SECONDS);// 5 seconds
        cb.connectTimeout(C.SOCKET_TIMEOUT, TimeUnit.SECONDS);
        cb.writeTimeout(C.SOCKET_TIMEOUT, TimeUnit.SECONDS);
        cb.connectionPool(new ConnectionPool(32, 5, TimeUnit.MINUTES));
        OkHttpClient client = cb.build();
        Request request = new Request.Builder().url(C.serverUrl).build();
        socket = client.newWebSocket(request, new WSListener());
    }

    public synchronized static void send(String action, Object data, MutableLiveData<String> mld) {
        if (socket == null) {
            init();
        }
        WSClient.mld = mld;
        long txNo = System.currentTimeMillis();
        String message = action + "$$" + txNo + "$$" + U.toJSONString(data);
        if (socket != null) {
            socket.send(message);
        }
    }

    public static void disconnect(int code, String reason) {
        if (socket != null) {
            socket.close(code, reason);
            socket = null;
        }
    }

    static class WSListener extends WebSocketListener {
        @Override
        public void onClosed(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
            super.onClosed(webSocket, code, reason);
            socket = null;
        }

        @Override
        public void onClosing(@NonNull WebSocket webSocket, int code, @NonNull String reason) {
            super.onClosing(webSocket, code, reason);
        }

        @Override
        public void onFailure(@NonNull WebSocket webSocket, @NonNull Throwable t, @Nullable Response response) {
            super.onFailure(webSocket, t, response);
            mld.postValue(t.getMessage());
            socket = null;
        }

        @Override
        public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
            super.onMessage(webSocket, text);
            String[] str = text.split("\\$\\$");
            mld.postValue(str[2]);
        }

        @Override
        public void onMessage(@NonNull WebSocket webSocket, @NonNull ByteString bytes) {
            super.onMessage(webSocket, bytes);
            // process binary data
        }

        @Override
        public void onOpen(@NonNull WebSocket webSocket, @NonNull Response response) {
            super.onOpen(webSocket, response);
            socket = webSocket;
        }
    }
}

这个类代码很少,主要方法就是收和发,
收的方法是send, 接收的方法就是onMessage事件,先看send方法,
第一个参数是action,我们定的常数指令,比如登录我们就定义一个指令LOGIN, 第2个参数data 就是我们要发送的具体VO对象,
比如登录的请求数据LoginRqData:

public class LoginRqData {
    public String username;
    public String password;
    public String client;
    public String attrs;
}

第3个参数,是我们要传出去的参数,MutableLiveData是一个安卓推出的MVC机制中的数据容器,含义我们下面再说明。在这里我们先传进来,在onMessage收到数据时要使用。

 WSClient.mld = mld;


 交易号我们就用一个时间戳。
        long txNo = System.currentTimeMillis();


 然后把VO对象系列化成文本。
        String message = action + "$$" + txNo + "$$" + U.toJSONString(data);

 就可以发送出去了:

    if (socket != null) {
            socket.send(message);
        }


再看接收方法onMessage,
 

@Override
public void onMessage(@NonNull WebSocket webSocket, @NonNull String text) {
    super.onMessage(webSocket, text);
    String[] str = text.split("\\$\\$");
    mld.postValue(str[2]);
}


收到数据后,我们切割出VO数据,用postValue方法放置到数据容器。
安卓的MVC机制,就是我们只需要往这个容器中放置数据,就会激活前端的观察者方法,也就是前端事先注册好的响应方法。
下面我们看看怎么调用这个send方法。

流程机制

以登录为例,我们一般会有一个LoginActivity和一个LoginViewModel
Activity处理界面事务,ViewModel访问远程数据,   先看 Activity

public class LoginActivity extends BaseActivity {
    private LoginViewModel vm;
    private LoginBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        binding = LoginBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
    vm = new ViewModelProvider(this).get(LoginViewModel.class);
        vm.getLoginResult().observe(this, this::finishLogin);
        binding.login.setOnClickListener(v -> {
            String username = binding.username.getText().toString();
            String password = Objects.requireNonNull(binding.password.getText()).toString();
            LoginRqData data = new LoginRqData();
            data.username = username;
            data.password = password;
            data.client = Build.BRAND + " " + Build.MODEL;
            vm.login(this, data);
        });
    }

    void finishLogin(String str) {
        BaseResult br = super.processResult(A.LOGIN, str);
        if (br == null) {
            return;
        }
        TypeReference<ObjResult<LoginRpData>> type = new TypeReference<ObjResult<LoginRpData>>() { };
        ObjResult<LoginRpData> r = U.parseObject(str, type);
        if (r == null) {
            return;
        }
        if (r.isSuccess()) {
            // open MainActivity
            finish();
        } else {
            WSClient.disconnect(1000, "LOGIN_FAIL");
        }
    }
}


在onCreate方法里我们先创建ViewModel,然后对这个ViewModel的数据容器LoginResult注册一个观察者方法finishLogin
vm.getLoginResult().observe(this, this::finishLogin);
意思是,这个LoginResult数据我预定了啊,如果收到后就执行finishLogin这个回调方法。
我们再看LoginViewModel里有啥。

public class LoginViewModel extends ViewModel {

    private final MutableLiveData<String> loginResult = new MutableLiveData<>();

    public LoginViewModel() {
    }

    public MutableLiveData<String> getLoginResult() {
        return loginResult;
    }

    public void login(BaseActivity a, LoginRqData data) {
        a.send(A.LOGIN, data, loginResult);
    }
}


这里其实也很简单,一个数据容器loginResult, 一个get方法返回它,
再就是一个调远程的方法:
   public void login(BaseActivity a, LoginRqData data) {
        a.send(A.LOGIN, data, loginResult);
    }   

基类BaseActivity的调用方法如下:
   

 public void send(String action, Object data, MutableLiveData<String> mld) {
        if (isCalling(action)) {//正在处理上一个操作,忽略
            return;
        }
        WSClient.send(action, data, mld);// 调用WebSocket通用类的send方法发走数据
    }

     public synchronized boolean isCalling(String action) {//显示等待状态
        if (callingActions.contains(action)) {
            return true;
        }
        progressBar = findViewById(R.id.progressBar);
        if (progressBar != null) {
            progressBar.setVisibility(View.VISIBLE);
        }
        callingActions.add(action);
        return false;
    }

我们再看收到信息后的处理,之前我们提到过,
WSClient的onMessage方法里postValue会触发Activity里注册的回调方法,在这里,这个方法就是LoginActivty里的finishLogin
我们再回头看看这个方法的内容:

   void finishLogin(String str) {
        BaseResult br = super.processResult(A.LOGIN, str);
        if (br == null) {
            return;
        }
        TypeReference<ObjResult<LoginRpData>> type = new TypeReference<ObjResult<LoginRpData>>() { };
        ObjResult<LoginRpData> r = U.parseObject(str, type);
        if (r == null) {
            return;
        }
        if (r.isSuccess()) {
            // open MainActivity
            finish();
        } else {
            WSClient.disconnect(1000, "LOGIN_FAIL");
        }
    }


先在基类里做一些基本处理,比如网络不通啥的,显示一个通用的错误信息,没数据显示“无数据”啥的。
    super.processResult(A.LOGIN, str); 

接下来,这2句是重点:

TypeReference<ObjResult<LoginRpData>> type = new TypeReference<ObjResult<LoginRpData>>() { };
 ObjResult<LoginRpData> r = U.parseObject(str, type);


 我们从WebSocket收到的是文本,需要反系列化为VO对象,
Java里复杂对象反系列化,需要先定义一个TypePreference才行,
U是一个工具类,里面我们写了一个parseObject, 用jackson来解析对象。

  private static final ObjectMapper om = new ObjectMapper();
    static {
        om.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        om.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
        om.setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }

  public static <T> T parseObject(String json, TypeReference<T> clazz) {
        try {
            return om.readValue(json, clazz);
        } catch (Exception ignored) {
        }
        return null;
    }

   得到这个VO对象数据,我们就可以做该做的事情了,处理UI事务啥的。


   至此,Android处理WebSocket的完整流程就出来了,我们有了一个完整的程序框架。

   总结

     通过以上的描述,我们可以总结出在android开发中处理WebSocket数据通讯的基本框架是这样的:

  • 编写一个处理WebSocket收发的通用类WSClient,
  • 定义操作指令和VO对象   
  • 然后通过ViewModel声明数据容器MutableLiveData<String>,编写调用WS的方法,带入数据VO和数据容器。
  • WSClient通用类的接收事件onMessage中把收到的文本数据postValue到数据容器
  • Activity中创建ViewModel声明数据容器的回调方法
  • 在回调方法中编写代码,解析出业务对象,处理UI事务。

标签:WebSocket,String,void,数据通讯,webSocket,Android,data,public
From: https://blog.csdn.net/usabcd2/article/details/142137052

相关文章

  • Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
    由于先进的数据管理和加密协议,从现代Android设备内部存储器中删除的文件通常会不可挽回地消失。删除文件时,系统会删除指向数据块的指针,从而使文件无法访问。此外,现代设备中使用的强大加密方法意味着访问数据所需的加密密钥也被删除或变得无法访问,从而使数据不可读且几乎无法......
  • Android应用开发详述
    Android应用开发是一个涉及多个方面的过程,主要包括开发环境搭建、应用设计、编码实现、测试调试以及发布上线等阶段。以下是对Android应用开发的详细阐述:一、开发环境搭建安装JavaJDK:Android应用开发主要使用Java语言(也可以使用Kotlin等其他语言),因此需要在开发计算机上安装......
  • Android中数据存储
    数据存储Android中提供了五种主要的数据存储方式,分别为文件存储,SharedPreferences存储,SQLite数据库存储,ContentProvider存储,网络存储。主要整理文件存储,SharedPreferences存储,SQLite数据库存储,Litepal,序列化知识点及应用文件存储Android的文件存储主要分为内部存储和外部......
  • 如何从Android恢复已删除的视频?
    我们使用智能手机来存储数据、通信、访问互联网、捕捉激动人心的视频等等。但是,如果您从Android手机中删除了您最喜欢的视频之一,您会有什么感觉?你肯定会感到恼怒和焦虑。如何恢复永久删除的视频?您可以使用多种方式将已删除的视频恢复到Android设备。让我们教您如何从Android恢......
  • WebSocket1
    服务端开启websocket中间件//需要UseWebSockets,否则无法使用WebSocketapp.UseWebSockets(newWebSocketOptions(){KeepAliveInterval=TimeSpan.FromSeconds(60),});//处理websocket的中间件app.UseMiddleware<WebsocketMiddlware>();WebsocketMiddlwarepublic......
  • android_studio安装
    1、下载androidstudio官网下载网盘进行下载:百度网盘提取码:asdi2、开始安装直到这个完成界面3、打开androidstudio,直接选择不导入设置,点击OK漫长的等待直到下载完成4、OK安装完成5、配置一下环境,我这边常用的只用到了adb,只配置了adb的环境变量(......
  • Android Service服务使用方法
    启动服务的方法我们要隐式启动一个Service,首先我们需要配置AndroidMainfest.xml<serviceandroid:name=".MyAsdlService"><intent-filter><actionandroid:name="com.example.myasdlservice"/></int......
  • Android生成C++ AIDL
    生成C++[Android]接口cpp和ndk的区别cpp:生成的代码是为了在Android源码中编译,代码中会调用Android源码中的native接口。例如,引用的头文件:,,,ndk:生成的代码是为了使用ndk独立编译,调用的是ndk的接口,例如,引用的头文件:–lang=cpp,参数指定生成Android源码下编译的C++接口文件......
  • 高德地图SDK Android版开发 11 覆盖物示例 4 线
    高德地图SDKAndroid版开发11覆盖物示例4线前言界面布局MapPolyline类常量成员变量初始值创建覆盖物移除覆盖物设置属性加载地图和释放地图MapPolylineActivity类控件响应事件运行效果图前言文本通过创建多个不同线宽的折线和大地曲线,介绍Polyline的使用方法。......
  • 解决Android Studio项目加载过慢问题
    解决方案替换掉谷歌原地址:(1)官网地址:https://services.gradle.org/distributions/(2)腾讯镜像Gradle下载地址:https://mirrors.cloud.tencent.com/gradle/(3)阿里云镜像Gradle下载地址:https://mirrors.aliyun.com/macports/distfiles/gradle/把官方下载地址替换成腾讯或者阿里云的......