玩转Android拍摄功能

时间:2023-01-17 07:59:44

简单拍照与摄像

在富媒体开始流行之前,整个世界是一个灰暗且平淡无奇的地方。还记得Gopher吗?我或许不记得了。自从APP成为用户生活的一部分之后,这便给他们提供了一种方式可以来存放他们生活的细节。使用设备上的相机,程序可以使用户扩大周围的视野或者见解,使以独特的化身,记录各个角落里的奇闻异事,或者只是简单的分享他们的境遇。

假设你正在实现一个众包的天气服务程序,这个服务可以使运行在设备上的客户端APP通过天空的混合照片生成全部的天气示意图(言下之意就是可以生成各种天气的天气图像),而整合照片只是程序很小的一部分功能。你并不想在这里花费很多时间,也不想彻底的改造相机程序。幸运的是,大多数的Android设备至少已经安装了一款相机应用。下面就是学习如何利用这个应用为程序拍一张照片。

请求相机权限

为了告知系统程序是基于相机的,需要在清单文件中添加标签

玩转Android拍摄功能

如果程序需要使用,但是为了整个功能而不强制要求相机,那么可以设置 Android:required为false。这样做的话,Google Play会允许不带相机的设备下载你的程序。不过你有责任需要在运行时通过调用 hasSystemFeature(PackageManager.FEATURE_CAMERA) 方法检查设备上的相机是否可用。如果相机是不可用的,你应该禁用掉与相机相关的功能。

通过相机APP拍照

Android通过授权的方式让其他程序通过调用一个Intent来描述你想要做的事情。这个过程包含了三块:Intent本身,一个启动外部Activity的调用,以及一些当焦点返回Activity时处理图像数据的代码。

下面代码的功能用于调用一个意图来拍摄照片:

玩转Android拍摄功能

要注意,startActivityForResult() 方法被一个调用 resolveActivity() 方法的条件所保护,这个方法返回了可以处理这个Intent的第一个Activity组件。执行这项检查是非常重要的,因为如果你调用 startActivityForResult() 方法所使用的Intent没有APP可以处理的话,那么你的APP将会崩溃。所以只要结果不是null,那么就意味着可以安全使用这个Intent。

获取缩略图像

如果简单的拍照功能并不是APP的主要功能,那么你可能想通过相机应用获得一张照片,并且利用这张照片做点什么事情。

Android的相机应用会将照片作为一个小的 Bitmap 对象打包进Intent中,然后通过onActivityResult() 方法将该Intent返回。具体的Bitmap对象会附加在键”data”后。下面的代码段接收到这个图像,并将它展示到了一个 ImageView 中:

玩转Android拍摄功能

Note: 从”data”中获得的缩略图像可能适合用于图标上面,但不适用于很大的图标。处理全尺寸的图像需要花费更多一点的工作。

保存全尺寸照片

如果你提供了文件的保存路径的话,那么Android相机应用会将全尺寸的照片保存在这个地方。你必须提供文件的全路径,这个路径所指向的地方就是相机应用将要保存照片的地方。

通常情况下,用户通过相机应用所拍摄的任何照片都应该被保存在设备外部存储器上的一个公共文件夹中,这样就可以使所有的APP都可以访问。这个适用于存放共享照片的目录由getExternalStoragePublicDirectory() 提供,并需要传递 DIRECTORY_PICTURES 参数。因为由这个方法所提供的分享目录适用于所有的APP,读取以及写入分别需要READ_EXTERNAL_STORAGE WRITE_EXTERNAL_STORAGE 权限。写入权限隐式地包含了读取权限,所有如果你需要写入到外部存储上的话,只需要请求一个权限就可以:玩转Android拍摄功能

然而,如果你希望这些照片只是被保存在APP的私有目录中的话,你可以使用getExternalFilesDir() 方法提供的目录来做替换方案。在Android 4.3之前,写入到这个目录需要 WRITE_EXTERNAL_STORAGE 权限。从Android 4.4开始,这个权限不再被需要,因为这个目录不再对其它APP可见,所以这里声明的权限只对低版本的Android适用,并需要添加maxSdkVersion 属性:

玩转Android拍摄功能

Note:当用户卸载了你的APP时,那么通过 getExternalFilesDir() 方法所提供的目录下的文件也会一并删除。

一旦你决定了文件存储的目录,需要创建一个防止冲突的文件名称。你可能还希望将路径保存到一个成员变量中,以便稍后再使用。这里在方法中有一个示例的解决办法,它会通过日期时间戳来对新照片返回一个唯一的文件名称:

玩转Android拍摄功能

随着这个方法可用来对照片创建文件,你现在可以创建并且调用Intent,就像这样:

玩转Android拍摄功能

添加照片到相册

