Android编程权威指南总结(五)

第十六章     使用 intent 拍照

一、布置照片

二、文件存储

      相机拍摄的照片比较大,保存在SQLite数据库中肯定不行。显然,它们需要在设备文件系统的某个地方保存。

      设备上就有这么一个地方:私有存储空间。使用类似 Context.getFileStreamPath(String) 和 Context.getFilesDir() 这样的方法,像照片这样的文件也可以这么保存。结果就是照片文件保存在 databases 子目录相邻的某个子目录中。

      Context类提供的基本文件和目录处理方法如下:

  • File  getFileDir():获取 /data/data/<包名>/files目录
  • FileInputStream  openFileInput(String  name):打开现有文件进行读取
  • FileOutputStream  openFileOutput(String  name,int  mode):打开文件进行写入,如果不存在就创建它
  • File  getDir(String  name,int  mode):获取 /data/data/<包名>/目录的子目录(如果不存在就先创建它)
  • String[ ]  fileList():获取主文件目录下的文件列表,可与其他方法配合使用,如 openFileInput(String)
  • File  getCacheDir():获取 /data/data/<包名>/cache目录,应注意及时清理该目录,并节约使用

      如果存储的文件仅供应用内部使用,使用上述方法就够了。

      如果其他应用要读写你的文件,事情就没那么简单了。比如外部相机应用需要在你的应用里保存拍摄的照片。虽然有个 Context.MODE_WORLD_READABLE 可以传入 openFileOutput(String,int) 方法,但这个flag已经废弃了。即使强制使用,在新系统设备上也不那么可靠。以前,还有个方法是通过公共外部存储转存,但出于安全考虑,这条路在新设备版本上也被堵住了。

      如果想共享文件给其他应用,或是接收其他应用文件(如相机应用拍摄的照片),可以通过 ContentProvider 把要共享的文件暴露出来。ContentProvider 允许你暴露内容URI给其他应用。

1、使用 FileProvider

      如果只想从其他应用接收一个文件,自己实现ContentProvider太浪费。Google提供了一个名叫 FileProvider 的便利类。

      首先,声明 FileProvider 为 ContentProvider,并给予一个指定权限。在AndroidManifest.xml中添加一个FileProvider声明:

 

      这里的权限是指一个位置:文件保存地。把FileProvider和你指定的位置关联起来,就相当于你给发出请求的其他应用一个目标地。添加 exported = "false" 属性就意味着,除了你自己以及你给予授权的人,其他任何人都不允许使用你的FileProvider。而 grantUriPermissions 属性用来给其他应用授权,允许它们向你指定位置的 URI 写入文件。

      既然已让Android知道FileProvider在哪儿,还需要配置 FileProvider,让它知道该暴露哪些文件。这个配置用另外一个XML资源文件处理。在项目工具窗口,右键点击app/res目录,然后选择 New--->Android resource file菜单项,资源类型选XML。



    

      这是个描述性XML文件,其表达的意思是把私有存储空间的根路径映射为 crime_photos。

      最后,在清单文件中,添加一个meta-data标签,让FileProvider能找到files.xml文件。

三、使用相机Intent

      1、触发拍照

      我们需要的intent操作是定义在 MediaStore 类中的 ACTION_IMAGE_CAPTURE。MediaStore 类定义了一些公共接口,可用于处理图像、视频以及音乐这些常见的多媒体任务。

      ACTION_IMAGE_CAPTURE 打开相机应用,默认只能拍摄缩略图这样的低分辨率照片,而且照片会保存在 onActivityResult(......) 返回的 Intent 对象里。

      要想获得全尺寸照片,就要让它使用文件系统存储照片。这可以通过传入保存在 MediaStore.EXTRA_OUTPUT 中的指向存储路径的 Uri 来完成,这个Uri会指向 FileProvider 提供的位置。

final Intent captureImage = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
boolean canTakePhoto = mPhotoFile != null && captureImage.resolveActivity(packageManager)     
                                                                                 != null;
