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

Android Studio(minSdk29)点击按钮上传raw目录MP4至服务器的问题

问题:Android Studio中上传raw目录视频文件失败

问题背景

minSdk版本29的Android Java项目,点击按钮上传raw目录下的.mp4视频到服务器时,使用File类访问文件抛出IOException,错误信息如下:

/2131689473: open failed: ENOENT (No such file or directory)

当前权限配置(AndroidManifest.xml)

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.hardware.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

尝试的代码

private void configureUploadButton() {
        uploadButton = findViewById(R.id.uploadButton);
        uploadButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                // creating a client
                OkHttpClient okHttpClient = new OkHttpClient();

                int videoResourceId = getResources().getIdentifier("name_of_video", "raw", getPackageName());
                Uri fileUri = Uri.parse("android.resource://" + getPackageName() + "/" + videoResourceId);
                Log.i("URI", "File URI: " + fileUri);

                File videoFile = new File(fileUri.getPath());
                Log.i("FILE_TAG", "File: " + videoFile + " Path: " + videoFile.getPath());

                RequestBody requestBody = RequestBody.create(MediaType.parse("video/mp4"), videoFile);
                MultipartBody multipartBody = new MultipartBody.Builder()
                        .setType(MultipartBody.FORM)
                        .addFormDataPart("video", videoFile.getName(), requestBody)
                        .build();

                Request request = new Request.Builder()
                        .url("http://10.0.2.2:5000/upload_video")
                        .post(multipartBody)
                        .build();

                okHttpClient.newCall(request).enqueue(new Callback() {
                    @Override
                    public void onFailure(@NonNull Call call, @NonNull IOException e) {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                Log.i("SERVER_DOWN", "The sever is down: " + e.getMessage());
                                Toast.makeText(Record.this, "server down", Toast.LENGTH_SHORT).show();
                            }
                        });
                    }
                    @Override
                    public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                int responseCode = response.code();
                                String responseMessage = response.message();
                                Log.i("RESPONSE", "Response code: " + responseCode + " Message: " + responseMessage);
                                if (response.isSuccessful()) {
                                    Toast.makeText(Record.this, "Connected to server successfully.", Toast.LENGTH_SHORT).show();
                                } else {
                                    Toast.makeText(Record.this, "Not able to connect to server.", Toast.LENGTH_SHORT).show();
                                }
                            }
                        });
                    }
                });
            }
        });
    }

解决方案

核心问题

raw目录下的资源并非真实的文件系统文件,Uri.getPath()返回的只是资源ID而非真实路径,因此无法通过File类直接访问,导致文件不存在的错误。

修复步骤

  1. 通过资源流读取内容:使用getResources().openRawResource(videoResourceId)获取输入流,将流内容封装成RequestBody上传,无需依赖文件系统路径。
  2. 精简权限:minSdk29及以上,WRITE_EXTERNAL_STORAGE和READ_EXTERNAL_STORAGE权限对内部资源无意义,若没有其他外部存储操作可直接移除。

修改后的代码

private void configureUploadButton() {
    uploadButton = findViewById(R.id.uploadButton);
    uploadButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            OkHttpClient okHttpClient = new OkHttpClient();

            // 获取raw资源ID,校验资源是否存在
            int videoResourceId = getResources().getIdentifier("name_of_video", "raw", getPackageName());
            if (videoResourceId == 0) {
                Toast.makeText(Record.this, "视频资源不存在", Toast.LENGTH_SHORT).show();
                return;
            }

            // 基于资源输入流创建RequestBody
            RequestBody requestBody = RequestBody.create(
                    MediaType.parse("video/mp4"),
                    new InputStreamRequestBody(getResources().openRawResource(videoResourceId))
            );

            // 构建多表单请求,手动指定上传文件名
            MultipartBody multipartBody = new MultipartBody.Builder()
                    .setType(MultipartBody.FORM)
                    .addFormDataPart("video", "name_of_video.mp4", requestBody)
                    .build();

            Request request = new Request.Builder()
                    .url("http://10.0.2.2:5000/upload_video")
                    .post(multipartBody)
                    .build();

            okHttpClient.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(@NonNull Call call, @NonNull IOException e) {
                    runOnUiThread(() -> {
                        Log.e("UPLOAD_ERROR", "上传失败: " + e.getMessage());
                        Toast.makeText(Record.this, "上传失败", Toast.LENGTH_SHORT).show();
                    });
                }

                @Override
                public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
                    runOnUiThread(() -> {
                        int responseCode = response.code();
                        Log.i("UPLOAD_RESPONSE", "响应码: " + responseCode);
                        if (response.isSuccessful()) {
                            Toast.makeText(Record.this, "上传成功", Toast.LENGTH_SHORT).show();
                        } else {
                            Toast.makeText(Record.this, "上传失败,响应码: " + responseCode, Toast.LENGTH_SHORT).show();
                        }
                    });
                }
            });
        }
    });
}

// 自定义RequestBody包装InputStream,实现流式上传
static class InputStreamRequestBody extends RequestBody {
    private final InputStream inputStream;

    InputStreamRequestBody(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    public MediaType contentType() {
        return MediaType.parse("video/mp4");
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            sink.write(buffer, 0, bytesRead);
        }
        inputStream.close();
    }
}

额外说明

  • 手动指定上传文件名(如name_of_video.mp4),因为raw资源没有真实的文件名属性。
  • 增加资源ID校验,避免因资源不存在触发后续错误。
  • 使用Lambda简化UI线程代码,需在build.gradle中配置Java 8支持:
    android {
        compileOptions {
            sourceCompatibility JavaVersion.VERSION_1_8
            targetCompatibility JavaVersion.VERSION_1_8
        }
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 08:30:50