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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.18 07:37:11