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类直接访问,导致文件不存在的错误。
修复步骤
- 通过资源流读取内容:使用
getResources().openRawResource(videoResourceId)获取输入流,将流内容封装成RequestBody上传,无需依赖文件系统路径。 - 精简权限: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
相关产品推荐
相关产品推荐

