You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Expo Android:从第三方应用打开文件时FileSystem.copyAsync权限被拒

问题解决:Android第三方应用分享PDF到Expo应用时的权限拒绝问题

核心问题分析

从KakaoTalk、Slack等应用通过content:// URI打开PDF到你的Expo应用时,调用FileSystem.copyAsync触发权限拒绝,原因是:

  1. 第三方应用的FileProvider未对外暴露(exported=false),仅通过临时权限授予你的应用
  2. Expo的FileSystem.copyAsync无法正确继承Activity的临时URI权限,导致访问失败
  3. 原Intent Filter缺少PDF类型匹配,第三方应用可能未正确传递完整权限

解决方案步骤

1. 完善Manifest的Intent Filter

添加PDF的MIME类型和扩展名匹配,确保第三方应用能正确触发你的应用并传递权限:

<intent-filter>
    <action android:name="android.intent.action.VIEW"/>
    <category android:name="android.intent.category.DEFAULT"/>
    <category android:name="android.intent.category.BROWSABLE"/>
    <!-- 自定义Scheme保留 -->
    <data android:scheme="moonlight"/>
    <data android:scheme="com.corca.moonlight"/>
    <data android:scheme="exp+moonlight"/>
    <!-- 添加PDF匹配规则 -->
    <data android:mimeType="application/pdf"/>
    <data android:pathPattern=".*\\.pdf"/>
    <data android:host="*"/>
</intent-filter>

2. 替换FileSystem.copyAsync为原生文件复制逻辑

FileSystem.copyAsync无法处理第三方content:// URI的临时权限,需通过原生模块用ContentResolver读取文件流:

原生模块代码(Android)
@ReactMethod
public void copyContentUriToCache(String contentUriStr, Promise promise) {
    try {
        Uri uri = Uri.parse(contentUriStr);
        Context context = getReactApplicationContext();
        InputStream inputStream = context.getContentResolver().openInputStream(uri);
        
        File cacheDir = context.getCacheDir();
        File tempFile = new File(cacheDir, "temp_shared.pdf");
        
        OutputStream outputStream = new FileOutputStream(tempFile);
        byte[] buffer = new byte[1024];
        int length;
        while ((length = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, length);
        }
        
        inputStream.close();
        outputStream.close();
        promise.resolve(tempFile.getAbsolutePath());
    } catch (Exception e) {
        promise.reject("COPY_FAILED", e.getMessage());
    }
}
TypeScript调用代码
import { NativeModules } from 'react-native';

const { FileCopierModule } = NativeModules;

async function copySharedPathToUri(contentUri: string) {
  try {
    return await FileCopierModule.copyContentUriToCache(contentUri);
  } catch (error) {
    console.error('文件复制失败:', error);
    throw error;
  }
}

3. 修正Intent URI传递逻辑

确保URI未被错误编码,在+native-intent.ts中添加解码校验:

export function redirectSystemPath({ path, initial }: { path: string; initial: string }) {
  try {
    const decodedPath = decodeURIComponent(path);
    console.log("redirectSystemPath", decodedPath, initial);
    return `/?sharedPath=${encodeURIComponent(decodedPath)}`;
  } catch (err) {
    console.error('路径处理失败:', err);
    return "/";
  }
}

4. 配置必要权限

在app.json中添加Android权限声明(适配Android 13+):

{
  "expo": {
    "android": {
      "permissions": [
        "READ_MEDIA_DOCUMENTS",
        "READ_EXTERNAL_STORAGE"
      ]
    }
  }
}

内容的提问来源于stack exchange,提问作者rudxor02

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 12:51:06