Android使用Retrofit上传PDF到PHP服务器遇FileNotFoundException
问题:使用Retrofit上传PDF时出现FileNotFoundException错误
我尝试将多个PDF文件上传到服务器,编写代码后执行时onFailure()持续报错:
java.io.FileNotFoundException: /document/document:1000011388: open failed: ENOENT (No such file or directory)
相关代码
server_interface.java
public interface server_interface { @Multipart @POST("server.php") Call<ResponseBody> uploadMultipleFilesDynamic( @Part List<MultipartBody.Part> files ); }
MainActivity.java(核心逻辑)
server_interface api; @Override protected void onCreate(Bundle savedInstanceState) { List<MultipartBody.Part> parts = new ArrayList<>(); for(Uri uri: list){ parts.add(prepareFilePart(uri)); } OkHttpClient.Builder httpClient = new OkHttpClient.Builder() .callTimeout(2, TimeUnit.MINUTES) .connectTimeout(2, TimeUnit.MINUTES) .readTimeout(3, TimeUnit.MINUTES) .writeTimeout(3, TimeUnit.MINUTES); Retrofit retrofit = new Retrofit.Builder() .baseUrl(server_interface.JSONURL) .addConverterFactory(ScalarsConverterFactory.create()) .client(httpClient.build()) .build(); api = retrofit.create(server_interface.class); Call<ResponseBody> uploadcall = api.uploadMultipleFilesDynamic(parts); if(uploadcall != null){ uploadcall.enqueue(new Callback<ResponseBody>() { @Override public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) { if(response.body() != null) { if (response.isSuccessful()) { Toast.makeText(MainActivity.this, response.body().toString(), Toast.LENGTH_SHORT).show(); } } @Override public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) { Log.d("response",t.toString()); } }); } } @NonNull private MultipartBody.Part prepareFilePart(Uri fileUri) { File file = new File(fileUri.toString()); Log.d("exists", String.valueOf(file.exists())); //显示false RequestBody requestFile = RequestBody.create( MediaType.parse(getContentResolver().getType(fileUri)), file ); return MultipartBody.Part.createFormData("pdf", file.getName(), requestFile); }
server.php
<?php error_reporting(E_ALL); ini_set('display_errors', 1); header('Content-Type: text/plain; charset=utf-8'); header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: POST, GET, OPTIONS'); if(count($_FILES) > 0){ $location = array(); $folder="attachments/"; for($i=0; $i<count($_FILES['file']['name']); $i++){ if(!file_exists($folder.$_FILES['file']['name'][$i])){ $moved = move_uploaded_file($_FILES['file']['tmp_name'][$i],$folder.$_FILES['file']['name'][$i]); if($moved){ if($i != count($_FILES['file']['name'])-1) array_push($location,$folder.$_FILES['file']['name'][$i].';'); else array_push($location,$folder.$_FILES['file']['name'][$i]); } else{ array_push($location,"Unable to upload"); } } else{ if($i != count($_FILES['file']['name'])-1) array_push($location,$folder.$_FILES['file']['name'][$i].';'); else array_push($location,$folder.$_FILES['file']['name'][$i]); } } if(count($location) != 0){ if(!in_array("Unable to upload",$location)) echo json_encode($location); else echo "An attachment wasn't uploaded. Please try again. If issue persists, contact ITC/NFR/MLG"; } else echo "Unable to upload"; } ?>
文件Uri获取代码
ActivityResultLauncher<Intent> intentresultlauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK) { Intent data = result.getData(); if(data != null){ Uri pdf_data = data.getData(); prepareFilePart(pdf_data); } } } ); button.setOnClickListener(view -> { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("application/pdf"); intentresultlauncher.launch(intent); });
解决方案
问题根源
通过Intent.ACTION_OPEN_DOCUMENT获取的Uri是内容提供者(Content Provider)的访问Uri,并非真实文件路径,直接用new File(fileUri.toString())无法定位到文件,导致报错。
修复步骤
1. 修改文件读取逻辑,通过ContentResolver获取文件内容
替换prepareFilePart方法,不要直接创建File对象,而是通过内容解析器读取文件字节流:
@NonNull private MultipartBody.Part prepareFilePart(Uri fileUri) { String fileName = getFileName(fileUri); try { InputStream inputStream = getContentResolver().openInputStream(fileUri); byte[] fileBytes = IOUtils.toByteArray(inputStream); RequestBody requestFile = RequestBody.create( MediaType.parse(getContentResolver().getType(fileUri)), fileBytes ); return MultipartBody.Part.createFormData("pdf", fileName, requestFile); } catch (IOException e) { e.printStackTrace(); return null; } } // 从Uri中提取文件名的辅助方法 private String getFileName(Uri uri) { String result = null; if (uri.getScheme().equals("content")) { Cursor cursor = getContentResolver().query(uri, null, null, null, null); try { if (cursor != null && cursor.moveToFirst()) { result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); } } finally { if (cursor != null) { cursor.close(); } } } if (result == null) { result = uri.getPath(); int cut = result.lastIndexOf('/'); if (cut != -1) { result = result.substring(cut + 1); } } return result; }
需要在build.gradle中添加依赖以使用IOUtils:
implementation 'commons-io:commons-io:2.11.0'
2. 修正服务器端参数名
客户端使用createFormData("pdf", fileName, requestFile),服务器端需对应读取$_FILES['pdf'],修改server.php的循环部分:
for($i=0; $i<count($_FILES['pdf']['name']); $i++){ if(!file_exists($folder.$_FILES['pdf']['name'][$i])){ $moved = move_uploaded_file($_FILES['pdf']['tmp_name'][$i],$folder.$_FILES['pdf']['name'][$i]); if($moved){ if($i != count($_FILES['pdf']['name'])-1) array_push($location,$folder.$_FILES['pdf']['name'][$i].';'); else array_push($location,$folder.$_FILES['pdf']['name'][$i]); } else{ array_push($location,"Unable to upload"); } } else{ if($i != count($_FILES['pdf']['name'])-1) array_push($location,$folder.$_FILES['pdf']['name'][$i].';'); else array_push($location,$folder.$_FILES['pdf']['name'][$i]); } }
3. 确保Uri读取权限(可选)
Android 10及以上无需额外存储权限,但可以在获取Uri后添加持久权限,避免后续访问失效:
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
内容的提问来源于stack exchange,提问作者user21917572
相关产品推荐
相关产品推荐

