Android相机意图: 如何获取全尺寸照片?

67

我正在使用Intent来启动相机:

Intent cameraIntent = new Intent(
    android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
getParent().startActivityForResult(cameraIntent, CAMERA_PIC_REQUEST);

并且使用:

Bitmap thumbnail = (Bitmap) data.getExtras().get("data");
photoImage.setImageBitmap(thumbnail);
photoImage.setVisibility(View.VISIBLE);

但这只是一个缩略图,我该如何获取完整的位图?我知道可以使用自己的Activity并使用:

Camera.PictureCallback()

但是有没有使用Intent的方法可以实现呢?

谢谢!

编辑:

我也尝试过:

public void onActivityResult(int requestCode, int resultCode, Intent data) {
    Uri uri = data.getData();
    imageView.setImageURI(uri);
}

对于从相册选择的照片可以正常工作,但是对于相机意图,data.getData()返回空。

8个回答

110
为了获取全尺寸的相机图像,您应该将相机指向保存在临时文件中的图片,例如:
    private URI mImageUri;

    Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
    File photo;
    try
    {
        // place where to store camera taken picture
        photo = this.createTemporaryFile("picture", ".jpg");
        photo.delete();
    }
    catch(Exception e)
    {
        Log.v(TAG, "Can't create file to take picture!");
        Toast.makeText(activity, "Please check SD card! Image shot is impossible!", 10000);
        return false;
    }
    mImageUri = Uri.fromFile(photo);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
    //start camera intent
    activity.startActivityForResult(this, intent, MenuShootImage);

private File createTemporaryFile(String part, String ext) throws Exception
{
    File tempDir= Environment.getExternalStorageDirectory();
    tempDir=new File(tempDir.getAbsolutePath()+"/.temp/");
    if(!tempDir.exists())
    {
        tempDir.mkdirs();
    }
    return File.createTempFile(part, ext, tempDir);
}

当图像捕获意图完成工作后 - 只需使用以下代码从imageUri获取您的图片:

public void grabImage(ImageView imageView)
{
    this.getContentResolver().notifyChange(mImageUri, null);
    ContentResolver cr = this.getContentResolver();
    Bitmap bitmap;
    try
    {
        bitmap = android.provider.MediaStore.Images.Media.getBitmap(cr, mImageUri);
        imageView.setImageBitmap(bitmap);
    }
    catch (Exception e)
    {
        Toast.makeText(this, "Failed to load", Toast.LENGTH_SHORT).show();
        Log.d(TAG, "Failed to load", e);
    }
}


//called after camera intent finished
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent)
{
    //MenuShootImage is user defined menu option to shoot image
    if(requestCode==MenuShootImage && resultCode==RESULT_OK) 
    {
       ImageView imageView;
       //... some code to inflate/create/find appropriate ImageView to place grabbed image
       this.grabImage(imageView);
    }
    super.onActivityResult(requestCode, resultCode, intent);
}

P.S. 在 Android M 中应用了新的安全限制,因此代码需要进行修改 - FileProvider: mImageUri 必须与 FileProvider 一起使用。


1
@barmaley,感谢您解决了hzxu的问题,但是我对您的答案感到困惑。那段代码不能直接使用。您能否重新格式化并解释一下“grabImage”如何适用于onActivityResult过程?谢谢! - shanabus
5
想要指出的是上面的重要代码行是 intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);,请参考 http://developer.android.com/reference/android/provider/MediaStore.html#ACTION_IMAGE_CAPTURE 以获得关于此行为的难以捉摸的文档说明。 - Cheezmeister
5
我尝试了这个解决方案,但图像处理的结果出现了错误,错误信息如下:java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=188, result=-1, data=null} to activity {com.ketanolab.lbc/com.ketanolab.lbc.Robototal}: java.lang.NullPointerException。请帮我看看。 - Pedro Teran
这些文件会保留在临时目录中吗?我该如何删除它们? - Sudhir Singh Khanger
是的,文件将保留在临时目录中,但对于图库来说它将是不可见的。要删除它,只需删除目录:Environment.getExternalStorageDirectory()+"/.temp/" - Barmaley
显示剩余9条评论

44

打开相机并将图像保存到某个特定目录

