简介
Android的framework支持通过android.hardware.camera2API或者是过时的Camera来拍摄图片和视频。这篇文章讲解的是Camera的使用,后续会写一篇关于camera2使用的文章。当然,如果你等不及的话可以去看看google博客关于camera2的文章,camera2文章地址,请翻墙查看。
基础
- android.hardware.camera2,这个包是控制设备相机的主要API,它可以被用来拍摄图片或者视频当你想构建一个相机app的时候;
- Camera,这个类也是用来控制设备相机的,但是在API21之后过时了;
- SurfaceView,这个类用来给用户呈现相机的预览;
- MediaRecorder,这个类通过相机来录制视频;
- Intent,如果你的目的仅仅只是简单的拍摄一张图片或者一段视频,你完全可以调用系统的相机让它来完成这些事情,而不需要直接操控系统相机。你可以使用MediaStore.ACTION_IMAGE_CAPTURE来调用系统相机来拍摄图片,或者通过MediaStore.ACTION_VIDEO_CAPTURE来调用系统相机来拍摄视频。
Manifest声明
当你使用Camera API进行开发的时候,你必须确保你的manifest文件中声明了相关的东西。
Camera Permission,当app需要使用设备相机的时候必须声明这个权限
1
<uses-permission android:name="android.permission.CAMERA" />
Camera Features,同时得声明使用相机相关的特性,比如。关于camera features的列表,可以查看Features Reference。添加camera features到manifest文件中会导致Google Play防止你的应用安装在那些没有相机或者是不支持相机特性的设备上。如果你的app并不是一定需要camera,你可以在manifest中通过 android:required 来指定。
1
<uses-permission android:name="android.hardware.camera" android:required="false" />
Storage Permission,如果应用程序需要将图片或者视频保存到external storage中,还需要声明这个权限;
1
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Audio Recording Permission,视频录制的过程中还需要录制音频,所以得添加音频录制的权限,音频录制在之前的文章中已经讲过,不懂的可以去 Android录制系列之音频录制进行查阅;
1
<uses-permission android:name="android.permission.RECORD_AUDIO" />
Location Permission,如果app需要给图片打上GPS定位信息的tag,你需要申请 ACCESS_FINE_LOCATION 权限,这里需要注意,Android5.0及以上版本需要声明使用设备的GPS;
1
2
3<uses-permission android:name ="android.permission.ACCESS_FINE_LOCATION" />
<!-- targetSdkVersion >= 21 的需要添加 -->
<uses-feature android:name="android.hardware.location.gps" />
如何使用
访问相机
检索并访问相机,如果你的app没有在manifest文件中声明是否只能安装在有相机的设备上,那么在使用之前,请检测设备是否有相机的硬件支持。1
fun hasFeatureCamera(context: Context) = context.packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA)
Android2.3 之后你可以直接通过 Camera.getNumberOfCameras() 方法直接获取到设备上可用的相机数量。如果你非常清楚你app运行在有相机支持的设备上运行时,你可以直接通过 Camera.open() 方法去请求并获取一个Camera的实例,它会 访问设备上的第一个后置摄像头。1
2
3
4
5
6
7
8
9
10fun getCamera(): Camera? {
var camera: Camera? = null
try {
camera = Camera.open()
} catch (e: Exception) {
//如果不捕获异常,相机在被其他的app使用,或者设备上根本没有相机的时候,就会导致app crash
Log.d("Amoryan", "open camera error!")
}
return camera
}
Android 2.3 之后你可以通过 Camera.open(int) 访问指定的相机。比如,你可以这么玩。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15fun getCameraInfo() {
try {
for (i in 0 until Camera.getNumberOfCameras()) {
val cameraInfo: Camera.CameraInfo = Camera.CameraInfo()
Camera.getCameraInfo(i, cameraInfo)
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_BACK) {
Log.d("Amoryan", "$i is back facing,orientation is ${cameraInfo.orientation}")
} else {
Log.d("Amoryan", "$i is front facing,orientation is ${cameraInfo.orientation}")
}
}
} catch (e: Exception) {
Log.d("Amoryan", "open camera error!")
}
}
设置预览
设置预览界面,你可以直接使用SurfaceView,或写一个SurfaceView的子类,然后实现SurfaceHolder.Callback的相关回调,这里我为了方便起见,直接在布局文件中使用SurfaceView了1
2
3
4
5
6
7
8<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
然后实现相对应的回调接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19class MainActivity : AppCompatActivity(), SurfaceHolder.Callback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
surfaceView.holder.addCallback(this)
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
override fun surfaceCreated(holder: SurfaceHolder?) {
}
}
拍摄图片
通过 takePicture 方法拍摄图片,这里只是简单的将拍摄的图片显示在ImageView上了。1
2
3
4
5
6
7
8
9
10
11captureTv.setOnClickListener { camera?.safeTakePicture(null, null, pictureCallback) }
private val pictureCallback = Camera.PictureCallback { data, camera ->
data?.apply {
releaseBitmap()
bitmap = BitmapFactory.decodeByteArray(this, 0, this.size)
previewImage.setImageBitmap(bitmap)
camera?.safeStopPreview()
setupCamera()
}
}
释放Camera
Camera是设备上的共享资源,当app获取到Camera的实例后就可以使用它,但是在app不需要Camera或者当界面变得不可见的时候应该正确的释放。举个栗子,当界面变得不可见的时候,如果你没有正确的释放掉资源,就会导致接下来想要访问Camera的app获取失败,而没办法正常工作。1
2
3
4
5override fun onPause() {
super.onPause()
camera?.safeRelease()
camera = null
}
完整代码
github项目地址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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98package com.yanfangxiong.camerademo
import android.Manifest
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.hardware.Camera
import android.os.Bundle
import android.support.v4.app.ActivityCompat
import android.support.v7.app.AppCompatActivity
import android.view.SurfaceHolder
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity(), SurfaceHolder.Callback {
companion object {
val REQUEST_CAMERA_PERMISSION = 1
}
private var camera: Camera? = null
private var bitmap: Bitmap? = null
private val pictureCallback = Camera.PictureCallback { data, camera ->
data?.apply {
releaseBitmap()
bitmap = BitmapFactory.decodeByteArray(this, 0, this.size)
previewImage.setImageBitmap(bitmap)
camera?.safeStopPreview()
setupCamera()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
surfaceView.holder.addCallback(this)
captureTv.setOnClickListener { camera?.safeTakePicture(null, null, pictureCallback) }
}
private fun checkCameraPermission() {
val cameraPermissionStatus = ActivityCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
if (cameraPermissionStatus == PackageManager.PERMISSION_GRANTED) {
openCamera()
} else {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA_PERMISSION)
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode == REQUEST_CAMERA_PERMISSION && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
openCamera()
}
}
private fun openCamera() {
camera = safeOpenCamera()
setupCamera()
}
private fun setupCamera() {
camera?.apply {
setDisplayOrientation(90)
setPreviewDisplay(surfaceView.holder)
safeStartPreview()
}
}
override fun surfaceCreated(holder: SurfaceHolder?) {
if (camera == null) {
checkCameraPermission()
} else {
setupCamera()
}
}
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
camera?.safeStopPreview()
setupCamera()
}
override fun surfaceDestroyed(holder: SurfaceHolder?) {
}
override fun onPause() {
super.onPause()
camera?.safeRelease()
camera = null
}
private fun releaseBitmap() {
bitmap?.recycle()
bitmap = null
}
}
毒鸡汤
人生不如意的时候是上帝给的长假。