前端和android之间传递流数据 - 初版


上期文章:https://juejin.cn/post/7259272190637539386,感谢大家支持

背景

在androidx-Webkit,我们如果需要传递一张图片在webview与native,可以通过两种方式来实现

  • 文件blog转换成base64,再由接收端继续处理
  • 发送端先将图片上传至服务器,再将图片地址传递至接收端,接收端再将图片下载下来

相比于将图片直接发送到文件服务,然后传个链接给客户端,文件blog转换成base64传给客户端具有明显优势

  • 不占用服务资源
  • 速度快
  • 不需要服务端开发成本

但是在真正落地时才发却发生了一个问题,android内存溢出了,最后经过排查是base64的字符串长度太长了,其实本质也是图片太大,导致输出的base64格式的字符串也相应的超过了客户端的字符串最大长度。难道我们要放弃这个方案么?显然不可!

探索直接传流:demo源码 解析

既然是图片太大了导致的,那么就改变思路,尝试将图片分片传递,然后由native去逐片保存,那么问题来了,再经过九九一天的尝试过后,终于完成了这项不太艰巨但是可以爽快摸鱼的任务。

流处理服务-UploadBlobServer

这个类主要做了两件事情,一个是将base64还原回byte[]对象,另外一件事情就是将文件保存到本地。

ini 复制代码
  // base64还原回byte[]
  import android.util.Base64;
  byte[]  buffer = Base64.decode(bufferBase64,Base64.DEFAULT);
 
arduino 复制代码
   // 保存流到指定文件-android中需要创建一个工作线程
   private class  DownThread {
        private String cacheFilePath = null;
        private InputStream inputStream;
        public DownThread(String cacheFilePath) {
            this.cacheFilePath = cacheFilePath;
        }
        private RandomAccessFile fileOutputStream = null;
        public void write(byte[] buffer,int seekStart) throws IOException {
            File cacheFile = new File(this.cacheFilePath);
            if (!cacheFile.exists()) {
                cacheFile.createNewFile();
            }
            fileOutputStream = new RandomAccessFile(cacheFile,"rwd");
            inputStream = new ByteArrayInputStream(buffer);
            fileOutputStream.seek(seekStart);
            int length = -1;
            while ((length = inputStream.read(buffer)) != -1) {
                fileOutputStream.write(buffer,0,length);
            }
        }
        public File close() throws IOException {
            inputStream.close();
            fileOutputStream.close();
            return  new File(cacheFilePath);
        }
    }
   
文件传递控制器UploadBlobContainer

为了方便我们去管理文件传递的过程而封装的这个类,它的主要作用是控制多文件、分片下载的过程

typescript 复制代码
public class UploadBlobContainer {

    private static HashMap<String, UploadBlobServer> updateBlobHashMap =new HashMap<>();

    private static UploadBlobListener<UploadBlobBean> uploadBlobListener;

    public static void setUploadBlobListener(UploadBlobListener<UploadBlobBean> uploadBlobListener) {
        UploadBlobContainer.uploadBlobListener = uploadBlobListener;
    }

    /**
     * 获取唯一类
     * @param uploadBlobBean
     * @return
     */
    public static UploadBlobServer getUpdateBlob(UploadBlobBean uploadBlobBean){
        String fileName = uploadBlobBean.getFileName();
        if(updateBlobHashMap.containsKey(fileName)){
            return updateBlobHashMap.get(fileName);
        }
        // 初始化 流传输类
        UploadBlobServer updateBlob=new UploadBlobServer();
        // 事件统一输出
        updateBlob.setUploadBlobListener(new UploadBlobListener<String>() {
            @Override
            public void start(String fileName) {
                 if(UploadBlobContainer.uploadBlobListener!=null){
                     UploadBlobContainer.uploadBlobListener.start(uploadBlobBean);
                 }
            }

            @Override
            public void write(String fileName) {
                if(UploadBlobContainer.uploadBlobListener!=null){
                    UploadBlobContainer.uploadBlobListener.write(uploadBlobBean);
                }
            }

            @Override
            public void end(String fileName) {
                if(UploadBlobContainer.uploadBlobListener!=null){
                    UploadBlobContainer.uploadBlobListener.end(uploadBlobBean);
                }
                // 完成后从输出站内退出
                updateBlobHashMap.remove(fileName);
            }
        });
        updateBlobHashMap.put(fileName,updateBlob);
        return updateBlob;
    }