private String pictureImagePath = "";
private void openBackCamera() {
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    String imageFileName = timeStamp + ".jpg";
    File storageDir = Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_PICTURES);
    pictureImagePath = storageDir.getAbsolutePath() + "/" + imageFileName;
    File file = new File(pictureImagePath);
    Uri outputFileUri = Uri.fromFile(file);
    Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);               
    cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
    startActivityForResult(cameraIntent, 1);
}

处理图片

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == 1) {
    File imgFile = new  File(pictureImagePath);
        if(imgFile.exists()){        
       Bitmap myBitmap = BitmapFactory.decodeFile(imgFile.getAbsolutePath());
       ImageView myImage = (ImageView) findViewById(R.id.imageviewTest);
       myImage.setImageBitmap(myBitmap);

        }
    }

}

6
搜索了互联网24小时后,这是唯一做我想做的事情的代码。现在唯一的问题是,即使我以肖像模式拍摄照片,它仍然显示为横向模式。 - Srujan Barai
7
也是唯一一个正在工作的......我真的不明白为什么这样简单的任务会变得如此复杂。 - Igor Vuković
2
请记得您需要 WRITE_EXTERNAL_STORAGE 权限。此答案中的代码来自 https://developer.android.com/training/camera/photobasics.html。 - patraulea
1
好的代码简单易懂,其他人写的代码太过复杂。 - Hitesh Danidhariya
人们总是用冗长的回答把事情搞得很复杂。我面临的一个问题是,每次点击后我都会回到活动页面,我不能同时点击多个图像。有什么好的想法吗?感谢您提供这个伟大的代码。 - Panache
显示剩余5条评论

6
即使这是一个老问题并且已经有了接受的答案,我仍然想分享我的解决方案。在这种情况下,您不必创建临时文件。此外,我们创建了一个选择器,为用户提供两个选项:使用相机拍摄照片或从图库中选择现有照片。
    Intent galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    Intent chooser = new Intent(Intent.ACTION_CHOOSER);
    chooser.putExtra(Intent.EXTRA_INTENT, galleryIntent);
    chooser.putExtra(Intent.EXTRA_TITLE, getString(R.string.chooseaction));
    Intent[] intentArray = {cameraIntent};
    chooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
    startActivityForResult(chooser, RESULT_LOAD_IMAGE);

现在我们要获取结果:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    // todo use appropriate resultCode in your case
    if (requestCode == RESULT_LOAD_IMAGE && resultCode == FragmentActivity.RESULT_OK) {
        if (data.getData() != null) {
            // this case will occur in case of picking image from the Gallery,
            // but not when taking picture with a camera
            try {
                Bitmap bitmap = MediaStore.Images.Media.getBitmap(getActivity().getContentResolver(), data.getData());

                // do whatever you want with the Bitmap ....           

            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            // this case will occur when taking a picture with a camera
            Bitmap bitmap = null;
            Cursor cursor = getActivity().getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
                    new String[]{MediaStore.Images.Media.DATA, MediaStore.Images.Media.DATE_ADDED,
                            MediaStore.Images.ImageColumns.ORIENTATION}, MediaStore.Images.Media.DATE_ADDED,
                    null, "date_added DESC");
            if (cursor != null && cursor.moveToFirst()) {
                Uri uri = Uri.parse(cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA)));
                String photoPath = uri.toString();
                cursor.close();
                if (photoPath != null) {
                    bitmap = BitmapFactory.decodeFile(photoPath);
                }
            }

            if (bitmap == null) {
                // for safety reasons you can
                // use thumbnail if not retrieved full sized image
                bitmap = (Bitmap) data.getExtras().get("data");
            }
            // do whatever you want with the Bitmap ....
        }

        super.onActivityResult(requestCode, resultCode, data);
    }
}

这是一种非常好的方式,因为它不需要读取外部存储权限。同时,本地相机或图库选择器也是用户完成操作的舒适方式。 - Ragaisis
3
这和许多其他答案一样,返回相机中的最后一个相册图片而不是照片。在API 23模拟器和API 21设备上进行了测试。Uri引用的路径与原始照片不同。另外,数据为null。 - CoolMind
尝试一下:如果我选择画廊,一切都按照描述的那样工作。但是,如果我选择相机,则resultCode == REQUEST_CANCELLED。 - jwehrle

