Android Java:通过ActivityResultLauncher选图上传至PHP服务器的最优代码
问题排查与修复方案
核心问题分析
你的代码无法运行的主要原因及其他问题点:
- 内容URI转文件错误:系统返回的图片URI是
content://开头的内容URI,直接调用uri.getPath()无法得到真实文件路径,导致创建的File对象无效,上传时会找不到文件。 - 过时API使用:
RequestBody.create(MediaType, File)方法已被OkHttp标记为过时,且不适用于内容URI场景。 - 资源浪费:每次上传都新建
OkHttpClient实例,应全局复用单例。 - 无错误日志:回调中未添加日志输出,无法定位请求失败的具体原因。
- 权限缺失:Android 6.0+读取媒体文件需要动态申请权限,未处理会导致无法读取图片内容。
修复后的完整代码
1. 全局复用OkHttpClient
private final OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
2. 修正图片选择与上传逻辑
private final ActivityResultLauncher<Intent> pickImageLauncher = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), result -> { if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { Uri imageUri = result.getData().getData(); if (imageUri == null) return; ContentResolver resolver = getContentResolver(); try (InputStream inputStream = resolver.openInputStream(imageUri)) { if (inputStream == null) { Log.e("Upload", "无法打开图片输入流"); return; } // 获取图片原始文件名,或自定义文件名 String fileName = getImageFileName(resolver, imageUri); // 构建基于输入流的RequestBody(避免大图片内存溢出) RequestBody imageBody = new RequestBody() { @Override public MediaType contentType() { return MediaType.parse(resolver.getType(imageUri)); } @Override public void writeTo(@NonNull BufferedSink sink) throws IOException { sink.writeAll(Okio.source(inputStream)); } }; // 构建多表单请求体 MultipartBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("fileToUpload", fileName, imageBody) .build(); // 创建上传请求 Request request = new Request.Builder() .url("https://你的PHP服务器上传接口地址") // 替换为真实接口地址 .post(requestBody) .build(); // 异步执行上传 okHttpClient.newCall(request).enqueue(new Callback() { @Override public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException { if (response.isSuccessful()) { Log.d("Upload", "上传成功,响应内容:" + response.body().string()); // 可在此处添加UI更新逻辑,需切换到主线程 runOnUiThread(() -> { Toast.makeText(getApplicationContext(), "上传成功", Toast.LENGTH_SHORT).show(); }); } else { Log.e("Upload", "上传失败,响应码:" + response.code()); } } @Override public void onFailure(@NonNull Call call, @NonNull IOException e) { Log.e("Upload", "请求失败", e); runOnUiThread(() -> { Toast.makeText(getApplicationContext(), "上传失败,请检查网络", Toast.LENGTH_SHORT).show(); }); } }); } catch (IOException e) { Log.e("Upload", "读取图片失败", e); } } }); // 辅助方法:从ContentResolver获取图片文件名 private String getImageFileName(ContentResolver resolver, Uri uri) { String[] projection = {MediaStore.Images.Media.DISPLAY_NAME}; try (Cursor cursor = resolver.query(uri, projection, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { return cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DISPLAY_NAME)); } } catch (Exception e) { Log.e("Upload", "获取文件名失败", e); } // 失败时使用自定义文件名 return "upload_img_" + System.currentTimeMillis() + ".jpg"; }
3. 权限配置
在AndroidManifest.xml中添加权限声明:
<!-- Android 13+ 读取图片权限 --> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" /> <!-- Android 12及以下 读取外部存储权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
同时在代码中动态申请权限(以Android 13为例):
// 检查权限并申请 if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) { ActivityResultContracts.RequestPermission requestPermission = new ActivityResultContracts.RequestPermission(); registerForActivityResult(requestPermission, isGranted -> { if (isGranted) { // 权限已授予,启动图片选择器 launchImagePicker(); } else { Toast.makeText(this, "需要读取图片权限才能上传", Toast.LENGTH_SHORT).show(); } }).launch(Manifest.permission.READ_MEDIA_IMAGES); } else { launchImagePicker(); } // 启动图片选择器的方法 private void launchImagePicker() { Intent intent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); pickImageLauncher.launch(intent); }
替代实现方式(使用Jetpack的ActivityResultContracts.GetContent)
你可以使用更简洁的ActivityResultContracts.GetContent来替代StartActivityForResult,简化图片选择逻辑:
private final ActivityResultLauncher<String> pickImageLauncher = registerForActivityResult( new ActivityResultContracts.GetContent(), uri -> { if (uri == null) return; // 后续上传逻辑与上述修复代码一致,直接使用uri处理即可 }); // 启动选择器时调用 pickImageLauncher.launch("image/*");
内容的提问来源于stack exchange,提问作者Bosco Tsin
相关产品推荐
相关产品推荐