    public static  void upload(UploadBlobBean uploadBlobBean) {
        upload(uploadBlobBean,null);
    }
    public static  void upload(UploadBlobBean uploadBlobBean,UploadBlobListener<UploadBlobBean> uploadBlobListener) {
        setUploadBlobListener(uploadBlobListener);
        UploadBlobServer uploadBlobServer = getUpdateBlob(uploadBlobBean);
        switch (uploadBlobServer.status){
            case PADDING:
                uploadBlobServer.init(uploadBlobBean.getFileName(),uploadBlobBean.getContentLength());
            case WRITE:
                try {
                    uploadBlobServer.uploadFile(uploadBlobBean.getBody(),uploadBlobBean.getFileName());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                break;

        }
    }
}
js的实现

最主要的功能是

  • 转化流为byte[]格式
  • byte[]分片
  • byte[]转base64
ini 复制代码
// 转化流为byte[]格式
function getUint8ArrayBlob (blobStr) {
  const byteString = atob(blobStr.split(',')[1])
  const ab = new ArrayBuffer(byteString.length)
  const blob = new Uint8Array(ab)
  for (let i = 0; i < byteString.length; i++) {
    blob[i] = byteString.charCodeAt(i)
  }
  return blob
}
matlab 复制代码
// byte[]分片
function sliceUint8Array (byteArray) {
  const blocks = []
  let length = byteArray.length
  console.log('getFile-h5-length', length + '::=>' + length)
  while (length) {
    const startIdnex = byteArray.length - length
    const end = startIdnex + (length >= 65535 ? 65535 : length)
    const byteArrayBlock = byteArray.slice(startIdnex, end)
    length -= byteArrayBlock.length
    blocks.push(byteArrayBlock)
    console.log('getFile-h5-length', startIdnex + '::' + end)
  }
  return blocks
}

byte[]转化为base64格式,这里这么处理说明一下,为何不直接转成base64,因为尝试过直接转化后,并不能还原,所以String.fromCharCode()将其再次处理后才能正常还原,有谁知道为什么么?

vbnet 复制代码
// byte[]转化为base64格式
function arrayBufferToBase64 (buffer) {
  let binary = ''
  const bytes = new Uint8Array(buffer)
  const len = bytes.byteLength
  for (let i = 0; i < len; i++) {
    binary += String.fromCharCode(bytes[i])
  }
  // window.btoa():将ascii字符串或二进制数据转换成一个base64编码过的字符串,该方法不能直接作用于Unicode字符串.
  return window.btoa(binary)
}

又能愉快的传图了

look、look一下子

题外话 轻松加愉快的androidx-Webkit

在androidx-Webkit之中,我们就不用为传递文件的功能大为光火,因为androidx-Webkit已经支持了字节流传递的方式,直接秒杀上面的所有方案,以下是我搬运的简绘Android同学的文章代码,文章链接在底端,希望大家同样支持一下。

native:

scss 复制代码
// App (in Java)
WebMessageListener myListener = new WebMessageListener() {
  @Override
  public void onPostMessage(WebView view, WebMessageCompat message, Uri sourceOrigin,
           boolean isMainFrame, JavaScriptReplyProxy replyProxy) {
    // Communication is setup, send file data to web.
    if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_ARRAY_BUFFER)) {
      // Suppose readFileData method is to read content from file.
      byte[] fileData = readFileData("myFile.dat");
      replyProxy.postMessage(fileData);
    }
  }
}

js:

javascript 复制代码
// Web page (in JavaScript)
myObject.onmessage = function(event) {
  if (event.data instanceof ArrayBuffer) {
    const data = event.data;  // Received file content from app.
    const dataView = new DataView(data);
    // Consume file content by using JavaScript DataView to access ArrayBuffer.
  }
}
myObject.postMessage("Setup!");

参考文章

juejin.cn/post/725976...

项目地址:

Android端:github.com/runner-up-j...

h5端:github.com/runner-up-j...

相关推荐
叽哥42 分钟前
Kotlin学习第 1 课:Kotlin 入门准备:搭建学习环境与认知基础
android·java·kotlin
风往哪边走1 小时前
创建自定义语音录制View
android·前端
用户2018792831671 小时前
事件分发之“官僚主义”?或“绕圈”的艺术
android
用户2018792831671 小时前
Android事件分发为何喜欢“兜圈子”?不做个“敞亮人”!
android
Kapaseker3 小时前
你一定会喜欢的 Compose 形变动画
android
QuZhengRong3 小时前
【数据库】Navicat 导入 Excel 数据乱码问题的解决方法
android·数据库·excel
zhangphil4 小时前
Android Coil3视频封面抽取封面帧存Disk缓存,Kotlin(2)
android·kotlin
程序员码歌11 小时前
【零代码AI编程实战】AI灯塔导航-总结篇
android·前端·后端
书弋江山12 小时前
flutter 跨平台编码库 protobuf 工具使用
android·flutter
来来走走15 小时前
Flutter开发 webview_flutter的基本使用
android·flutter