当你通过一个意图创建了一张照片,你应该知道图像位于何处,因为在上面的代码中你告诉了照片保存的位置。对于其他人而言,可能使照片最简单的访问方式就是使照片对系统的媒体扫描器(Media Provider)是可访问的。

Note: 如果照片保存的文件目录是由 getExternalFilesDir() 所提供的,那么,媒体扫描器是不能访问这些文件的,因为照片对于你的APP来说是私有的。

下面的示例方法演示了如何调用系统的扫描器来添加你的照片到媒体扫描器(Media Provider)的数据库中,使得这些照片可以被系统的相册应用或者其它APP可访问:

玩转Android拍摄功能

解码缩放图像

在一定的内存中管理多个全尺寸的图像可能是很棘手的。如果你发现你的程序在展示了几张图片之后造成了内存溢出,那么可以通过将JPEG图像缩放到目标视图的尺寸大小的方式来显著的降低堆内存的使用量。

下面的示例方法演示了这一技术:

玩转Android拍摄功能

通过相机APP摄像

权限申请可参考请求相机权限,相关描述可以参考通过相机APP拍照,下面代码的功能用于调用一个意图来捕获视频:

玩转Android拍摄功能

查看视频

Android的相机应用会通过 onActivityResult() 方法将视频返回,视频位于 onActivityResult()方法的回调参数Intent中的Uri所指向的位置。下面的代码展示了接收这个视频并且在VideoView中播放它:

玩转Android拍摄功能

调用API

直接控制设备的相机拍照或者摄像的代码远比通过其他相机应用来完成要多得多。然而,如果你想构建一个专业的相机应用或者在APP的UI中完全集成相机的话,下面展示了如何去做。

开启相机对象

直接控制相机的第一步就是获得 Camera 对象的实例。和Android自身的相机应用相同,推荐访问相机的方式就是在独立的线程打开 Camera,这种方式是应对阻塞UI线程的一个好的解决方法。在更加基础化的实现当中,开启相机这一步操作可以推迟到onResume()方法中执行,这样可以促使代码重用并且保持简单的控制流。

如果相机已经正在被其它应用所使用,那么调用 Camera.open() 方法会抛出一个异常,所以我们需要使用try控制块包裹住它:

玩转Android拍摄功能

从API 9开始,相机框架支持多个相机。如果你使用的是过去的API,然后调用了没有参数的open()方法,那么你会获得后置面板的相机。

创建相机预览

拍照通常需要可以使用户能看到目标的预览图。你可以使用 SurfaceView 来绘制相机传感器捕获到的图像。

为了可以显示预览,你需要预览类。预览需要一个 android.view.SurfaceHolder.Callback 接口的实现,它被用来从相机硬件给应用传递图像数据:

玩转Android拍摄功能

在开始预览之前,必须将预览对象传递给Camera对象,就像下面部分展示的那样。

相机实例的创建于相关预览对象创建必须是以指定顺序进行的,从相机对象开始。在下面的代码中,实例化相机对象的过程被封装起来了,所以 Camera.startPreview() 是可以通过setCamera() 调用的,每当用户做了什么事情使相机发生了改变。预览也必须在预览类的surfaceChanged() 回调方法重新启动:

玩转Android拍摄功能

修改相机设置

相机设置可以改变相机拍照的方式,从缩放等级到曝光补偿等等。下面的示例只是更改了预览的大小;请查看相机应用的源代码获取更多可能:

玩转Android拍摄功能

设置预览方向

大多数的相机应用将展示锁定在了水平方向,因为这是相机传感器的自然方向。这个设置并不能阻止你在垂直方向上拍摄,因为相机的方向会被记录到EXIF的头部。setCameraDisplayOrientation() 方法允许你改变如何展示预览,而不受图像记录方向的影响。然而,在API14之前,在改变方向之前必须停止预览,然后在重新启动它。

拍照

一旦预览启动后,可以使用 Camera.takePicture() 方法来拍一张照片。你可以创建Camera.PictureCallback 对象和 Camera.ShutterCallback 对象然后将它们传递给Camera.takePicture() 方法。

重启预览

在拍了一张照片之后,你必须在用户拍另一张照片之前重新启动预览。在这个例子中,通过重写快门按钮来完成重启:

玩转Android拍摄功能

停止预览并释放相机

一旦你的程序不再需要使用相机,这时就需要执行清理工作。尤其是你需要释放相机对象,否则会使其它程序面临崩溃的风险,包括你自己程序中新的实例。

何时应该停止预览并释放相机呢?好吧,当预览界面被销毁的时候便是停止预览并释放相机的最佳时机,就像下面Preview类中显示的那样:

玩转Android拍摄功能

在上面创建相机预览中,这段程序也是 setCamera() 方法的一部分,所以实例化一个相机总是从停止这段预览开始的。