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] || '';
}

  

posted @ 2026-05-07 15:25  玛卡巴鉲  阅读(77)  评论(0)    收藏  举报