解决批量发送多媒体文件无序问题

437 阅读2分钟

概述

微信发送图片、视频的功能很多人都用过,不知道是否有人注意到了它是无序的。从相册中选择图片、视频时有序号,但是发送出去却是乱序。对社交app来说这无可厚非,但是办公软件无序发送并不友好

现状

发送消息到服务端没有提供批量接口,批量发送功能采用发送单个消息的接口实现

模拟代码如下:

// 设核心线程数 = 终端内核数
ExecutorService executor = Executors.newCacheThreadExecutor();

// 富媒体类型
enum MEDIA_TYPE {
    Photo, Video;
}

// 接受用户选择的文件,多线程异步发送
public void sendMediaMessages(ArrayList<Media> meidas) {
    // 此时接受到的数据是用户选择的顺序
    executor.submit(new Runnable() {
        @Override
        public void run() {
            // 遍历选中的文件发送
            for (int i = 0; i < meidas.size(); i++) {
                Media media = meidas.get(i);
                Upload.upload(media);
            }
        }
    });
}

class Upload implements UploadListener {
    public static void upload(Media media) {
        // 发送到服务端
    }
    
    @Override
    public void onFailed(Media media) {}

    @Override
    public void onSuccess(Media media) {}

    @Override
    public void onFirstFrameSuccess(Media media) {}
}

现状

  • 服务端不支持批量接口以及排序功能,需要客户端处理
  • 尽量小的改动不影响其他功能,接受性能损耗

需求

  • 图片和视频批量发送时保证各自类型相对有序
  • 多个视频需要发送时先占位,避免给用户歧义
  • 该应用一次只能选择9个文件,上传过程中再次选中文件发送依然要保证相对有序
// 新增三个队列,用于维护图片、视频
private ConcurrentLinkedDeque<Media> photos = new ConcurrentLinkedDeque<>();
private ConcurrentLinkedDeque<Media> videos = new ConcurrentLinkedDeque<>();
private ConcurrentLinkedDeque<Media> videoFirstFrames = new ConcurrentLinkedDeque<>();
...
public void sendMediaMessages(ArrayList<Media> meidas) {
    for (Media media: medias) {
        if (media.type == photo) {
            photos.add(media);
        } else {
            videoFirstFrames.add(media);
        }
    }
    
    // 省略图片的,比较简单
    
    
    // 判断Upload.firstFrameListener == null,二次上传加入队列即可
    if (Upload.firstFrameListener == null && !photos.isEmpty()) {
        Upload.setFirstFrameListener(firstFrameListener);
        uploadNext(videoFirstFrames.poll());
    }
}

private UploadListener videoListener = (media) -> {
    if (videos.isEmpty()) {
        Upload.setVideoListener(null);
        return;
    }
    uploadNext(videos.poll());
}

private FirstFrameListener firstFrameListener = (media) -> {
    // 占位后,放到videos队列等待上传
    videos.add(media);
    if (Upload.videoListener == null && !videos.isEmpty()) {
        Upload.setVideoListener(videoListener);
        uploadNext(videos.poll());
    }
    if (videoFirstFrames.isEmpty()) {
        Upload.setFirstFrameListener(null);
        return;
    }
    uploadNext(videoFirstFrames.poll());
}

private void uploadNext(Media media) {
    executor.submit(() -> {
        Upload.upload(media);
    });
}

class Upload implements UploadCallback {
    public UploadListener photoListener;
    public UploadListener videoListener;
    public FirstFrameListener firstFrameListener;
    
    ...
}