上期文章:https://juejin.cn/post/7259272190637539386,感谢大家支持
背景
在androidx-Webkit,我们如果需要传递一张图片在webview与native,可以通过两种方式来实现
- 文件blog转换成base64,再由接收端继续处理
- 发送端先将图片上传至服务器,再将图片地址传递至接收端,接收端再将图片下载下来
相比于将图片直接发送到文件服务,然后传个链接给客户端,文件blog转换成base64传给客户端具有明显优势
- 不占用服务资源
- 速度快
- 不需要服务端开发成本
但是在真正落地时才发却发生了一个问题,android内存溢出了,最后经过排查是base64的字符串长度太长了,其实本质也是图片太大,导致输出的base64格式的字符串也相应的超过了客户端的字符串最大长度。难道我们要放弃这个方案么?显然不可!
探索直接传流: demo源码 解析
既然是图片太大了导致的,那么就改变思路,尝试将图片分片传递,然后由native去逐片保存,那么问题来了,再经过九九一天的尝试过后,终于完成了这项不太艰巨但是可以爽快摸鱼的任务。
流处理服务-UploadBlobServer
这个类主要做了两件事情,一个是将base64还原回byte[]对象,另外一件事情就是将文件保存到本地。
// base64还原回byte[]
import android.util.Base64;
byte[] buffer = Base64.decode(bufferBase64,Base64.DEFAULT);
// 保存流到指定文件-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
为了方便我们去管理文件传递的过程而封装的这个类,它的主要作用是控制多文件、分片下载的过程
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
// 转化流为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
}
// 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()将其再次处理后才能正常还原,有谁知道为什么么?
// 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:
// 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:
// 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!");
补充native于webview交互:juejin.cn/post/708674…
参考文章
项目地址:
Android端:github.com/runner-up-j…