Java 使用 Socket、RxJava、Gson 传输解析 json

    项目源码地址:地址

    这几天和做嵌入式的学长做了一个小项目,学长使用 Socket 来传输包含了各个传感器信息和摄像头图片的 json 字符串,我这边使用 Android 客户端显示和加载信息和图片。

    这里使用的 Base64 编码来对图片进行编码,每一张图片都比较大,我们想实现高频率传输图片做成视频的效果。

  • 使用 GsonFormat 创建 Bean 类:

    先导入 GsonFormat 对 json 字符串的 Bean 类:File---Settings---Plugins,然后搜索 GsonFormat,进行安装,重启 AS:


    然后创建 ImageBean.java 文件,在文件里使用默认快捷键 Alt+Insert,然后选择 GsonFormat:


    然后输入 json 字符串输入文本框,点击确定,GsonFormat 就会将 ImageBean.java 文件完善:



  • 使用 Socket 进行 TCP 的读取输入流和发送输出流,这个项目服务器需要客户端发送一个 byte 数组,来验证客户端身份:
try {
            // 创建 Socket,需要传入两个参数,第一个是 IP 地址,第二个是端口号
            // 这里我们的 IP 地址是本地服务器的,所有小伙伴们可以自己写一个简单的服务器程序
            Socket socket = new Socket("192.168.1.105",8000);

            // 获取输出流
            OutputStream outputStream = socket.getOutputStream();
            // 创建一个 OutputStreamWriter 来写输出流
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
            // 这里我们使用 BufferWriter 来写,更加方便
            BufferedWriter bufferedWriter = new BufferedWriter(outputStreamWriter);
            // 服务器需要验证客户端身份,客服端发送大小为 5 的 byte 数组
            byte[] buff = new byte[5];
            // 服务器规定 buff[3]=0x20,buff[4]='\0'
            buff[3] = 0x20;
            buff[4] = '\0';
            // 写入数据
            bufferedWriter.write(new String(buff));
            bufferedWriter.flush();

            // 获取输入流,和输出流一样,我们也要使用 InputStreamReader 和 BufferedReader
            InputStream inputStream = socket.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

            // 服务器端会在每一个 json 字符串完成后发送一个 \t,所有我们使用 readLine()
            String line = null;
            while ((line = bufferedReader.readLine())!=null){
                // 这里我们先打印获取的 json 字符串
                System.out.println(line);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    我们把它放到一个 Java 文件中测试一下,打印结构如下:


  • 导入 RxJava 的依赖库和 Gson 的依赖库:

    RxJava:compile 'io.reactivex:rxjava:1.3.8'

    Gson:compile 'com.google.code.gson:gson:2.8.5'

  • 使用 RxJava 和 Gson 对获取得到的 json 字符串进行操作,我们将上面的代码放到
Observable.unsafeCreate(new Observable.OnSubscribe<String>() {
            @Override
            public void call(Subscriber<? super String> subscriber) {
                // 调用其 onStart() 方法,初始化数据或者做一些准备
                subscriber.onStart();
                try {
                    // 创建 Socket,需要传入两个参数,第一个是 IP 地址,第二个是端口号
                    // 这里我们的 IP 地址是本地服务器的,所有小伙伴们可以自己写一个简单的服务器程序
                    Socket socket = new Socket("192.168.1.105", 8000);

                    ...

                    String line = null;
                    while ((line = bufferedReader.readLine())!=null){
                        // 这里我们先打印获取的 json 字符串
                        System.out.println(line);
                        // 调用 onNext 方法,当被观察者状态变化(即发送数据)
                        // 则进行事件操作
                        subscriber.onNext(line);
                    }
                    
                    // 调用 onCompleted() 方法
                    subscriber.onCompleted();


                }catch (IOException e){
                    e.printStackTrace();
                }

            }
        })
                /*
                    RxJava 支持线程调度,能将操作切换到其它线程
                    Schedulers.immediate():当前线程
                    Schedulers.newThread():新线程
                    Schedulers.io() :I/O 操作的线程(线程无限的内部线程池)
                    Schedulers.computation() :计算线程(大小为 CPU 数的内部线程池)
                    AndroidSchedulers.mainThread():Android 的主线程
                 */
                // 这里将被观察者(即 Socket 通信)放到子线程
                .subscribeOn(Schedulers.io())
                // 将观察者(即响应事件)切换到计算线程
                .observeOn(Schedulers.computation())
                /*
                      RxJava 支持类型转换
                      就是将事件序列中的对象或整个序列进行加工处理,转换成不同的事件或事件序列
                      可以使用 map() 和 flatMap() 进行类型转换
                      感兴趣的同学可以去看看
                 */
                
                // 因为 onNext() 进行事件响应时是传入的 String 类型的 json 字符串
                // map() 方法实现了一个 Funcl 的匿名内部类
                // 其构造方法有两个参数,第一个参数代表要转换的,第二个代表转换成的
                // 所以这里我们先将 String 转换成 ImageBean 类型
                .map(new Func1<String, ImageBean>() {
                    @Override
                    public ImageBean call(String s) {
                        // 使用 Gson 将 json 字符串转换为 Bean 的对象
                        ImageBean bean = gson.fromJson(s,ImageBean.class);
                        return bean;
                    }
                })
                // 将观察者切换到 Android 主线程
                .observeOn(AndroidSchedulers.mainThread())
                .map(new Func1<ImageBean, ImageBean>() {
                    @Override
                    public ImageBean call(ImageBean imageBean) {
                        // 因为我们这里主要做一些图片解析
                        // 所有如果需要我们可以在这里对 Json 字符串中基本类型进行操作
                        return imageBean;
                    }
                })
                .observeOn(Schedulers.computation())
                // 因为我们的 Json 字符串比较复杂,摄像头图片的数据在
                // ImageBean 的内部类 UsbCamDataBean 中,所以我们先进行类型转换
                .map(new Func1<ImageBean, ImageBean.UsbCamDataBean>() {
                    @Override
                    public ImageBean.UsbCamDataBean call(ImageBean imageBean) {
                        // 因为我们可能不止一个摄像头,所以用集合保存的
                        // get(0) 获取第一个摄像头的数据,小伙伴们不用管
                        return imageBean.getUsb_cam_data().get(0);
                    }
                })
                // 我们的图片使用的是 Base64 编码,所以这里先将它的数据转换成 byte 数组
                .map(new Func1<ImageBean.UsbCamDataBean, byte[]>() {
                    @Override
                    public byte[] call(ImageBean.UsbCamDataBean usbCamDataBean) {
                        byte[] data = Base64.decode(usbCamDataBean.getData(),Base64.DEFAULT);
                        return data;
                    }
                })
                // 再将其转换为 Bitmap
                .map(new Func1<byte[], Bitmap>() {
                    @Override
                    public Bitmap call(byte[] bytes) {
                        Bitmap bitmap = BitmapFactory.decodeByteArray(bytes,0,bytes.length);
                        return bitmap;
                    }
                })
                // 然后在 Android 主线程进行事件订阅
                // 完成其 onStart()、onCompleted()、onError()、onNext() 方法的实现
                // 这四个方法不用多讲,大家看名字应该就知道是干嘛的
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<Bitmap>() {
                    @Override
                    public void onStart() {
                        super.onStart();
                        Toast.makeText(MainActivity.this,"解析開始",Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onCompleted() {
                        Toast.makeText(MainActivity.this,"解析結束",Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onError(Throwable e) {
                        // 进行发生错误后的操作
                        // 比如判断异常类型进行相关操作
                    }

                    @Override
                    public void onNext(Bitmap bitmap) {
                        // 加载图片
                        imageView.setImageBitmap(bitmap);
                    }
                });

    现在按理来说整个项目是能够完美运行的,但是我们的 Json 接受发生了错误。

    服务端是学长用 C 语言写的,为了达到效果,每秒可能会传送几十上百条 Json 字符串,而服务端的 Json 字符串破解和发送也是单独线程,并且有锁。现在有一个问题就是,我们通过 debug 发现,客户端接收的 Json 字符串会出现重复和半截,大概如图中的情况:


    我们降低了服务端的发送频率,也将客户端接收频率降低,还是会出现这种情况,现在我们还没有解决这个问题,也想各位小伙伴能够指导指导。

    。。。

    后面这个问题解决了,开始的时候 C 语言那边从客户端往服务器发送数据时是直接读取的缓存区,有多少就发送多少。可能是当频率太快时缓存区还没有刷新,所以导致的这个问题。

    后面通过发送一个长度,当从客户端发送至服务器时,先发送一个长度,只从缓存区读取该长度的字符发送,再经过优化,后面发生错误的几率已经非常小了,然后移动端再捕获错误 json 无法解析导致的 Bean 为空的异常,因为频率比较快,这样程序不会崩溃而且也看不出来异常。

    。。。

    项目的源代码在最上面,虽然最新版还没有 push 上去,但是主要的功能实现和注解都有,大家有兴趣可以看一看,这里放一张效果图(那个,我的 GIF 转换工具不能给它旋转,在电脑里面一编辑就不是 GIF 图了,大家将就看一下):


猜你喜欢

转载自blog.csdn.net/young_time/article/details/80524206