从Android 5.0 (API 21)开始谷歌废弃了Camera ,并提供了一套新的API,称之为Camera 2 ,不再是大而全的一个类了,也使用了更多的回调以异步化,流程与参数的控制更加的灵活,但也变得更加的复杂了,今天就来学习一下这套新API的使用方法。
基本使用大法
新的这一套API使用起来略复杂,涉及的较多的对象,可以按照如下步骤来使用。
Step 1: 申请相机使用权限
自从Android M(6.0)以后,对权限的限制比以前严格多了,必须要在运行时动态的去申请权限,征得用户同意后,方可正常使用。
需要在AndroidManifest中声明相机权限:
1
<uses-permission android:name= "android.permission.CAMERA" />
注意 :官方还建议在manifest中声明use camera feature ,但用处不大,这主要是给Google Play用的,Google Play可以根据这些方便精准投递你的app,如设备无camera硬件,或者硬件配置不支持,就不会安装,但就国内的市场来说基本用不到。
但这还不够,还需要在运行时动态检查,如果未授权,则要去申请,如仍被用户拒绝,则可直接退出,因为相机应用嘛,没有相机权限啥也做不了。一般来说,这个过程放在onCreate中做就可以了:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Override
protected void onCreate ( Bundle savedInstanceState ) {
super . onCreate ( savedInstanceState );
setContentView ( R . layout . activity_camera );
if ( ContextCompat . checkSelfPermission ( getApplicationContext (), Manifest . permission . CAMERA ) ==
PackageManager . PERMISSION_GRANTED ) {
setupCamera ();
} else {
requestPermissions ( new String []{ Manifest . permission . CAMERA }, REQ_CODE_PERM );
}
}
@Override
public void onRequestPermissionsResult ( int requestCode , @NonNull String [] permissions , @NonNull int [] grantResults ) {
super . onRequestPermissionsResult ( requestCode , permissions , grantResults );
if ( requestCode == REQ_CODE_PERM ) {
if ( grantResults . length > 0 && grantResults [ 0 ] == PackageManager . PERMISSION_GRANTED ) {
setupCamera ();
} else {
Toast . makeText ( getApplicationContext (),
"To use this app you must grant CAMERA permission." ,
Toast . LENGTH_LONG ). show ();
finish ();
}
}
}
注意 :不建议放在onResume中去做,因为可能会造成死循环,比如权限申请,虽然看起来像一个对话框,但其实是一个Activity,也就是说从权限授权那个弹窗页面回来,是会走onResume的,如果用户同意授予了还好,直接就往下走流程了,假如用户拒绝了权限,检查发现权限还未授予,会继续去申请,这就会死循环了。
权限申请是必须要做,且要处理好,在应用的入口做好处理,保证后面的流程权限是有的,这样可以为业务逻辑减轻复担,里面就可以不用管权限了。基于这样的假设:要么权限已授予,要么就不会走进来(无权限时应该直接退出)。
Step 2: 获取相机的配置情况
处理好了权限以后,就要真正开始搞了,无论你的use case是什么,第一步肯定 是获取相机的配置信息情况,也就是说要搞清楚有几个硬件camera可以用,它的能力是什么(输出能满足什么样的需求),它的特点是什么(前置还是后置)。这里最主要用到的是就是CameraManager 对象,它是一个注册在ServiceManager里面的独立service,所以可以通过Context来获取:
1
cameraManager = ( CameraManager ) context . getSystemService ( Context . CAMERA_SERVICE );
然后就是获取当前有多少可用的摄像头:
1
String [] ids = cameraManager . getCameraIdList ();
这个列表就是当前可用的camera,用一个String形式的id来标识,查询具体的摄像头的能力和配置都需要传入对应的id,比如说简单的dump一下信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void enumerateCameras ( Consumer < String > consumer ) {
final List < String > cameraIds = cameraManager . getCameraIdList ();
List < String > status = cameraIds . stream ()
. map ( cameraManager: : getCameraCharacteristics )
. map ( CameraContext: : dumpCameraInfo )
. collect ( Collectors . toList ());
}
private static String dumpCameraInfo ( CameraCharacteristics characteristics ) {
StringBuilder sb = new StringBuilder ();
sb . append ( "{Physical Id: " );
sb . append ( characteristics . getPhysicalCameraIds ());
sb . append ( ", Facing: " );
int facing = characteristics . get ( CameraCharacteristics . LENS_FACING );
sb . append ( facing == CameraMetadata . LENS_FACING_FRONT ? "FRONT" : "BACK" );
sb . append ( "}" );
return sb . toString ();
}
因为大多数场景都是默认使用后摄,但要先把找出来:
1
2
3
4
5
6
7
8
9
final List < String > cameraIds = cameraManager . getCameraIdList ();
cameraIds . stream ()
. forEach ( id -> {
CameraCharacteristics cc = cameraManager . getCameraCharacteristics ( id );
if ( cc . get ( CameraCharacteristics . LENS_FACING ) == CameraMetadata . LENS_FACING_BACK ) {
currentCamera = new CameraAgent ( id , cc , cameraThreadHandler );
return ;
}
});
因为官方的CameraManager封装略差一些,以及它的每一个方法都要检查权限异常,所以做一下进一步的简单封装:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class CameraManagerWrapper {
private final CameraManager cameraManager ;
public CameraManagerWrapper ( Context context ) {
cameraManager = ( CameraManager ) context . getSystemService ( Context . CAMERA_SERVICE );
}
public List < String > getCameraIdList () {
try {
return Arrays . asList ( cameraManager . getCameraIdList ());
} catch ( CameraAccessException e ) {
e . printStackTrace ();
}
return new ArrayList <>();
}
public CameraCharacteristics getCameraCharacteristics ( String id ) {
try {
return cameraManager . getCameraCharacteristics ( id );
} catch ( CameraAccessException e ) {
e . printStackTrace ();
}
return null ;
}
}
Step 3:打开相机
找到了摄像头,下一步就可以尝试打开并使用了。通过CameraManager#openCamera )来获取CameraDevice 对象,这里的CameraDevice就是一个摄像头的封装,后面所有的操作都要在它上面进行。还需要提供一个CameraDevice.StateCallback ,以及一个Handler,它是用于StateCallback运行的线程,也就是说StateCallback的几个方法都是在Handler所在的线程中运行的。
因为打开摄像头是需要去操作硬件的,所以可能会耗时,因此,在这里引入了异步的回调,用以通知打开的结果CameraManager#openCamera是没有任何返回值的,结果 是通过StateCallback来通知给应用程序,且是运行在指定的Handler 中的,如果不传Handler,那么StateCallback就会运行在主线程中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private CameraDevice . StateCallback stateCallback = new CameraDevice . StateCallback () {
@Override
public void onOpened ( @NonNull CameraDevice camera ) {
Log . d ( LOG_TAG , "Device Status: onOpened" );
cameraDevice = Optional . of ( camera );
}
@Override
public void onDisconnected ( @NonNull CameraDevice camera ) {
Log . d ( LOG_TAG , "Device Status: onDisconnected" );
cameraDevice = Optional . empty ();
errorCode = 0 ;
}
@Override
public void onError ( @NonNull CameraDevice camera , int error ) {
cameraDevice = Optional . empty ();
errorCode = error ;
}
};
cameraManager . openCamera ( backId , stateCallback , handler );
打开操作仅是一个开端,本身也没有特别复杂的,就是需要在StateCallback中做好处理就可以了。
Step 4: 创建拍照会话
下一步就是要创建拍照会话,对于每一个CameraDevice,打开了以后,要想使用必须创建一个CameraCaptureSession ,所有的use case都需要通过有效的CameraCaptureSeesion对象来实现(如预览和拍照),CameraDevice对象本身提供的功能并不多。与打开类似,也需要提供一个CameraCaptureSession.StateCallback 以接收创建session的结果,同时也要提供一个StateCallback使用的线程Handler:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
private CameraCaptureSession . StateCallback regularSessionCallback = new CameraCaptureSession . StateCallback () {
@Override
public void onConfigured ( @NonNull CameraCaptureSession session ) {
Log . d ( LOG_TAG , "Session State: onConfigured" );
captureSession = Optional . of ( session );
}
@Override
public void onConfigureFailed ( @NonNull CameraCaptureSession session ) {
captureSession = Optional . empty ();
}
@Override
public void onClosed ( @NonNull CameraCaptureSession session ) {
Log . d ( LOG_TAG , "Session State: onClosed" );
super . onClosed ( session );
captureSession = Optional . empty ();
}
};
private void createCaptureSession ( Surface surface ) {
SessionConfiguration sessionConfig = new SessionConfiguration (
SessionConfiguration . SESSION_REGULAR ,
Arrays . asList ( new OutputConfiguration ( surface )),
executor ,
regularSessionCallback );
try {
cameraDevice . get (). createCaptureSession ( sessionConfig );
} catch ( CameraAccessException ignored ) {
}
}
需要注意,同时需要提供一个camera的输出目标,比如预览的Surface。另外就是,创建CaptureSession是有2个API的,以前常用的是createCaptureSession(List outputs, CameraCaptureSession.StateCallback callback, Handler handler) ),但这个方法已被标记为Deprecated的了,推荐使用createCaptureSession(SessionConfiguration config) )。这两者基本没区别,所需要传入的参数 都差不多,唯一不同的就是供回调所使用的线程,旧的API是用Handler,新的这个要求是Executor ,这里有些不太一致,但容易解决,通过Handler可以很容易的创建出来一个Executor:
1
executor = command -> handler . post ( command );
Step 5:请求预览
有了CameraCaptureSession对象以后,就可以启动预览了,这个是相机类应用的最为基础的一个use case。
主要是通过setRepeatingRequest )这个方法,需要提供一个CaptureRequest ,一个CaptureCallback 以及一个供回调使用的Handler,其实,这一步通常与Step 4融合在一起:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 省略的代码
@Override
public void onConfigured ( @NonNull CameraCaptureSession session ) {
Log . d ( LOG_TAG , "Session State: onConfigured" );
captureSession = Optional . of ( session );
// In session state callback
try {
captureSession . get (). setRepeatingRequest ( requestBuilder . build (), captureCallback , cameraHandler );
} catch ( CameraAccessException ignored ) {
}
}
// 省略的代码
private CameraCaptureSession . CaptureCallback captureCallback = new CameraCaptureSession . CaptureCallback () {
@Override
public void onCaptureStarted ( @NonNull CameraCaptureSession session , @NonNull CaptureRequest request , long timestamp , long frameNumber ) {
super . onCaptureStarted ( session , request , timestamp , frameNumber );
}
};
private void doStartPreview ( Surface surface ) {
SessionConfiguration sessionConfig = new SessionConfiguration (
SessionConfiguration . SESSION_REGULAR ,
Arrays . asList ( new OutputConfiguration ( surface )),
executor ,
regularSessionCallback );
try {
requestBuilder = cameraDevice . get (). createCaptureRequest ( CameraDevice . TEMPLATE_PREVIEW );
requestBuilder . set ( CaptureRequest . CONTROL_AF_MODE , CaptureResult . CONTROL_AF_MODE_CONTINUOUS_PICTURE );
requestBuilder . addTarget ( surface );
sessionConfig . setSessionParameters ( requestBuilder . build ());
cameraDevice . get (). createCaptureSession ( sessionConfig );
} catch ( CameraAccessException ignored ) {
}
}
总结
前面说了这么多,其实关键的就是三个对象和三个回调,三个对象分别是
CameraManager – 用以获取静态的配置信息
CameraDevice – 用以创建拍照会话
CameraCaptureSession – 用以启动预览和进行拍照
三大回调分别是:
CameraDevice.StateCallback – 用于获取CameraDevice
CameraCaptureSession.StateCallback – 用于获取拍照会话
CameraCaptureSession.CaptureCallback – 用于获取预览和拍照的结果
再有就是,提供回调的时候要提供一个回调发生的线程,通过Handler或者Executor来指定。因为硬件操作可能会耗时甚至完全卡死,所以回调和线程是用来实现异步的。
参考资料