Earth Guardian

You are not LATE!You are not EARLY!

0%

Android Camera 简单使用

调用手机自带的 Camera 应用,并获取图片、视频的简单使用步骤。

本文是基于手机系统已经自带 Camera 应用的前提下,通过发出 Intent 请求 Camera 应用响应拍照和录像。

权限

不管是拍照还是录像,都需要申请使用 cameraAndroid M 及以上版本还需要动态申请权限。

AndroidManifest.xml 申请功能

AndroidManifest.xml 文件中申请使用 camera 硬件功能。

1
2
3
4
5
<manifest ... >
<uses-feature android:name="android.hardware.camera"
android:required="true" />
...
</manifest>
  • android:required
    表示手机中必须有 camera 应用软件(如果为 false 的话,表示不需要存在,通常情况下间接表示当前应用已经实现了 camera 功能)。
  • 运行时判断是否支持 camera 硬件功能
    PackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY) 返回当前手机是否支持 camera 硬件。

动态申请权限

  • 检查是否已经授权
    ContextCompat.checkSelfPermission(context, Manifest.permission.CAMERA);
  • 申请权限
    requestPermissions(new String[]{Manifest.permission.CAMERA}, REQUEST_CAMERA_PERMISSION);
  • 确认用户是否授权
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void onRequestPermissionsResult(int requestCode, 
    @NonNull String[] permissions, @NonNull int[] grantResults) {
    if (requestCode == REQUEST_CAMERA_PERMISSION) {
    if (grantResults.length != 1 ||
    grantResults[0] != PackageManager.PERMISSION_GRANTED) {
    ... // show error dialog.
    }
    } else {
    super.onRequestPermissionsResult(requestCode,
    permissions, grantResults);
    }
    }

拍照

Intent

拍照对应的 IntentMediaStore.ACTION_IMAGE_CAPTURE 。通过 Intent 来启动手机中的 camera 应用:

1
2
3
4
5
6
7
8
static final int REQUEST_IMAGE_CAPTURE = 1;

private void dispatchTakePictureIntent() {
Intent takePictureIntent=new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null){
startActivityForResult(takePictureIntent,REQUEST_IMAGE_CAPTURE);
}
}

获取缩略图 thumbnail

手机中的 camera 应用在拍完照后,会返回一个 Intent 数据,其中字段 data 包含了一个 Bitmap 格式的缩略图,通过 ImageView 来显示。

1
2
3
4
5
6
7
8
9
10
@Override
protected void onActivityResult(int requestCode,
int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE
&& resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
mImageView.setImageBitmap(imageBitmap);
}
}

录像

Intent

录像对应的 IntentMediaStore.ACTION_VIDEO_CAPTURE

1
2
3
4
5
6
7
8
static final int REQUEST_VIDEO_CAPTURE = 1;

private void dispatchTakeVideoIntent() {
Intent takeVideoIntent=new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(getPackageManager()) != null){
startActivityForResult(takeVideoIntent,REQUEST_VIDEO_CAPTURE);
}
}

查看视频

手机中的 camera 应用在录像完后,返回的 Intent 中,包含一个录像文件对应的 Uri,根据 Uri 来显示录像,通过 VideoView 来显示。

1
2
3
4
5
6
7
8
9
10
@Override
protected void onActivityResult(int requestCode, int resultCode,
Intent data) {
if (requestCode == REQUEST_VIDEO_CAPTURE
&& resultCode == RESULT_OK) {
Uri videoUri = data.getData();
mVideoView.setVideoURI(videoUri);
// mVideoView.start();
}
}

图片保存及后期处理

外部存储权限

外部存储文件分为:

  • 公共文件:getExternalStoragePublicDirectory()
    可以被其他应用自由访问,应用卸载仍然保留;存储路径为 /sdcard/ 或者 /storage/emulated0/
  • 私有文件:getExternalFilesDir(String customDir)
    应用私有文件,在应用卸载时会被删除;存储路径为 /Android/data/<package_name>/files/customDir

但不管是那种类型,都需要申请权限:

1
2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="18" />

创建应用私有的图片存储文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private File createImageFile() throws IOException {
// Create an image file name
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss")
.format(new Date());
String imageFileName = "JPEG_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, // prefix
".jpg", // suffix
storageDir // directory
);

