Camera(一)

一引言

Camera做为android手机必备的一款硬件,不管是二维码识别,拍照都离不开Camera。那么我们怎么使用Camera呢?

  • 调用系统的相机
  • 自己定义一个相机

二调用系统相机

调用系统相机其本质是跨应用间调用,所以我们可以通过隐私意图去打开系统相机,核心代码如下:

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent, 100);复制代码

当然高版本有权限问题,我们在调用这段代码之前要确定已经动态获得相机权限,否则会报错:

<uses-permission android:name="android.permission.CAMERA" />复制代码

这个时候我们可以在onActivityResult方法回调中获取返回来的图片信息:

Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
mImageView.setImageBitmap(imageBitmap);复制代码

注意:这里获取图片是通过getExtras获取,并且直接返回的是bitmap缩略图对象。而并未返回图片储存地址的uri。这样方式我们只能获取一次缩略图bitmap对象。我甚至都怀疑google没有物理储存这张拍摄的图片,而只是在内存中储存了缩略图。

且不管到底有没有物理储存,就返回缩略图这种方式而言,都满足不了我们需求。我们要的是原图。我们怎么才能获取拍出来的原图呢?

google给我们提供了能够传递图片储存路径的方法,让我们获取通过路径获取原图:

intent.putExtra(MediaStore.EXTRA_OUTPUT, camera_uri);复制代码

第二个参数需要一个uri,这个uri是通过File转换到:

file2 = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis() + ".jpg");
file2.getParentFile().mkdirs();
//根据路径获取uri
camera_uri = Uri.fromFile(file2);复制代码

当然为了适配高版本限制,我们需要判断大于等于24的版本,通过Provider获取这个uri,最后代码如下:

//创建隐私意图
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
//创建图片储存路径
file2 = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis() + ".jpg");
file2.getParentFile().mkdirs();
//根据路径获取uri
camera_uri = Uri.fromFile(file2);
//如果版本是n,用另外一种方式获取uri
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
    camera_uri = FileProvider.getUriForFile(MainActivity.this, "com.advertising.administrator.camera.FileProvider", file2);
    //添加权限
    intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
//把储存路径传递过去
intent.putExtra(MediaStore.EXTRA_OUTPUT, camera_uri);
startActivityForResult(intent, 100);复制代码

清单配置如下:

<provider
    android:name="android.support.v4.content.FileProvider"
    //注意这里要和代码中字符串一样,最后以FileProvider结尾
    android:authorities="com.advertising.administrator.camera.FileProvider"
    android:exported="false"
    android:grantUriPermissions="true"
    tools:replace="name,authorities,exported,grantUriPermissions">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths"
        tools:replace="name,resource" />复制代码

xml如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <paths>
        <external-path name="camera_photos" path="" />
    </paths>
</resources>复制代码

注意:这里再用上边的方式获取图片地址会报异常。别忘了获取读写外部储存和相机权限,否则会报错。

那么我们该怎么获取呢?

我们知道图片储存路径,就可以在回调中通过路径直接获取图片:

String absolutePath = file2.getAbsolutePath();
mImageView.setImageBitmap(BitmapFactory.decodeFile(absolutePath));复制代码

当然我们也知道储存图片的uri,我们同样可以根据uri去加载图片glide框架支持通过uri去加载图片。

这样我们就能获取原图片。当然有的需求是获取原图,然后裁剪。

那么怎么裁剪呢?

其实裁剪的本质还是通过隐私意图,调用系统的裁剪

Intent intent = new Intent("com.android.camera.action.CROP")
activity.startActivityForResult(intent, requestCode);复制代码

如果需求是裁剪图片再返回,我们应该在第一次回调中开启裁剪的意图,然后第二次接受裁剪后图片的回调,这里我吧裁剪的跳转抽取成方法如下:

public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX, int aspectY, int width, int height, int requestCode) {
    Intent intent = new Intent("com.android.camera.action.CROP");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
    intent.setDataAndType(orgUri, "image/*");
    intent.putExtra("crop", "true");
    intent.putExtra("aspectX", aspectX);
    intent.putExtra("aspectY", aspectY);
    intent.putExtra("outputX", width);
    intent.putExtra("outputY", height);
    intent.putExtra("scale", true);
    //将剪切的图片保存到目标Uri中
    intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);
    intent.putExtra("return-data", false);
    intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
    intent.putExtra("noFaceDetection", true);
    activity.startActivityForResult(intent, requestCode);
}复制代码

我们只需要在第一次回调的时候调用这个静态方法即可:

 case 100://打开系统相机获取图片地址
