Android相机拍照后通过Retrofit Multipart上传异常问题排查
问题根源分析
从错误日志能看出这是连锁问题导致的:
- 首先触发
SecurityException:你的应用没有获得WRITE_EXTERNAL_STORAGE的实际授权,导致调用MediaStore.Images.Media.insertImage失败,返回的path为null - 接着
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
相关产品推荐
相关产品推荐

