uniapp 谷歌权限android.permission.READ_MEDIA_IMAGES和android.permission.READ_MEDIA_VIDEO,原生打开系统相册
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- 移除 Android 13+ 的图片/视频读取权限,改用系统照片选择器 -->
<uses-permission
android:name="android.permission.READ_MEDIA_IMAGES"
tools:node="remove" />
<uses-permission
android:name="android.permission.READ_MEDIA_VIDEO"
tools:node="remove" />
<!-- 兼容旧版本:移除外部存储读写权限,避免被提升为 READ_MEDIA_* -->
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:node="remove" />
<application>
<!-- 此文件用于权限移除,不声明组件 -->
</application>
</manifest>
upload.ts
import i18n from "@/lang/i18n";
const { t } :any =i18n.global
// App-Plus 环境声明
declare const plus: any;
export interface ChooseImageResult {
tempFilePaths: string[];
}
type Source = 'album' | 'camera';
const isAppPlus = typeof plus !== 'undefined';
const isAndroid = isAppPlus && plus.os && plus.os.name === 'Android';
function toLocalPath(path: string): string {
try {
// 转换为 file:// 本地绝对路径,便于后续处理
return isAppPlus && plus.io ? plus.io.convertLocalFileSystemURL(path) : path;
} catch {
return path;
}
}
async function ensureCameraPermission(): Promise<boolean> {
if (!isAndroid) return true;
try {
const ContextCompat = plus.android.importClass('androidx.core.content.ContextCompat');
const Manifest = plus.android.importClass('android.Manifest');
const PackageManager = plus.android.importClass('android.content.pm.PackageManager');
const activity = plus.android.runtimeMainActivity();
const granted = ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) === PackageManager.PERMISSION_GRANTED;
if (granted) return true;
return await new Promise<boolean>((resolve) => {
plus.android.requestPermissions(
[Manifest.permission.CAMERA],
(e: any) => resolve(Array.isArray(e.granted) && e.granted.length > 0),
() => resolve(false)
);
});
} catch {
// 若异常,尝试继续拍照,由系统处理权限
return true;
}
}
/**
* 调用 Android 原生系统相册 (Photo Picker)
* 特点:不需要任何存储权限,符合 Google Play 政策
*/
export function openSystemPhotoPicker(): Promise<string> {
return new Promise((resolve, reject) => {
// #ifdef APP-PLUS
if (plus.os.name !== 'Android') {
reject('当前非 Android 环境');
return;
}
try {
// 1. 获取主 Activity 对象
const mainActivity = plus.android.runtimeMainActivity();
// 2. 导入必要的 Java 类
const Intent = plus.android.importClass('android.content.Intent');
const Uri = plus.android.importClass('android.net.Uri');
// 3. 创建 Intent
const intent = new Intent();
intent.setAction(Intent.ACTION_PICK); // 动作:选取
intent.setType('image/*'); // 数据:所有图片
// 4. 定义回调监听器
// 我们需要实现 Android 的 DialogInterface.OnClickListener 接口或者直接使用回调
// 这里使用最直接的 startActivityForResult 配合回调
const RequestCode = 10086; // 自定义请求码
// 5. 调用原生方法 startActivityForResult
// 注意:5+ 引擎会拦截这个调用并处理回调,或者我们需要通过 plus.android.implements 来实现回调接口
// 但在 uni-app/5+ 的较新版本中,推荐直接通过 mainActivity 的 startActivityForResult
// 关键步骤:实现 ActivityResultCallback 接口 (Android X 方式) 或 传统的 onActivityResult
// 为了兼容性,我们使用传统的 onActivityResult 方式注入
mainActivity.onActivityResult = function(requestCode, resultCode, data) {
// 恢复原始的 onActivityResult (防止内存泄漏或冲突)
const originalOnActivityResult = mainActivity.onActivityResult;
// 注意:这里通常不需要手动恢复,因为是重写,但如果是添加监听器则需要移除
if (requestCode === RequestCode) {
if (resultCode === -1) { // -1 代表 android.app.Activity.RESULT_OK
if (data) {
// 获取图片的 Uri
const uri = data.getData();
// 将 Uri 转换为 5+ 可识别的本地路径
// 注意:Android 10+ 的 Uri 可能无法直接转为绝对路径,但 plus.io 可以处理 content:// 协议
const localPath = plus.android.invoke(uri, 'toString');
resolve(localPath);
} else {
reject('未获取到数据');
}
} else {
reject('用户取消或操作失败');
}
}
// 调用父类或原始方法 (如果有的话,通常不需要)
if (originalOnActivityResult && originalOnActivityResult !== this.onActivityResult) {
originalOnActivityResult(requestCode, resultCode, data);
}
};
// 6. 启动 Activity
mainActivity.startActivityForResult(intent, RequestCode);
} catch (e) {
reject('调用失败: ' + JSON.stringify(e));
}
// #endif
// #ifndef APP-PLUS
reject('仅支持 App 端');
// #endif
});
}
async function pickFromGallery(): Promise<string[]> {
return new Promise((resolve, reject) => {
try {
plus.gallery.pick(
(path: string) => resolve([toLocalPath(path)]),
(err: any) => reject(err),
{
filter: 'image',
// 在 Android 13+ 使用系统 Photo Picker;老版本仍可正常选择
system: true,
multiple: false
}
);
} catch (e) {
reject(e);
}
});
}
async function takePhoto(): Promise<string[]> {
const ok = await ensureCameraPermission();
if (!ok) {
uni.showToast({ title: t('未授予相机权限'), icon: 'none' });
return [];
}
return new Promise((resolve, reject) => {
try {
const camera = plus.camera.getCamera();
camera.captureImage(
(path: string) => resolve([toLocalPath(path)]),
(err: any) => reject(err)
);
} catch (e) {
reject(e);
}
});
}
/**
* 替代 uni.chooseImage 的封装
* - App-Plus:使用 5+ 能力
*/
export async function chooseImage(options?: { source?: Source; count?: number }): Promise<ChooseImageResult> {
const source = options?.source;
const count = options?.count ?? 1;
// App-Plus:当前实现单选,满足 OCR 使用场景
const finalSource = source
const paths = finalSource === 'camera' ? await takePhoto() : await pickFromGallery();
return { tempFilePaths: paths.slice(0, count) };
}
export async function chooseImagePath(source?: Source, count = 1): Promise<string> {
const res = await chooseImage({ source, count });
return res.tempFilePaths[0] || '';
}

浙公网安备 33010602011771号