//把裁剪的路径转换成uri
     fileCropUri  = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis()+ "crop_photo.jpg");
     Uri cropImageUri = Uri.fromFile(fileCropUri);
     //开启裁剪
     cropImageUri(this, camera_uri, cropImageUri, 1, 1, 480, 480, CODE_RESULT_REQUEST);

     break;
 case CODE_RESULT_REQUEST://裁剪的回调
     mImageView.setImageBitmap(BitmapFactory.decodeFile(fileCropUri.getAbsolutePath()));
     break;复制代码

这样我们就可以通过copy的File或者uri去获取Copy的图片。

不知道你有没有发现一个问题,我们拍的照片并未在手机相册中显示,如果我们想让拍摄的相片原图和缩略图都在手机系统相册中显示,我们应该怎么办呢?

先来解释一下为什么没有显示,原因是因为拍摄好的照片并未通知系统数据库,让其更新。

那么,怎么通知系统更新呢?

这里就需要用到广播:

private void galleryAddPic(File file) {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    //file即文件的路径
    Uri contentUri = Uri.fromFile(file);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}复制代码

在每次回调地方添加上这个方法,即可让裁剪图和原图都在系统相册中显示。通知数据类添加一个uri。

之后就是奉上完整代码:

请求相机代码:

//请求权限
new RxPermissions(this)
        .request(Manifest.permission.CAMERA,Manifest.permission.WRITE_EXTERNAL_STORAGE)
        .subscribe(new Consumer<Boolean>() {
            @Override
            public void accept(Boolean aBoolean) throws Exception {
                if (aBoolean) {
                    //创建隐私意图
                    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                    //创建图片储存路径
                    file2 = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis() + ".jpg");
                    file2.getParentFile().mkdirs();
                    //根据路径获取uri
                    camera_uri = Uri.fromFile(file2);
                    //如果版本是大于等于n,用另外一种方式获取uri
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        camera_uri = FileProvider.getUriForFile(MainActivity.this, "com.advertising.administrator.camera.FileProvider", file2);
                        //添加权限
                        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    }
                    //把储存路径传递过去
                    intent.putExtra(MediaStore.EXTRA_OUTPUT, camera_uri);
                    startActivityForResult(intent, 100);
                }
            }
        });复制代码

回调处理代码:

 @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            switch (requestCode) {
                case 100://打开系统相机获取图片回调
                    //通知数据库,让其在系统相册中显示图片
                    galleryAddPic(file2);
                    //获取原图展示
//                    String absolutePath = file2.getAbsolutePath();
//                    mImageView.setImageBitmap(BitmapFactory.decodeFile(absolutePath));

//                    //把裁剪的路径转换成uri
                    fileCropUri = new File(Environment.getExternalStorageDirectory().getPath() + "/zjyl/" + System.currentTimeMillis() + "crop_photo.jpg");
                    Uri cropImageUri = Uri.fromFile(fileCropUri);
                    //开启裁剪
                    cropImageUri(this, camera_uri, cropImageUri, 1, 1, 480, 480, CODE_RESULT_REQUEST);

                    break;
                case CODE_RESULT_REQUEST://裁剪的回调
                    //通知数据库,让其在系统相册中显示图片
                    galleryAddPic(fileCropUri);
                    //获取裁剪后的图片展示
                    mImageView.setImageBitmap(BitmapFactory.decodeFile(fileCropUri.getAbsolutePath()));

                    break;

                default:
            }

        }
    }复制代码

通知数据库代码:

private void galleryAddPic(File file) {
    Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
    //mCurrentPhotoPath即文件的路径
    Uri contentUri = Uri.fromFile(file);
    mediaScanIntent.setData(contentUri);
    this.sendBroadcast(mediaScanIntent);
}复制代码

开启裁剪代码:

public static void cropImageUri(Activity activity, Uri orgUri, Uri desUri, int aspectX, int aspectY, int width, int height, int requestCode) {
    Intent intent = new Intent("com.android.camera.action.CROP");
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }
    intent.setDataAndType(orgUri, "image/*");
    intent.putExtra("crop", "true");
    intent.putExtra("aspectX", aspectX);
    intent.putExtra("aspectY", aspectY);
    intent.putExtra("outputX", width);
    intent.putExtra("outputY", height);
    intent.putExtra("scale", true);
    //将剪切的图片保存到目标Uri中
    intent.putExtra(MediaStore.EXTRA_OUTPUT, desUri);
    intent.putExtra("return-data", false);
    intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
    intent.putExtra("noFaceDetection", true);
    activity.startActivityForResult(intent, requestCode);
}复制代码

调用相机基本就这些用法和适配。

三打开系统相册

