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

Android相机拍照后通过Retrofit Multipart上传异常问题排查

问题根源分析

从错误日志能看出这是连锁问题导致的:

  1. 首先触发SecurityException:你的应用没有获得WRITE_EXTERNAL_STORAGE的实际授权,导致调用MediaStore.Images.Media.insertImage失败,返回的path为null
  2. 接着Uri.parse(path)因为传入null,直接抛出NullPointerException,也就是你看到的uriString空指针

接下来咱们一步步解决这些问题:

1. 补充动态存储权限申请

Android 6.0(API 23)及以上版本,像WRITE_EXTERNAL_STORAGE这类危险权限,只在Manifest里声明是不够的,必须在运行时主动向用户申请权限。

你可以在启动相机前先检查权限状态:

private static final int REQUEST_STORAGE_PERMISSION = 1001;
private static final int CAMERA_REQUEST = 67424;

// 启动相机前先检查权限
private void startCamera() {
    if (ContextCompat.checkSelfPermission(getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE)
            != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(getActivity(),
                new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                REQUEST_STORAGE_PERMISSION);
    } else {
        // 权限已授权,启动相机
        launchCameraIntent();
    }
}

// 处理权限申请结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == REQUEST_STORAGE_PERMISSION) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            launchCameraIntent();
        } else {
            Toast.makeText(getActivity(), "需要存储权限才能保存图片", Toast.LENGTH_SHORT).show();
        }
    }
}

// 启动相机的Intent
private void launchCameraIntent() {
    Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
    startActivityForResult(cameraIntent, CAMERA_REQUEST);
}

2. 优化图片文件处理逻辑(避免依赖MediaStore)

其实还有更稳妥的方式:直接将Bitmap保存到应用私有目录,这个目录不需要存储权限(Android 10+),还能避免MediaStore插入失败的问题。

替换你原来的getImageUri和getRealPathFromURI方法,改成直接保存Bitmap到文件:

private File saveBitmapToFile(Bitmap bitmap) throws IOException {
    // 创建应用私有临时文件
    File tempFile = File.createTempFile("camera_photo", ".jpg", getActivity().getCacheDir());
    
    // 将Bitmap写入文件
    FileOutputStream fos = new FileOutputStream(tempFile);
    bitmap.compress(Bitmap.CompressFormat.JPEG, 90, fos);
    fos.flush();
    fos.close();
    
    return tempFile;
}

然后在onActivityResult里直接使用这个方法:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == CAMERA_REQUEST && resultCode == Activity.RESULT_OK) {
        try {
            final Bitmap photo = (Bitmap) data.getExtras().get("data");
            // 直接保存Bitmap到私有目录文件
            File file = saveBitmapToFile(photo);
            
            MyWebService webService = MyWebService.retrofit.create(MyWebService.class);
            MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), 
                    RequestBody.create(MediaType.parse("image/jpeg"), file));
            Call<com.syamantak.footsteps.models.response.Response> call = webService.set_profile_photo(filePart, "Bearer " + MainActivity.api_token);
            
            MainActivity.setProgressBarVisible();
            call.enqueue(new Callback<com.syamantak.footsteps.models.response.Response>() {
                @Override
                public void onResponse(Call<com.syamantak.footsteps.models.response.Response> call, Response<com.syamantak.footsteps.models.response.Response> response) {
                    MainActivity.setProgressBarInVisible();
                    if (response.isSuccessful() && response.body() != null) {
                        com.syamantak.footsteps.models.response.Response jsonResponse = response.body();
                        selfieImage.setImageBitmap(photo);
                        Toast.makeText(getActivity(), jsonResponse.getMessage(), Toast.LENGTH_SHORT).show();
                    }
                }
                
                @Override
                public void onFailure(Call<com.syamantak.footsteps.models.response.Response> call, Throwable t) {
                    MainActivity.setProgressBarInVisible();
                    Toast.makeText(getActivity(), "上传失败:" + t.getLocalizedMessage(), Toast.LENGTH_SHORT).show();
                    t.printStackTrace();
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(getActivity(), "保存图片失败", Toast.LENGTH_SHORT).show();
        }
    }
}

3. 进阶优化:获取相机拍摄的原图(可选)

你当前从data.getExtras().get("data")获取的是相机返回的缩略图,分辨率很低。如果需要上传原图,可以指定相机拍照的输出Uri,这样直接获取完整图片文件:

private Uri cameraOutputUri;

private void launchCameraIntent() {
    Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    // 创建保存原图的文件
    File photoFile = null;
    try {
        photoFile = createImageFile();
    } catch (IOException ex) {
        Toast.makeText(getActivity(), "创建图片文件失败", Toast.LENGTH_SHORT).show();
        return;
    }
    if (photoFile != null) {
        cameraOutputUri = FileProvider.getUriForFile(getActivity(),
                "com.syamantak.footsteps.fileprovider", // 替换成你的FileProvider authority
                photoFile);
        cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraOutputUri);
        startActivityForResult(cameraIntent, CAMERA_REQUEST);
    }
}

// 创建原图文件
private File createImageFile() throws IOException {
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = getActivity().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    return File.createTempFile(
            imageFileName,  /* 前缀 */
            ".jpg",         /* 后缀 */
            storageDir      /* 存储目录 */
    );
}

然后在onActivityResult里直接使用cameraOutputUri对应的文件,不需要处理Bitmap:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == CAMERA_REQUEST && resultCode == Activity.RESULT_OK) {
        // 直接获取原图文件
        File file = new File(getRealPathFromURI(cameraOutputUri));
        
        // 后续上传逻辑和之前一致...
    }
}

注意:使用FileProvider需要在Manifest里配置:

<application>
    ...
    <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="com.syamantak.footsteps.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>
</application>

然后在res/xml目录下创建file_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="my_images" path="Pictures" />
</paths>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.13 08:27:31