l***@163.com
l***@163.com
  • 发布:2026-07-21 16:42
  • 更新:2026-07-21 17:06
  • 阅读:94

uniapp android 怎么选择本地文件 excel

分类:uni-app

uniapp android 怎么选择本地文件 excel,不使用 uniappx 怎么选择本地文件,可否提供代码

2026-07-21 16:42 负责人:无 分享
已邀请:
空白人吗

空白人吗 - 7年开发经验 可接APP 小程序 网页 后端 vx:wu1020yt

// App file helpers.
export default class appChooseFileUtils {
static androidRequestCode = 10091;

static openFileAndroid(type = "all") {
return new Promise((resolve, reject) => {
let timer = null;
let main = null;
let previousActivityResult = null;
let settled = false;
const cleanup = () => {
if (timer) {
clearTimeout(timer);
timer = null;
}
if (main) {
main.onActivityResult = previousActivityResult;
}
};
const finishResolve = (result) => {
if (settled) return;
settled = true;
cleanup();
resolve(result);
};
const finishReject = (error) => {
if (settled) return;
settled = true;
cleanup();
reject(error);
};
try {
main = plus.android.runtimeMainActivity();
const Intent = plus.android.importClass("android.content.Intent");
const Activity = plus.android.importClass("android.app.Activity");
const intent = new Intent(Intent.ACTION_GET_CONTENT);
const requestCode = this.androidRequestCode;

    intent.addCategory(Intent.CATEGORY_OPENABLE);  
    intent.setType(this.android_mimeType(type));  
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);  
    if (Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) {  
      intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);  
    }  

    previousActivityResult = main.onActivityResult;  
    // 只处理当前文件选择结果,避免影响其他系统选择器(如日期选择器)。  
    main.onActivityResult = (currentRequestCode, resultCode, data) => {  
      if (currentRequestCode != requestCode) {  
        if (typeof previousActivityResult == "function") {  
          previousActivityResult(currentRequestCode, resultCode, data);  
        }  
        return;  
      }  
      if (resultCode == 0) {  
        finishReject({ cancel: true });  
        return;  
      }  
      const uri = data ? plus.android.invoke(data, "getData") : null;  
      if (resultCode != Activity.RESULT_OK || !uri) {  
        finishReject({ msg: "未获取到文件信息" });  
        return;  
      }  

      // 关键修复:onActivityResult 由系统在 UI 线程派发,  
      // 若在此处同步解析/拷贝大文件(如 PDF),UI 线程会被占满导致页面卡死(ANR)。  
      // 这里立即 return 让系统回调尽快结束,把耗时工作 defer 到下一个事件循环再做。  
      this.android_handleResultAsync(main, uri, finishResolve, finishReject);  
    };  

    timer = setTimeout(() => {  
      finishReject({ msg: "选择文件超时" });  
    }, 30000);  
    main.startActivityForResult(intent, requestCode);  
  } catch (e) {  
    cleanup();  
    console.log("openFileAndroid start error", e);  
    finishReject({ msg: "无法打开文件选择器" });  
  }  
});  

}

static android_handleResultAsync(context, uri, finishResolve, finishReject) {
// 先弹出加载提示,给用户明确反馈(大文件拷贝可能需要一点时间)。
let loadingShown = false;
try {
uni.showLoading({ title: "正在读取文件...", mask: true });
loadingShown = true;
} catch (e) {}

const hideLoading = () => {  
  if (!loadingShown) return;  
  loadingShown = false;  
  try {  
    uni.hideLoading();  
  } catch (e) {}  
};  

// 用 setTimeout 把耗时工作推到下一个事件循环,  
// 确保系统的 onActivityResult 回调能立即结束,UI 线程不被阻塞。  
setTimeout(() => {  
  try {  
    const picked = this.android_buildPickedFile(context, uri);  
    hideLoading();  
    if (!picked.path) {  
      finishReject({ msg: "文件复制失败" });  
      return;  
    }  
    finishResolve(picked);  
  } catch (e) {  
    hideLoading();  
    console.log("openFileAndroid error", e);  
    finishReject({ msg: "文件处理失败" });  
  }  
}, 0);  

}

