这几天本人参加了一个公司举办的编程马拉松,我打算使用Android摄像头来做。我一直都认为Android的API很糟糕,但是没有详细说出哪些地方糟糕,也没有说怎么改进会更好。趁这个机会,现在我就来解释解释。
我认为,Android关于摄像头的API非常糟糕,如果你没有用过,那么自己花点时间看看去吧。使用这个Camera API的 时候,经常会使开发者使用错误,会导致开发者忽略很多重要的东西,然后出问题了也很难发现,甚至在StackOverFlow上也很不乐观。换句话说,如 果有API需要你读完十个步骤(还有几个标记为“重要”的),那么,其实它是有问题的,怎么会写出这么麻烦的API呢?我们要改进它。
所以我打算重新构造它。我把重构后的代码放在GitHub上( EasyCamera),以下列出了我改的地方,还有修改的理由:
- 在使用startPreview()方法之前,需要先调用setPreviewDisplay(..)方法,然后再进行拍照的时候又需要调用它。为 什么不改进一下呢?如果不执行这个方法的话就会出问题,我们可以简单地抛出一个异常“Preview display not set”,但是这样终究不太好。所以,这个setPreviewDisplay(..)可以不要了,我们直接让startPreview(..)带上 surface作参数,然后还可以重载它让SurfaceTexture也作为参数传递进去。
- 在改进了预览设置后,现在开始改进预览操作。如果在拍照的时候,没有调用startPreview这个方法,那也会出问题,这里也可以简单抛出一个 异常“Preview not started”,但是要知道,这个异常是在运行时抛出的。所以,takePicture(..)这个方法也要改进,把它移到Camera这个类外面,放 到CameraAction类里。其它的需要在preview调用后才能使用的方法也需要这样改进(我暂时不知道有哪一些,当前的API里也没有列出 来)。接下来就是怎么获取一个CameraAction类实例了,很简单,直接调用startPreview(..)就可以返回一个 CameraAction了。
- 目前我们的改进只是让使用方法更加直观,然后减少出错的情况。不过takePicture这个方法好像有点奇怪,可以给它的参数全部传null值, 但是里面还有两个重写的方法需要这些参数。理论上说,传null值也可以,但是还有别的法子解决这个问题。其一就是介绍一个接口叫 PictureCallback,它有四个可以调用的方法,然后还提供一个默认的实现类,叫BasePictureCallback。当然,你会发现,在 这个场景中,好像也不太合适,因为这个例子中你传递这个callback接口的话,什么事都不会发生,但是传递null值的话,就不一样了(会出问题), 但是在我的手机上测试的时候,我传递一个shutter callback时,快门声音就会响,而传递null值就不会响。所以,这里主要介绍的就是使用这个callback接口,带有一个实现类,包含所有你需 要的所有方法。
- 到目前为止,改的还不错,还有就是,在照相完成后,需要restart preview,但是默认让它自动 restart也不是很好。目前的情况是,我们只有一个CameraAction类,然后调用startPreview(..)方法需要传递一个 surface参数,是不是需要引进一个restartPreview()方法呢?当然不用,这里我们让这个接口类的方法都返回一个布尔值,如果需要 restart preview,就可以返回true,这样会比较好,然后又有一个问题,如果这个callback类没有这4个方法调用呢,依赖这4个方法来得出是否需要 restart preview终究不好,所以,还需要在callback实现类中加一个restartPreview属性,只有在最后一个方法被调用时这个属性才会被设 置成true。
- 主要的步骤已经改进好了,还有其它的一些小问题需要改进,比如,关闭和打开camera的方法是不对称的,“open”和“release”配对感 觉不好,应该是“open”和“close”或者“acquire”和“release”。所以我觉得用“close”更好一些,况且(如果使用 java7),可以好好利用AutoClosable接口,还有try-with-resource结构(详情请查看java7新特性)。
- 在调用getParameter()方法的时候可以得到一份参数的副本,然后,你可能需要把更改过的这个副本设置回去,也是挺合理的。如果能提供一 个camera.setParameter(..)方法,可能使用起来会方便一些,不过Parameter类已经提供了很多方法了,所以在Camera类 里面添加不太好。或许这里可以设置可变参数?(当然现在也还没实现)
- 这个Camera的API糟糕的原因之一还有它的错误反馈,基本上可能得到一个报错信息“Came.takePicture failed”,无论什么原因导致的,通过以上的这些步骤,在某些情况下会过滤掉一些我们需要的异常信息,不过最好还是应该得到精确的报错信息。
- 我们的目的就是要让Camera使用更加友好(现在还不是),所以EasyCamera是一个容易使用的接口,CameraActions也是一个可造性很好的类。
本人的这个EasyCamera工程目前只是一个初版,也还没有用于实际生产环境中,但是我也希望得到大家的宝贵意见,争取把它做得更好,不断改进它。有的情况下,它也可以包含其它一些摄像头的功能,比如前后摄像头切换等等。
蹩脚的API会让开发者浪费大量的时间,金钱和精力,所以在设计API的时候,需要某些特殊的技能,还要多进行思考。Josh Bloch的关于API设计的见解很中肯,我强烈推荐它。事实上我也违反了其中一条-“if in doubt, leave it out”-通过CameraAction这个类我们可以完全掌控Camera,所以也有可能有很多操作是没用的,因为我对所有Camera的属性也不是很 了解,所以在代码里我也不会限制用户去做一些其它的操作。
一开始我写这篇文章的时候,其实我也没打算写EasyCamera这个东西,而事实上我是花了两个小时把它写出来了。最后,我建议所有开发者们,在 你遇到一些蹩脚的API时,可以像以上操作一样,自己动手做一些改造,多思考怎么设计和实现它们,然后你会发现这样比你慢慢修改和封住更省事,然后用起来 也就得心应手了。
修正Android摄像头API