mPhotoButton.setEnabled(canTakePhoto);
mPhotoButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Uri uri = FileProvider.getUriForFile(getActivity(),
                             "com.guoxuebing.demo.criminalintent.fileprovider",mPhotoFile);
        captureImage.putExtra(MediaStore.EXTRA_OUTPUT,uri);
        List cameraActivities = getActivity().getPackageManager()
                    .queryIntentActivities(captureImage,PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo activity : cameraActivities){
            getActivity().grantUriPermission(activity.activityInfo.packageName,uri,
                                                   Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
            startActivityForResult(captureImage,REQUEST_PHOTO);
        }
    }
});

      调用 FileProvider.getUriForFile(......) 会把本地文件路径转换为相机能看见的Uri形式。要实际写入文件,还需要给相机应用权限。为了授权,我们授予 FLAG_GRANT_WRITE_URI_PERMISSION 给所有 cameraImage  intent 的目标Activity,以此允许它们在Uri指定的位置写文件。当然,还有个前提条件:在声明 FileProvider 的时候添加过 android:grantUriPermissions 属性。

四、缩放和显示位图

      有了照片,接下来就是找到并加载它,然后展示给用户。使用 Bitmap对象展示。Bitmap 是个简单对象,它只存储实际像素数据,也就是说,即使原始照片已经压缩过,但存入 Bitmap对象时,文件并不会同样压缩,因此,一张1600万像素24位的相机照片,存为JPG格式大约5MB,一旦载入Bitmap对象,就会立即膨胀至48MB!

      这个问题需要手动缩放位图照片。具体做法就是,首先确定文件到底有多大,然后考虑按照给定区域大小合理缩放文件,最后重新读取缩放后的文件,创建Bitmap对象。

public static Bitmap getScaledBitmap(String path, int destWidth, int destHeight) { 
    // Read in the dimensions of the image on disk 
    BitmapFactory.Options options = new BitmapFactory.Options(); 
    options.inJustDecodeBounds = true; 
    BitmapFactory.decodeFile(path, options); 
 
    float srcWidth = options.outWidth; 
    float srcHeight = options.outHeight; 
 
    // Figure out how much to scale down by 
    int inSampleSize = 1; 
    if (srcHeight > destHeight || srcWidth > destWidth) { 
        float heightScale = srcHeight / destHeight; 
        float widthScale = srcWidth / destWidth; 
        inSampleSize = Math.round(heightScale > widthScale ? heightScale : widthScale); 
    } 
 
    options = new BitmapFactory.Options(); 
    options.inSampleSize = inSampleSize; 
 
    // Read in and create final bitmap 
    return BitmapFactory.decodeFile(path, options); 
 }

      上述方法中,inSampleSize 值很关键。它决定着缩略图像素的大小。假设这个值是1的话,就表明缩略图和原始照片的水平像素大小一样;如果是2的话,它们的水平像素比就是1:2,也就是缩略图像素数是原始文件的四分之一。

      解决图片缩放问题之后,还有新问题:页面刚启动时,没有人知道 photoView 有多大,onResume() 方法启动后,才会有首个实例化布局出现,此时显示在屏幕上的视图才会有大小尺寸。

      解决方案有两个:要么等布局实例化完成并显示,要么干脆使用保守估算值。特定条件下,虽然估算比较主观,但是是唯一可行的方法。添加一个静态Bitmap估算方法,调用上面的方法:

public static Bitmap getScaledBitmap(String path, Activity activity){
    Point size = new Point();
    activity.getWindowManager().getDefaultDisplay().getSize(size);
    return getScaledBitmap(path,size.x,size.y);
}

      这个方法先确定屏幕的尺寸,然后按此缩放图像。这样就能保证载入的ImageView永远不会过大。这是一个比较保守的估算,但是能解决问题。

五、功能声明

      告诉Android系统,该应用需要使用相机功能。在AndroidManifest.xml 文件里加入 标签:

      默认情况下,声明使用某个设备功能后,应用就无法支持那些无此功能的设备了。不过,这里设置 required 属性为false,Android系统因此就知道,即使不带相机的设备会导致该应用功能缺失,但应用仍然可以正常安装使用。      

      API 21 中将原来的 camera API 弃用,转而推荐新增的 camera2 API。具体架构区别这里不叙述,有想要了解的可以自行百度查看。

你可能感兴趣的:(Android编程权威指南总结(五))