static android_buildPickedFile(context, uri) {
const contentResolver = plus.android.invoke(context, "getContentResolver");
plus.android.importClass(contentResolver);
const uriText = this.android_toString(uri);
const info = this.android_getFileInfo(uri, contentResolver);
const realPath = this.android_getRealPath(context, uri, contentResolver);
const rawPath =
realPath ||
this.android_copyUriToCache(context, uri, contentResolver, info.name);
const name =
info.name ||
this.android_getNameByPath(rawPath || uriText) ||
"未命名文件";

return {  
  path: this.android_toUploadPath(rawPath),  
  name,  
  size: info.size || 0,  
  uri: uriText,  
};  

}

static android_mimeType(type) {
if (type == "audio") return "audio/";
if (type == "video") return "video/
";
if (type == "image") return "image/";
return "
/*";
}

static android_getFileInfo(uri, contentResolver) {
const info = { name: "", size: 0 };
let cursor = null;
try {
const OpenableColumns = plus.android.importClass(
"android.provider.OpenableColumns",
);
cursor = contentResolver.query(
uri,
[OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE],
null,
null,
null,
);
if (cursor && plus.android.invoke(cursor, "moveToFirst")) {
const nameIndex = plus.android.invoke(
cursor,
"getColumnIndex",
OpenableColumns.DISPLAY_NAME,
);
const sizeIndex = plus.android.invoke(
cursor,
"getColumnIndex",
OpenableColumns.SIZE,
);
if (nameIndex > -1)
info.name = plus.android.invoke(cursor, "getString", nameIndex);
if (sizeIndex > -1)
info.size = plus.android.invoke(cursor, "getLong", sizeIndex);
}
} catch (e) {
console.log("android_getFileInfo error", e);
} finally {
try {
if (cursor) plus.android.invoke(cursor, "close");
} catch (e) {}
}
return info;
}

static android_getRealPath(context, uri, contentResolver) {
try {
const scheme = this.android_toString(
plus.android.invoke(uri, "getScheme"),
).toLowerCase();
if (scheme == "file") return plus.android.invoke(uri, "getPath");
if (scheme != "content") return "";

  const DocumentsContract = plus.android.importClass(  
    "android.provider.DocumentsContract",  
  );  
  const MediaStore = plus.android.importClass(  
    "android.provider.MediaStore",  
  );  
  const Uri = plus.android.importClass("android.net.Uri");  
  const ContentUris = plus.android.importClass(  
    "android.content.ContentUris",  
  );  
  const Environment = plus.android.importClass("android.os.Environment");  

  if (DocumentsContract.isDocumentUri(context, uri)) {  
    const authority = this.android_toString(  
      plus.android.invoke(uri, "getAuthority"),  
    );  
    const docId = this.android_toString(  
      DocumentsContract.getDocumentId(uri),  
    );  

    if (authority == "com.android.externalstorage.documents") {  
      const parts = docId.split(":");  
      if (parts[0] && parts[0].toLowerCase() == "primary") {  
        const externalDir = Environment.getExternalStorageDirectory();  
        return (  
          plus.android.invoke(externalDir, "getAbsolutePath") +  
          "/" +  
          (parts[1] || "")  
        );  
      }  
      if (parts[0] && parts[1])  
        return "/storage/" + parts[0] + "/" + parts[1];  
    }  

    if (authority == "com.android.providers.downloads.documents") {  
      if (docId.indexOf("raw:") === 0) return docId.replace("raw:", "");  
      const direct = this.android_queryDataColumn(uri, contentResolver);  
      if (direct) return direct;  
      if (/^\d+$/.test(docId)) {  
        const downloadUri = ContentUris.withAppendedId(  
          Uri.parse("content://downloads/public_downloads"),  
          Number(docId),  
        );  
        return this.android_queryDataColumn(downloadUri, contentResolver);  
      }  
      return "";  
    }  

    if (authority == "com.android.providers.media.documents") {  
      const parts = docId.split(":");  
      const mediaType = (parts[0] || "").toLowerCase();  
      const mediaId = parts[1];  
      let mediaUri = null;  
      if (mediaType == "image")  
        mediaUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;  
      if (mediaType == "video")  
        mediaUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;  
      if (mediaType == "audio")  
        mediaUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;  
      if (mediaUri && mediaId) {  
        return this.android_queryDataColumn(  
          mediaUri,  
          contentResolver,  
          "_id=?",  
          [mediaId],  
        );  
      }  
    }  
  }  

  return this.android_queryDataColumn(uri, contentResolver);  
} catch (e) {  
  console.log("android_getRealPath error", e);  
  return "";  
}  

}

static android_queryDataColumn(
uri,
contentResolver,
selection = null,
selectionArgs = null,
) {
let cursor = null;
try {
cursor = contentResolver.query(
uri,
["_data"],
selection,
selectionArgs,
null,
);
if (cursor && plus.android.invoke(cursor, "moveToFirst")) {
const index = plus.android.invoke(cursor, "getColumnIndex", "_data");
if (index > -1) return plus.android.invoke(cursor, "getString", index);
}
} catch (e) {
} finally {
try {
if (cursor) plus.android.invoke(cursor, "close");
} catch (e) {}
}
return "";
}

static android_copyUriToCache(context, uri, contentResolver, name) {
let input = null;
let output = null;
try {
const File = plus.android.importClass("java.io.File");
const FileOutputStream = plus.android.importClass(
"java.io.FileOutputStream",
);
const BufferedInputStream = plus.android.importClass(
"java.io.BufferedInputStream",
);
const BufferedOutputStream = plus.android.importClass(
"java.io.BufferedOutputStream",
);
// 创建一个 8KB 的 Java byte[] 缓冲区,用于批量读写。
// 关键修复:原实现逐字节调用 plus.android.invoke 进行桥接,
// 选 PDF 等较大文件时会产生数百万次 JS<->Native 桥接调用,阻塞主线程导致页面卡死(ANR)。
const JavaArray = plus.android.importClass("java.lang.reflect.Array");
const Byte = plus.android.importClass("java.lang.Byte");
const buffer = JavaArray.newInstance(Byte.TYPE, 8192);

  const cacheRoot =  
    plus.android.invoke(context, "getExternalCacheDir") ||  
    plus.android.invoke(context, "getCacheDir");  
  const dir = new File(cacheRoot, "choose_files");  
  if (!plus.android.invoke(dir, "exists"))  
    plus.android.invoke(dir, "mkdirs");  

  const outFile = new File(  
    dir,  
    Date.now() + "_" + this.android_safeFileName(name || "choose_file"),  
  );  
  input = new BufferedInputStream(  
    plus.android.invoke(contentResolver, "openInputStream", uri),  
  );  
  output = new BufferedOutputStream(new FileOutputStream(outFile));  

  // 按 8KB 块读写,桥接调用次数从“文件字节数”降到“文件字节数/8192”。  
  let len = 0;  
  while ((len = plus.android.invoke(input, "read", buffer)) != -1) {  
    plus.android.invoke(output, "write", buffer, 0, len);  
  }  
  plus.android.invoke(output, "flush");  
  return plus.android.invoke(outFile, "getAbsolutePath");  
} catch (e) {  
  console.log("android_copyUriToCache error", e);  
  return "";  
} finally {  
  try {  
    if (output) plus.android.invoke(output, "close");  
  } catch (e) {}  
  try {  
    if (input) plus.android.invoke(input, "close");  
  } catch (e) {}  
}  

}

static android_toUploadPath(path) {
if (!path) return "";
const value = path.toString();
if (value.indexOf("file://") === 0) return value;
if (value.indexOf("file:/") === 0)
return value.replace(/^file:\//, "file:///");
return "file://" + value;
}

static android_getNameByPath(path) {
if (!path) return "";
const arr = path.toString().split("/");
return decodeURIComponent(arr[arr.length - 1] || "");
}

static android_safeFileName(name) {
const value = name ? name.toString() : "choosefile";
return value.replace(/[\/:*?"<>|]/g, "
");
}

static android_toString(value) {
return value == null ? "" : value.toString();
}

static base64DowImg(base64) {
const bitmap = new plus.nativeObj.Bitmap("test");
return new Promise((resolve) => {
bitmap.loadBase64Data(base64, () => {
const url = "_doc/" + new Date().getTime() + ".png";
bitmap.save(
url,
{ overwrite: true },
() => {
uni.saveImageToPhotosAlbum({
filePath: url,
success: function () {
resolve({ code: 0, msg: "图片保存成功", filePath: url });
bitmap.clear();
},
fail: function () {
resolve({ code: 1, msg: "图片保存失败", filePath: "" });
},
});
},
() => {
resolve({ code: 2, msg: "图片保存失败", filePath: "" });
bitmap.clear();
},
);
});
});
}
}
用的时候直接 openFileAndroid 就像了,记得引入这个js

要回复问题请先登录注册