Android相机开发(一):最简单的相机
转载自:Penguin
概述
作为系列的第一篇,就当做是Android开发入门了。本文主要讲解怎么在Android Studio下,从最开始慢慢实现一个只能拿来预览(不能拍照)的相机APP。
新建一个空的APP
打开Android Studio,选择New
->New Project
,然后给你的APP起个名字,比如:
![pload-images.jianshu.io/upload_images/9127909-7c61c6aeaf83b66a.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240 "New Project")
然后设置支持的最低API等级,我选择的是API 19: Android 4.4
:
再之后选择默认Activity,为了让我们能够更清楚Android的架构,选择Add No Activity
:
这样我们的空APP就生成了,文件目录大概会是这个样子:
Empty File List这个APP还不能够运行,如果你尝试运行,Android Studio会报错
添加外观,让APP能够运行
添加一个layout
在res
下新建一个文件夹layout
,在layout
下新建一个文件activity_main.xml
:
在activity_main.xml
下会出现一个编辑布局的窗口,这时点击左下角的Text
,即activity_main.xml
的文本编辑模式,在其中加入如下代码:
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Hello World!" />
</LinearLayout>
这个activity_main.xml
就是一个布局文件,而我们刚才设计了一个布局,布局方式是LinearLayout
,其中只有一个TextView
,其内容是Hello World!
。
使用这个layout
新建好一个布局文件后,我们需要让APP使用这个布局。
在java
->com.polarxiong.camerademo
(这个根据你自己写的APP名字来)下新建一个Java Class MainActivity
:
新建后会自动创建一个空的Java类:
Java
public class MainActivity {
}
接下来在这个类中加入一些代码:
Java
import android.app.Activity;
import android.os.Bundle;
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
解释一下,Activity
类可以创建一个“窗口”,就是APP运行时的那个窗口,而通过Activity
这个“窗口”添加布局,就能在窗口中应用这个布局,让用户能够看到;所以MainActivity
需要继承Activity
,作为这个APP的主窗口。
onCreate()
重载了Activity
的onCreate()
,在窗口被创建时执行这个方法,setContentView()
即设置这个窗口的布局,这里我们选择刚才创建的布局R.layout.activity_main
。这样当MainActivity
这个窗口创建时,就会应用activity_main
这个布局。
添加APP入口
我们希望APP在打开时就会显示MainActivity
这个窗口,但我们怎么告诉APP呢?答案是manifests
下的AndroidManifest.xml
文件,这个文件对APP来说是至关重要的,其中定义了关于APP的一些重要信息,AndroidManifest.xml
会在APP的代码执行之前就会读取。
AndroidManifest.xml
初始时大概是这样:
XML
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.polarxiong.camerademo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
</application>
</manifest>
现在我们给APP添加一个入口,也就是一个activity:
XML
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
其中activity android:name=".MainActivity"
就是指定了刚才的窗口MainActivity
,修改后的AndroidManifest.xml
像这样:
XML
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.polarxiong.camerademo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
运行
现在点击Android Studio上方的运行按钮就可以运行这个APP啦,你可以选择在真机或模拟器下运行,运行的效果像这样:
TextView Screenshot
让APP显示相机预览
想让相机拍到的画面显示在APP的窗口中,简单来说就是要启动相机,并且将相机内容绑定到窗口。
修改布局
想要相机预览显示在窗口中,实际上就是要显示在布局中,我们首先在布局中给相机预览留个位置,对于这个APP来说的话,就是把全部的位置都留给相机预览了。
修改activity_main.xml
:
XML
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/camera_preview"
android:layout_width="0px"
android:layout_height="fill_parent"
android:layout_weight="1"
/>
</LinearLayout>
我们会让相机预览填充在FrameLayout
中,但因为这个填充会在Java代码中实现,所以目前我们只给它指定一个ID,供以后使用,ID名字就是camera_preview
;而剩下的就是在设置这个FrameLayout
所占布局大小,设置为占有全部。
添加CameraPreview类
从布局层面来说,我们想要添加相机预览实际上就是在FrameLayout
中再添加一个View
,这个View
可以理解为一个“控件”,就像之前的TextView
,也是View
中的一种,只不过相机预览这个View
的内容是会一直变化的预览帧。因为Android并没有提供相机预览这个View
,所以需要我们自己创造一个,而这个View我们就起名叫做CameraPreview
。
在java
->com.polarxiong.camerademo
(这个根据你自己写的APP名字来)下新建一个Java Class CameraPreview
,并修改其内容为:
Java
import android.content.Context;
import android.hardware.Camera;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.io.IOException;
public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
private static final String TAG = "CameraPreview";
private SurfaceHolder mHolder;
private Camera mCamera;
public CameraPreview(Context context) {
super(context);
mHolder = getHolder();
mHolder.addCallback(this);
}
private static Camera getCameraInstance() {
Camera c = null;
try {
c = Camera.open();
} catch (Exception e) {
Log.d(TAG, "camera is not available");
}
return c;
}
public void surfaceCreated(SurfaceHolder holder) {
mCamera = getCameraInstance();
try {
mCamera.setPreviewDisplay(holder);
mCamera.startPreview();
} catch (IOException e) {
Log.d(TAG, "Error setting camera preview: " + e.getMessage());
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
mHolder.removeCallback(this);
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mCamera.release();
mCamera = null;
}
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
}
}
SurfaceView
是一个包含有Surface
的View
,而Surface
用来处理直接呈现在屏幕上的内容,这个我们不细究,可以认为是相机预览的原始数据交给Surface
就能转换成呈现在屏幕上的样子,这也是为什么CameraPreview
一定要继承SurfaceView
。CameraPreview
还使用了SurfaceHolder.Callback
接口,这个接口包含有三个方法:surfaceChanged()
、surfaceCreated()
和surfaceDestroyed()
,这三个方法会对应在Surface
内容变化、Surface
生成和Surface
销毁时触发。
成员变量mHolder
保存这个Surface
的“持有者”(还是Holder
顺口),而只有Holder
才能对对应的Surface
进行修改。成员变量mCamera
保存相机Camera
的实例。
先看构造函数,构造函数实际就是指定本View
(即CameraPreview
)是这个Surface
的Holder
。
对于SurfaceView
来说,这个View
创建时就会创建Surface
,而当Surface
创建时就会触发surfaceCreated()
,所以我们就要在surfaceCreated()
中打开相机、开始预览,并将预览帧交给Surface
处理。getCameraInstance()
是一个比较安全的获取并打开相机的方法,很简单。Camera
的setPreviewDisplay()
方法就是告知将预览帧数据交给谁,这里当然就是这个Surface
的Holder
了;最后用startPreview()开启相机,这样我们就完成了整个过程,创建好了CameraPreview
类。
但我们还要做一些善后处理,相机是共享资源,在APP运行结束后就应当“放弃”相机。我们在APP退出,即surfaceDestroyed()
中完成这些善后处理,surfaceDestroyed()
中的代码很简单,不需要详细说明,就是构造函数和
surfaceCreated()
的逆过程。
我们目前还不需要surfaceChanged()
,所以代码留空。
将CameraPreview
加入到FrameLayout
上一步只是创建了一个View
,而现在就是要将这个View
添加到activity_main
中,因为这个View
是实时创建的,当然我们不能直接去修改activity_main.xml
,而应当在MainActivity
中用代码添加。
修改MainActivity
,在onCreate()
最后添加:
Java
CameraPreview mPreview = new CameraPreview(this);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(mPreview);
代码首先创建一个CameraPreview
的实例mPreview
,再在布局中通过ID找到FrameLayout
,最后在FrameLayout
中添加mPreview
。修改之后的MainActivity
就是:
Java
import android.app.Activity;
import android.os.Bundle;
import android.widget.FrameLayout;
public class MainActivity extends Activity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
CameraPreview mPreview = new CameraPreview(this);
FrameLayout preview = (FrameLayout) findViewById(R.id.camera_preview);
preview.addView(mPreview);
}
}
在AndroidManifest中申请和声明相机
现在APP启动时就会调用MainActivity
,而MainActivity
创建时就会创建CameraPreview
,CameraPreview
创建时则会调用相机并开启相机预览。现在还存在的一个问题是APP启动后才会调用相机,而很显然我们希望APP在安装时就告知Android需要用到相机,这就是AndroidManifest
要干的事情啦。
在AndroidManifest.xml
中manifest
下加入:
XML
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
在AndroidManifest.xml
中activity
内加入:
XML
android:screenOrientation="landscape"
最后AndroidManifest.xml
像这样:
XML
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.polarxiong.camerademo">
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity"
android:screenOrientation="landscape">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
uses-permission
是APP申请相机权限,这样在安装APP时就会显示APP需要相机;uses-feature
则可以防止APP被安装到没有相机的Android设备上(目前仅Google Play支持)。android:screenOrientation
则是设置APP的方向,水平或竖直,这里设置为水平,因为从实际来说,相机预览一直是水平的。
运行
上述步骤完成后整个APP就算开发完毕了,虽然基本没啥功能,但总算是能跑起来了。如下:
Photo
一点唠叨
很显然这个“相机”APP还不能算真正的相机,不能拍照,而且还支持对焦,这个屏幕一篇模糊简直不能用。但如果你能成功写出这个APP,本文的目的也算达到了,至少你已经大体明白了Android开发的步骤,以及Android APP运行的基本过程。随后的系列文章会基于这个APP扩展各方面的功能,欢迎继续阅读。
DEMO
本文实现的相机APP源码都放在GitHub上,如果需要请点击zhantong/AndroidCamera-PreviewOnly。