2
不要使用onActivityResult的数据。我花了很多时间测试不同的解决方案。相机会保存一张照片(即使您没有在AndroidManifest中设置相机和读取卡的权限),但是onActivityResult返回data == nullMediaStore返回错误的路径。在这些解决方案中,您只能获取最后一个图库图片,而不是您的照片。
private Uri photoUri;

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    ...
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);

    if (requestCode == CAMERA_RESULT) {
        if (resultCode == RESULT_OK) {
            if (photoUri != null) {
                image.setImageURI(photoUri);
            }
        }
    }
}

private void showCamera() {
    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (intent.resolveActivity(getContext().getPackageManager()) != null) {
        File file = null;
        try {
            file = createImageFile();
        } catch (IOException e) {
            e.printStackTrace();
        }
        photoUri = null;
        if (file != null) {
            photoUri = Uri.fromFile(file);
            intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
            startActivityForResult(intent, CAMERA_REQUEST);
        }
    }
}

private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
    File storageDir = getContext().getExternalFilesDir(Environment.DIRECTORY_PICTURES);
    // File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
    return File.createTempFile(timeStamp, ".jpg", storageDir);
}

真的没有点赞吗?与所有其他解决方案相比,这是最好的解决方案... - DIRTY DAVE
@DIRTYDAVE,非常感谢!我很高兴这个解决方案对你有帮助! - CoolMind

2

好的,是时候偿还了。 所以你在清单上有你的权限。

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-feature android:name="android.hardware.camera" android:required="true" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

而且您还有提供者元数据。

 <provider
        android:name="androidx.core.content.FileProvider"
        android:authorities="${applicationId}.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths"></meta-data>
    </provider>

一个遗漏的细节是(android:authorities applicationId),您需要添加自己的应用程序包名称。 因此,在我们在manifest中提到的res文件夹下有xml文件,并且在其下面创建了file_paths,以分号分隔。
<?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>

我们已经完成了复制粘贴的第一部分。现在在我们上面的活动onCreate(savedInstanceState: Bundle?)中定义这些美丽的内容。
    val REQUEST_IMAGE_CAPTURE = 1
    lateinit var currentPhotoPath: String
    var cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)

您可能想查看原始资源,但通常会缺少细节,Android开发人员:link 另一个缺失的细节是packageName + ".fileprovider",如果您已经有方法,请注意需要提供自己的包名称。
// org android developers
    private fun dispatchTakePictureIntent() {
        Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
            // Ensure that there's a camera activity to handle the intent
            takePictureIntent.resolveActivity(packageManager)?.also {
                // Create the File where the photo should go
                val photoFile: File? = try {
                    createImageFile()
                } catch (ex: IOException) {
                    // Error occurred while creating the File
                    ex.message
                    null
                }
                // Continue only if the File was successfully created
                photoFile?.also {
                    val photoURI: Uri = FileProvider.getUriForFile(
                        this,
                        packageName + ".fileprovider",
                        it
                    )
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                    startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
                }
            }
        }
    }

createImageFile函数

 // org android developers
    @Throws(IOException::class)
    private fun createImageFile(): File {
        // Create an image file name
        val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        val storageDir: File = this!!.getExternalFilesDir(Environment.DIRECTORY_PICTURES)!!
        return File.createTempFile(
            "JPEG_${timeStamp}_", /* prefix */
            ".jpg", /* suffix */
            storageDir /* directory */
        ).apply {
            // Save a file: path for use with ACTION_VIEW intents
            currentPhotoPath = absolutePath
        }

    }

测试。 使用onClick事件调用您的dispatchTakePictureIntent()方法,确保权限已被允许。

 @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
  if (resultCode == Activity.RESULT_OK && requestCode == REQUEST_IMAGE_CAPTURE) {
var  mBitmap_org = MediaStore.Images.Media.getBitmap(
                this.getContentResolver(),
                //Uri.parse(currentPhotoPath)
                Uri.fromFile(File(currentPhotoPath))
            );
        //mImg_display?.setImageBitmap(mBitmap_org)
}
    }

