<template>
<view class="content">
<button @click="selectFile()">打开安卓文件管理器</button>
<view>
<text>单选URL</text>
{{ filePath }}
</view>
<view><text>多选</text></view>
<view v-for="(item, index) in fileList">{{ item }}</view>
</view>
</template>
<script>
export default {
data() {
return {
filePath: '',
fileList: []
};
},
onLoad() {},
methods: {
selectFile() {
let that = this;
let main = plus.android.runtimeMainActivity();
let Intent = plus.android.importClass('android.content.Intent');
let intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType('*/*');
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); //关键!多选参数
intent.addCategory(Intent.CATEGORY_OPENABLE);
main.startActivityForResult(intent, 200);
// 获取回调
main.onActivityResult = function(requestCode, resultCode, data) {
let Activity = plus.android.importClass('android.app.Activity');
let ContentUris = plus.android.importClass('android.content.ContentUris');
let Cursor = plus.android.importClass('android.database.Cursor');
let Uri = plus.android.importClass('android.net.Uri');
let Build = plus.android.importClass('android.os.Build');
let Environment = plus.android.importClass('android.os.Environment');
let DocumentsContract = plus.android.importClass('android.provider.DocumentsContract');
let InputStream = plus.android.importClass('java.io.InputStream');
let contentResolver = main.getContentResolver();
plus.android.importClass(contentResolver);
if (resultCode == Activity.RESULT_OK) {
if (data.getData() != null) {
let uri = data.getData();
let path = getPath(this, uri);
that.filePath = path;
// 获取文件大小
let fileSize = getFileSize(contentResolver, uri);
console.log("文件大小: " + fileSize + " bytes");
// 获取文件内容
let fileContent = getFileContent(contentResolver, uri);
console.log("文件内容: " + fileContent);
} else {
try {
let ClipData = plus.android.importClass('android.content.ClipData');
let clipData = new ClipData();
clipData = data.getClipData();
if (clipData != null) {
for (let i = 0; i < clipData.getItemCount(); i++) {
let item = clipData.getItemAt(i);
let uri = item.getUri();
let path = getPath(this, uri);
that.fileList.push(path);
// 获取文件大小
let fileSize = getFileSize(contentResolver, uri);
console.log("文件大小: " + fileSize + " bytes");
// 获取文件内容
let fileContent = getFileContent(contentResolver, uri);
console.log("文件内容: " + fileContent);
}
}
} catch (e) {
console.log(e);
}
}
}
function getFileSize(contentResolver, uri) {
let cursor = contentResolver.query(uri, null, null, null, null);
let size = 0;
if (cursor != null && cursor.moveToFirst()) {
let sizeIndex = cursor.getColumnIndex("_size");
if (sizeIndex != -1) {
size = cursor.getLong(sizeIndex);
}
cursor.close();
}
return size;
}
function getFileContent(contentResolver, uri) {
try {
// 打开输入流
let inputStream = contentResolver.openInputStream(uri);
let ByteArrayOutputStream = plus.android.importClass('java.io.ByteArrayOutputStream');
let byteArrayOutputStream = new ByteArrayOutputStream();
// 创建一个缓冲区(使用标准 JavaScript 数组)
let buffer = new Uint8Array(1024); // 每次读取 1024 字节
let bytesRead;
// 循环读取文件内容
while ((bytesRead = inputStream.read(buffer.buffer)) !== -1) {
console.log('bytesRead', bytesRead);
byteArrayOutputStream.write(buffer.subarray(0, bytesRead));
}
// 关闭输入流
inputStream.close();
// 获取完整的字节数组
let byteArray = byteArrayOutputStream.toByteArray();
byteArrayOutputStream.close();
console.log(`文件读取完成,共 ${byteArray.length} 字节`);
return byteArray; // 返回字节数组
} catch (e) {
console.error("读取文件内容时发生错误:", e);
return null;
}
}
function getPath(context, uri) {
try {
let isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
//一些三方的文件浏览器会进入到这个方法中,例如ES
//QQ文件管理器不在此列
if (isExternalStorageDocument(uri)) {
let docId = DocumentsContract.getDocumentId(uri);
let split = docId.split(':');
let type = split[0];
// 如果是手机内部存储
if ('primary' == type.toLowerCase()) {
return Environment.getExternalStorageDirectory() + '/' + split[1];
} else {
return '/storage/' + type + '/' + split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
let id = DocumentsContract.getDocumentId(uri);
if (id.indexOf('raw:') > -1) {
id = id.replace('raw:', '');
}
// 不同系统获取的id开头可能不一样,在这后面便是真实的地址
if (id.substring(0, 5) == 'msf:') {
return id.substring(5, id.length);
}
let contentUri = ContentUris.withAppendedId(Uri.parse(
'content://downloads/public_downloads'), ContentUris.parseId(uri));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
let MediaStore = plus.android.importClass('android.provider.MediaStore');
let docId = DocumentsContract.getDocumentId(uri);
let split = docId.split(':');
let type = split[0];
let contentUri = null;
if ('image' == type.toLowerCase()) {
contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
} else if ('video' == type.toLowerCase()) {
contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
} else if ('audio' == type.toLowerCase()) {
contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
}
let selection = '_id=?';
let selectionArgs = [split[1]];
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ('content' == uri.getScheme().toLowerCase()) {
if (isGooglePhotosUri(uri)) {
return uri.getLastPathSegment();
} else if (isQQMediaDocument(uri)) {
let paths = uri.getPath();
let Environment = plus.android.importClass('android.os.Environment');
let fileDir = Environment.getExternalStorageDirectory();
let files = plus.android.importClass('java.io.File');
let file = new files(fileDir, paths.substring('/QQBrowser'.length, paths
.length));
return file.getPath();
}
return getDataColumn(context, uri, null, null);
}
// File
else if ('file' == uri.getScheme().toLowerCase()) {
return uri.getPath();
}
return null;
} catch (e) {
console.log(e);
return null;
}
}
// 通过uri 查找出绝对路径
function getDataColumn(context, uri, selection, selectionArgs) {
try {
let MediaStore = plus.android.importClass('android.provider.MediaStore');
let cursor = null;
let column = MediaStore.MediaColumns.DATA;
let projection = [column];
cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
null);
if (cursor != null && cursor.moveToFirst()) {
let column_index = cursor.getColumnIndexOrThrow(column);
return cursor.getString(column_index);
}
} catch (e) {
console.log(e);
return null;
}
}
function isExternalStorageDocument(uri) {
return 'com.android.externalstorage.documents' == uri.getAuthority() ? true : false;
}
function isDownloadsDocument(uri) {
return 'com.android.providers.downloads.documents' == uri.getAuthority() ? true : false;
}
function isMediaDocument(uri) {
return 'com.android.providers.media.documents' == uri.getAuthority() ? true : false;
}
/**
* 使用第三方qq文件管理器打开
*
* @param uri
* @return
*/
function isQQMediaDocument(uri) {
return 'com.tencent.mtt.fileprovider' == uri.getAuthority() ? true : false;
}
/**
* @param uri The Uri to check.
* @return Whether the Uri authority is Google Photos.
*/
function isGooglePhotosUri(uri) {
return 'com.google.android.apps.photos.content' == uri.getAuthority() ? true : false;
}
};
},
}
};
</script>
<style></style>
获取文件大小正确,但是我怎末获取文件的二进制内容从而符合以上文档的发送格式
附录1:Ymodem OTA 升级流程
1. YMODEM协议概述
YMODEM是一种基于XMODEM的改进文件传输协议,支持批处理传输和更大文件。133字节版本是YMODEM的标准实现,使用128字节数据块加5字节头信息。
发送方和接收方都需支持YMODEM协议。
确定使用CRC-16校验(YMODEM标准)。
2. 传输流程详解
2.1 初始化阶段
• 接收方发送"C"
ASCII字符0x43,表示接收方准备就绪,请求使用CRC校验,连续发送直到收到第一个数据包或超时
• 发送方响应
发送第一个数据块(序号0),包含文件名和文件信息
2.2 发送文件信息包(第0号包)
项 说明
SOH (0x01) 起始头
00 包序号0
FF 包序号的补码(255-0=255)
文件名 以NULL(0x00)结尾的ASCII字符串
文件大小 ASCII字符串表示的文件大小,以NULL结尾
修改时间 可选,ASCII格式的时间戳
填充 用0x00填充至128字节
CRC-16 2字节校验码
2.3 数据传输阶段
• 接收方ACK(0x06)
正确接收文件信息包后确认
• 发送方开始发送数据包:
项 说明
STX (0x02) 表示133字节数据包
序号 从1开始的包序号(1-255)
补码 255-序号
数据 实际文件数据,不足128字节用EOF或填充字符补全
STX (0x02) 表示133字节数据包
• 接受方响应
项 说明
ACK(0x06) 成功接收
NAK(0x15) 请求重传
CAN(0x18) 取消传输
2.4 传输结束阶段
• 发送方发送EOT(0x04)
文件传输结束
• 接收方ACK(0x06)
确认结束
• 最终结束
发送方发送全零数据包表示批传输结束
接收方ACK确认
3. 错误处理机制
3.1 重传机制
• 接收方检测到错误时发送NAK
• 发送方重传当前数据包
3.2 超时处理
• 接收方等待数据包超时(通常0.5秒)发送NAK
• 发送方等待响应超时重传当前包
3.3 重新传输
• 发送方发送:
stop=1\r\n
其中\r\n是ascii表示换行回车符号,占2个字节
设备会在收到该指令后重新进行升级流程
4. 校验方式
• 多项式:x^16 + x^12 + x^5 + 1 (0x1021)
• 初始值:0x0000
• 计算范围:以133字节为例,CRC计算从第1个字节到第131个字节,132和133分别保存CRC值的高低位
• CRC-16校验算法
5. 注意事项
• 包序号从0开始(文件信息),数据包从1开始
• 包序号达到255后回绕到0
• 文件名和文件信息必须使用ASCII字符
最新发布