Android相机<第一篇>:启动系统相机

启动相机有两种方式:[不指定路径][指定路径]

[第一种]:不指定路径

这种方式是最简单的一种方式,代码如下:

            //启动系统相机
            Intent intent = new Intent();
            intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
            startActivityForResult(intent, REQUEST_CODE);

即用Intent启动系统相机,且指定action为MediaStore.ACTION_IMAGE_CAPTURE

当拍完照关闭或者直接关闭系统相机页面时,此时开始执行onActivityResult方法,接收相机返回数据,数据是通过Intent方式传递的,onActivityResult方法有三个参数,分别是requestCoderesultCodedata

  • requestCode:请求码
  • resultCode:响应码
  • data:数据

代码如下:

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

    if(requestCode == REQUEST_CODE && resultCode == RESULT_OK){
        if(data != null && data.hasExtra("data")){
            Bitmap bitmap = data.getParcelableExtra("data");
            imageview.setImageBitmap(bitmap);
        }
    }
}

由于请求系统相机时没有指定路径,所以在Intent里拿到的数据是Bitmap,这个Bitmap的分辨率大小是由系统相机决定的,为了防止内存的浪费,一般系统相机默认返回的Bitmap是特别特别小的。

如图:

图片.png

当然,Bitmap较小也为我们提供一个思路,这种方式可以用于设置头像或者设置缩略图。

由于Bitmap分辨率比较小,基本很难满足项目的大部分需求,显示图片过小或者不清晰,为了解决这个问题,则需要指定图片路径,将拍照的图片保存到本地。

指定文件目录的代码很简单,如下:

文件写权限:

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

调用系统相机代码:

    //获得项目缓存路径
    String filePath = Environment.getExternalStorageDirectory() + File.separator + "1" + File.separator;

    //如果目录不存在则必须创建目录
    File cameraFolder = new File(filePath);
    if (!cameraFolder.exists()) {
        cameraFolder.mkdirs();
    }

    //根据时间随机生成图片名
    String photoName = new DateFormat().format("yyyyMMddhhmmss", Calendar.getInstance(Locale.CHINA)) + ".jpg";
    filePath = filePath + photoName;

    File mOutImage = new File(filePath);

    mImageUri = Uri.fromFile(mOutImage);

    //启动相机
    Intent intent = new Intent();
    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
    startActivityForResult(intent, REQUEST_CODE);

onActivityResult处理代码如下:

    if(requestCode == REQUEST_CODE && resultCode == RESULT_OK){

        if(mImageUri != null){
            Glide.with(MainActivity.this)
                    .asBitmap()
                    .load(mOutImage)
                    .diskCacheStrategy(DiskCacheStrategy.NONE)
                    .into(imageview);
        }

    }

需要注意的是,保存照片的目录必须存在,如果不存在相机返回时本地没有图片,甚至有些手机的相机根本无法返回。

以上代码支持6.x或6.x以下的手机,在7.0或7.0以上的手机则无效,所以还需要对7.0以上的手机做下适配。

如果没有针对Android 7.0做处理,那么可能会出现以下问题。

图片.png
图片.png

Android7.0中尝试传递file:///开头的URI 会触发FileUriExposedException,因为在Android7.0之后Google认为直接使用本地的根目录即file:///作为URI是不安全的操作,直接访问会抛出FileUriExposedExCeption异常,那么如何解决这个问题呢?

Android7.0为我们提供了“file:///”向FileProvider转化的方法,Android可以通过FileProvider获取对应资源的URI。

兼容7.0代码如下:

【第一步】 权限

指定文件目录的拍照方式需要保存文件,所以需要在AndroidManifest中添加写文件的权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

【第二步】 在AndroidManifest中配置一个provider

    <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="org.bytedeco.javacpp.takephoto.fileprovider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/file_paths" />
    </provider>

其中,authorities需要保证唯一性,file_paths是指定需要授权访问的目录。

【第三步】 新建file_paths文件

在res目录下,新建xml文件夹,并在文件夹中新建file_paths文件,资源文件目录如下:

图片.png

代码如下:

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

external-path代表与Environment.getExternalStorageDirectory()相同的文件路径。除此之外,还有其它名称表示不同的文件目录:

files-path:代表与Context.getFileDir()相同的文件路径
cache-path:代表与getCacheDir()相同的文件路径
external-files-path:代表与Context.getExternalFilesDir(String) 和Context.getExternalFilesDir(null)相同的文件路径
external-cache-path:代表与Context.getExternalCacheDir()相同的文件路径

这个配置表示,如果拍照后的图片允许在Environment.getExternalStorageDirectory()+"/demo/"目录下保存。

【第四步】 启动系统相机

    //获得项目缓存路径
    String filePath = Environment.getExternalStorageDirectory() + File.separator + "demo" + File.separator;

    //如果目录不存在则必须创建目录
    File cameraFolder = new File(filePath);
    if (!cameraFolder.exists()) {
        cameraFolder.mkdirs();
    }

    //根据时间随机生成图片名
    String photoName = new DateFormat().format("yyyyMMddhhmmss", Calendar.getInstance(Locale.CHINA)) + ".jpg";
    filePath = filePath + photoName;

    File mOutImage = new File(filePath);

    //如果是7.0以上 那么就把uir包装
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        mImageUri = FileProvider.getUriForFile(MainActivity.this, "org.bytedeco.javacpp.takephoto.fileprovider", mOutImage);
    } else {
        //否则就用老系统的默认模式
        mImageUri = Uri.fromFile(mOutImage);
    }
    //启动相机
    Intent intent = new Intent();
    intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
    intent.putExtra(MediaStore.EXTRA_OUTPUT, mImageUri);
    startActivityForResult(intent, REQUEST_CODE);

其中,FileProvider.getUriForFile()的第二个参数必须和AndroidManifest.xml文件中声明的provider中的authorities一致,文件的保存目录必须和file_paths.xml文件制定的文件路径一致。

【第五步】 返回处理

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

    if(requestCode == REQUEST_CODE && resultCode == RESULT_OK){
        if(data != null && data.hasExtra("data")){
            Bitmap bitmap = data.getParcelableExtra("data");
            imageview.setImageBitmap(bitmap);
        }else{

            if(mImageUri != null){
                Glide.with(MainActivity.this)
                        .asBitmap()
                        .load(mImageUri)
                        .diskCacheStrategy(DiskCacheStrategy.NONE)
                        .into(imageview);

            }
        }
    }
}

以上解决7.0以上手机的兼容处理其实比较繁琐,需要在AndroidManifest中添加provider,需要配置授权目录,更需要FileProvider.getUriForFile方法获取URI,我们可以采用严格模式(StrictMode)一步到位,只需要在自定义Application的onCreate方法中添加如下代码即可:

    // android 7.0系统解决拍照的问题
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        StrictMode.VmPolicy.Builder builder = new StrictMode.VmPolicy.Builder();
        StrictMode.setVmPolicy(builder.build());
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
            builder.detectFileUriExposure();
        }
    }
总结:

以上有关兼容7.0以上手机有两种方式

  • 采用FileProvider方式,这种方式代码比较繁琐,优点是比较安全;
  • 采用严格模式,这种方式代码非常简单,但没有前者安全。

[本章完...]

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容