请勿检查数据,我们将通过imagePath获取数据。如果您正在检查Uri.parse(currentPhotoPath),请确保它是Uri.fromFile(File(currentPhotoPath))
现在您已经拥有了位图,可以花费其他几个小时/天来调整大小、解码和保存。
还有一种保存tokken图片的方法,也许您可以帮我看看应该把它放在哪里,如果我需要将tokken图片保存在相册中。
  // org android developers
    private fun galleryAddPic() {
        Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).also { mediaScanIntent ->
            val f = File(currentPhotoPath)
            mediaScanIntent.data = Uri.fromFile(f)
            sendBroadcast(mediaScanIntent)
        }
    }

2

我也使用了Vicky的答案,但是我不得不将uri保存到一个bundle中,以避免在方向改变时丢失它。所以如果您在倾斜设备后没有从意图获得结果,那么可能是因为您的uri没有经过方向更改而幸存下来。

static final int CAMERA_CAPTURE_REQUEST = 1;
static final String ARG_CURRENT_PIC_URI = "CURRENT_PIC_URI";


String pictureImagePath = folderName + "/" + imageFileName;
File file = new File(Environment.getExternalStorageDirectory(), pictureImagePath);
Uri outputFileUri = Uri.fromFile(file);

mCurrentPicUri = outputFileUri.getPath();

Intent cameraIntent = new Intent(android.provider.MediaStore.ACTION_IMAGE_CAPTURE);
cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, outputFileUri);
startActivityForResult(cameraIntent, CAMERA_CAPTURE_REQUEST);

活动结果代码:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {

  if (requestCode == CAMERA_CAPTURE_REQUEST && resultCode == Activity.RESULT_OK) 
  {
    File imgFile = new  File(mCurrentPicUri);
    // do something with your image
    // delete uri
    mCurrentPicUri = "";
  }
}

将URI保存到Bundle中:

@Override
public void onSaveInstanceState(Bundle outState) {
  super.onSaveInstanceState(outState);
  // save uri to bundle
  outState.putString(ARG_CURRENT_PIC_URI, mCurrentPicUri);
}

在 onCreate 期间从保存的捆绑包中检索它:

if (bundle.containsKey(ARG_CURRENT_PIC_URI))
  mCurrentPicUri = bundle.getString(ARG_CURRENT_PIC_URI);

1

API 级别 29

我尝试了被接受的答案,但是在 API 级别 29 中,被接受的答案中使用的 Environment.getExternalStorageDirectory() 和后续答案中使用的 Environment.getExternalStoragePublicDirectory() 都已被弃用。第三个答案中使用的 MediaStore.Images.Media.DATA 也在 API 级别 29 中被弃用。以下 Kotlin 代码将完整图像存储在 MediaStore 中,如 this stackoverflow answer 所示,而不依赖于 FileProvider

    var imageUri: Uri? = null

    fun takePhoto(view: View?) {
        val values = ContentValues()
        values.put(MediaStore.MediaColumns.DISPLAY_NAME, "myimage.jpg")
        values.put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
        values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
        imageUri = contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values)

        val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri)
        startActivityForResult(intent, TAKE_PHOTO_REQUEST)
    }

但是MediaStore.MediaColumns.RELATIVE_PATH也已经被弃用了。 - 林果皞
尝试使用以下代码:imageUri = contentResolver.insert( MediaStore.Images.Media.EXTERNAL_CONTENT_URI, ContentValues().apply { put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES) }) 在 API Level 33 上仍然有效。 - Andy Hill

0
为了从相机中获取最大的图片尺寸,希望以下这些简单的步骤对你有帮助。
 public static Camera mCamera;

Camera.Parameters parameters = mCamera.getParameters();
  parameters.getSupportedPictureSizes();
List<Camera.Size> supportedSizes = parameters.getSupportedPictureSizes();
  mSizePicture1 = supportedSizes.get(0);
  int cameraSize = supportedSizes.size();
  mSizePicture2 = supportedSizes.get(cameraSize - 1);

    if (mSizePicture1.height < mSizePicture2.height)
       mSizePicture = supportedSizes.get(cameraSize - 1);
    else
       mSizePicture = supportedSizes.get(0);

parameters.setPictureSize(mSizePicture.width, mSizePicture.height);

在这里,我们获取每个移动设备支持的尺寸,从中选择最大的尺寸作为拍摄照片的固定尺寸。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接