上边已经介绍了调用系统相机的一些用法,本来笔者不打算介绍打开相册,但是写到这里,篇幅很尴尬,如果直接去介绍自定义相机,文章会变得特别长,于是决定介绍一下常常伴随着调用启动相机的功能,打开系统相册。

打开系统相册是跨应用之间共享数据库,还是需要隐私意图,去开启相册,代码如下:

Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
// 如果限制上传到服务器的图片类型时可以直接写如:"image/jpeg 、 image/png等的类型" 所有类型则写 "image/*
photoPickerIntent.setType("image/*");
startActivityForResult(photoPickerIntent, 200);复制代码

注意:需要有读取外部储存的权限,否则回调的时候会出错。

接下来就是看回调处理了,我们在回调中可以通过data获取图片的uri。

Uri uri = data.getData();复制代码

这个返回的uri在4.4之前是:

content://media/external/images/media/20复制代码

4.4之后是:

content://com.android.providers.media.documents/document/image%3A23606复制代码

你会发现4.4之后给的uri好像被加密处理一样。我们没有办法像4.4之前通过查询数据库去获取图片绝对路径:

Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor != null) {
    if (cursor.moveToFirst()) {
        path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
    }
}复制代码
需要把传递过来的uri解密一下,才能去获取path。最后关于uri的处理如下:
public static String getPath(final Context context, final Uri uri) {
    //判断版本是否大于18
    final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
    //初始化一个字符串
    String pathHead = "file://";
    // 判断4.4之后的版本
    if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
        // ExternalStorageProvider,解析出来路径返回
        if (isExternalStorageDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];
            if ("primary".equalsIgnoreCase(type)) {
                return pathHead + Environment.getExternalStorageDirectory() + "/" + split[1];
            }
        }
        // DownloadsProvider,下载路径
        else if (isDownloadsDocument(uri)) {
            final String id = DocumentsContract.getDocumentId(uri);
            final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
            return pathHead + getDataColumn(context, contentUri, null, null);

        }
        // MediaProvider,多媒体
        else if (isMediaDocument(uri)) {
            final String docId = DocumentsContract.getDocumentId(uri);
            final String[] split = docId.split(":");
            final String type = split[0];

            Uri contentUri = null;
            if ("image".equals(type)) {
                contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
            } else if ("video".equals(type)) {
                contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
            } else if ("audio".equals(type)) {
                contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
            }

            final String selection = "_id=?";
            final String[] selectionArgs = new String[]{split[1]};
            return pathHead + getDataColumn(context, contentUri, selection, selectionArgs);
        }
    }
    // 4.4之前的版本
    else if ("content".equalsIgnoreCase(uri.getScheme())) {
        return pathHead + getDataColumn(context, uri, null, null);
    }
    // 4.4之前的版本,以File开头
    else if ("file".equalsIgnoreCase(uri.getScheme())) {
        return pathHead + uri.getPath();
    }
    return null;
}

/**
 * @param uri The Uri to check.
 * @return 如果是外部储存
 */
private static boolean isExternalStorageDocument(Uri uri) {
    return "com.android.externalstorage.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is DownloadsProvider.
 */
private static boolean isDownloadsDocument(Uri uri) {
    return "com.android.providers.downloads.documents".equals(uri.getAuthority());
}

/**
 * @param uri The Uri to check.
 * @return Whether the Uri authority is MediaProvider.
 */
private static boolean isMediaDocument(Uri uri) {
    return "com.android.providers.media.documents".equals(uri.getAuthority());
}

/**
 * 通过内容提供者,获取图片的绝对路径
 *
 * @param context
 * @param uri
 * @param selection
 * @param selectionArgs
 * @return
 */
private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {

    Cursor cursor = null;
    final String column = "_data";
    final String[] projection = {column};
    try {
        cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
        if (cursor != null && cursor.moveToFirst()) {
            final int column_index = cursor.getColumnIndexOrThrow(column);
            return cursor.getString(column_index);
        }
    } finally {
        if (cursor != null) {
            cursor.close();
        }

    }
    return null;
}复制代码

这样我们就可以获取图片的路径:

Uri uri = data.getData();
String path = PhotoUtils.getPath(this, uri);
path = path.substring(7, path.length());
image.setImageBitmap(BitmapFactory.decodeFile(path));复制代码

这里之所以需要截取前七个字符,是因为绝对路径前边不需要file:///开头

还有裁剪和通知数据库更新的代码和拍照的代码一样的实现。

注意7.0后禁止在您的应用外部公开 file:// URI。

之前,对于开启系统相机和打开相册笔者一直迷糊,迷糊的原因是:系统4.4之后对uri进行加密需要翻译一下,和7.0不能外部公开 file:// URI混为一谈。以为是一回事。下一篇,开始进入自定义相机。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值