1、背景介绍
去年年初的时候写过一篇文章 《CameraX:Android 相机库开发实践》,那时我想自己写一个 Android 相机库,但是因为名字和谷歌关放的 CameraX 冲突了,所以现在我将自己的项目改名为 iCamera.
之前的文章中也交代过一些 Android 相机库的背景,本身集成相机功能到自己的项目中并不复杂,但是如果设计一个功能全面的 Android 相机库就没那么简单了——你要满足更多用户的需求,基本的缩放、闪光灯等这些在日常开发中不会涉及的功能都要支持;此外,你还要处理相机的各种支持尺寸和宽高比的计算问题,满足用户自定义的需求等等。
在 iCamera 之前,为了集成相机功能,我也找过一些开源的相机库。比如 CameraView,虽然是挂名谷歌,但是并不算谷歌官方的库,而且因为代码设计的问题,本身性能并不好。再者 CameraFragment,虽然代码结构清晰得多,但是对很多功能的支持不够完善。还有 CameraX,这个库我没有仔细研究它的代码,但是它只支持 Camera2. Camera2 虽然 API 21 上就可以使用,但实际上很多手机对 Camera2 的支持并不好,就比如在我的手机(OnePlus6, API 29)上 Camera1 的启动速率明显高于 Camera2.
综上,我决定自己写一个性能更好、功能全面并且支持用户自定义的相机库。这个项目去年就开始写了,但是因为工作的问题一直没时间完善。最近有了些自己的时间,于是我解决了之前遗留下来的各种问题并做了系统的测试。现在,第一个版本已经正式发布可用了。
项目地址:iCamera
在这篇文章中,我重点介绍下是如何设计和实现这样一款 Android 相机库的,希望这能够对你的系统设计有所启发,并且希望这能够增进你对 iCamera 的了解。
2、整体的设计与实现
下面是这个项目整体的设计图,相比于第一个版本的设计,在下面的这个版本中我又新增了一些方法并对部分设计细节做了调整:
链接地址:https://www.processon.com/view/link/5c976af8e4b0d1a5b10a4049
了解了整体的设计,我再来具体介绍下我是如何通过多种设计模式的综合运用来满足用户的自定义等需求的。
2.1 单例的应用:缓存、自定义和预加载
最初我在设计的时候希望通过静态字段缓存一些相机的信息,这样一来可以避免多次从相机属性中读取和计算各种参数,二来可以通过预加载操作来把读取相机参数的操作提前到相机启动之前来达到加快相机启动速度的目的。不过后来我发现使用静态单例一样可以满足这个需求并且应该说更加合理一些。于是,我在项目中使用了单例的 ConfigurationProvider
来缓存计算结果并且提供方法提前读取相机信息。该类如下:
public final class ConfigurationProvider {
/** The singleton */
private static volatile ConfigurationProvider configurationProvider;
/**
* The sizes map from a int value, which was calculated from:
* hash = {@link CameraFace} | {@link CameraSizeFor} | {@link CameraType} */
private SparseArray<List<Size>> sizeMap;
/**
* The room ratios map from a int value, which was calculated from:
* hash = {@link CameraFace} | {@link CameraType} */
private SparseArray<List<Float>> ratioMap;
private ConfigurationProvider() {
if (configurationProvider != null) {
throw new UnsupportedOperationException("U can't initialize me!");
}
initWithDefaultValues();
}
private void initWithDefaultValues() {
// initialize all kinds of parameters
}
public static ConfigurationProvider get() {
if (configurationProvider == null) {
synchronized (ConfigurationProvider.class) {
if (configurationProvider == null) {
configurationProvider = new ConfigurationProvider();
}
}
}
return configurationProvider;
}
}
对于 Camera2,因为本身它不需要打开相机就能从系统服务中读取相机信息,所以我们可以方便地实现预加载的需求。按照下面这样,我们只需要在打开相机之前调用 prepareCamera2()
就可以提前将相机的信息读取并缓存起来,这样就可以减少相机启动过程中的时间。我测试了下,这样大概可以减少几十毫秒的时间:
public final class ConfigurationProvider {
// ....
private int numberOfCameras;
private AtomicBoolean camera2Prepared = new AtomicBoolean();
private SparseArray<String> cameraIdCamera2 =</