// Save a file: path for use with ACTION_VIEW intents
mCurrentPhotoPath = image.getAbsolutePath();
return image;
}

应用间文件共享 FileProvider

Android StrictMode 禁止在应用向外部公开 file://URI ,否则应用发出包含文件 URIIntent 时,会抛出 FileUriExposedException 异常。如果需要在应用间共享文件,可以通过 FileProvider 发送 content://URI,并授予 URI 临时访问权限。
AndroidManifest.xml 注册 FileProvider

1
2
3
4
5
6
7
8
9
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.android.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"></meta-data>
</provider>

其中需要注意两点:

  • authorities :在使用 FileProvider.getUriForFile 返回 Uri 时,使用的权限需要和这里匹配
  • file_paths :该 xml 配置文件中需要指定应用需要共享文件的目录,通常为应用私有文件目录
    1
    2
    3
    4
    5
    <?xml version="1.0" encoding="utf-8"?>
    <paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="my_images"
    path="Android/data/com.*.knowledge/files/Pictures" />
    </paths>

保存到指定路径

在发起拍照的 Intent 中,将在外部存储空间存储图片的绝对路径转换为 Uri 后,写入 MediaStore.EXTRA_OUTPUT 字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
//startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
// Create the File where the photo should go
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
}
// Continue only if the File was successfully created
if (photoFile != null) {
Uri photoURI = FileProvider.getUriForFile(this,
"com.*.knowledge.camera.file.provider",
photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT,photoURI);
startActivityForResult(takePictureIntent,REQUEST_IMAGE_CAPTURE);
}
}
}

如果指定了保存路径 MediaStore.EXTRA_OUTPUT ,返回的 Intent 将不再带有 Bitmap 缩略图。如果想显示图片,需要通过指定的保存路径来获取。

添加到媒体数据库

如果新增了多媒体文件,通过发送广播 Intent.ACTION_MEDIA_SCANNER_SCAN_FILE 来通知系统扫描这个文件,并加入多媒体数据库中。

1
2
3
4
5
6
7
8
private void addPicToMediaDB() {
Intent mediaScanIntent =
new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(mCurrentPhotoPath);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
sendBroadcast(mediaScanIntent);
}

如果图片存储路径为 getExternalFilesDir ,媒体扫描时无法扫描这些 APP 私有的目录;也就是说,如果希望能添加到多媒体数据中,不能将图片存储到 getExternalFilesDir 目录下。

缩放图片

如果按照 ImageView 宽高来缩放图片时,必须要在 xml 中先指定 ImageView 的宽高,不能设置为 wrap_content ,否则获取到的宽高为 0 。
通过 BitmapFactory.Options 先获取原始图片的大小,再根据 ImageView 的宽高来计算比率,设置到 BitmapFactory.Options.inSampleSize 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void scalePic() {
// Get the dimensions of the View
int targetW = mImageView.getWidth();
int targetH = mImageView.getHeight();

// Get the dimensions of the bitmap
BitmapFactory.Options bmOptions = new BitmapFactory.Options();
bmOptions.inJustDecodeBounds = true;
BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
int photoW = bmOptions.outWidth;
int photoH = bmOptions.outHeight;

// Determine how much to scale down the image
int scaleFactor = Math.min(photoW/targetW, photoH/targetH);

// Decode the image file into a Bitmap sized to fill the View
bmOptions.inJustDecodeBounds = false;
bmOptions.inSampleSize = scaleFactor;
bmOptions.inPurgeable = true;

Bitmap bitmap = BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
mImageView.setImageBitmap(bitmap);
}

小结

权限

不管是录像还是拍照,使用前需要定义使用 camera 硬件,并动态申请权限。

拍照和录像的 Intent

  • 拍照:MediaStore.ACTION_IMAGE_CAPTURE
  • 录像:MediaStore.ACTION_VIDEO_CAPTURE

返回数据

  • 拍照:返回 Bitmap ,数据在 Intent.Bundle 中的 data 字段
  • 录像:返回 Uri ,数据在 Intent.getData

保存文件及分享

通过 getExternalFilesDir 获取应用的私有文件目录,通过 FileProvider 共享给其他应用。

缩放图片因子

通过设置 BitmapFactory.Options.inSampleSize 的值,来达到缩放图片的效果。

参考文档