最近在做一个项目,需要用到无预览拍照功能,Camera已被官方废弃,所有计划用Camera2来实现,在度娘上找了一翻,感觉Camera2蛮强大的,但关于后台无预览拍照的资源不是还多,找不到满足自己的需求。于是乎,自己下载了Camera2官方Demo研究了一翻,感觉Camera2很复杂,一堆接口,各种参数,自己菜鸟,几乎懵逼。但搞懂了Demo中拍照功能的几个关键点:
1、使用继承TextureView的AutoFitTextureView作为拍照预览的控件(不明白为什么不直接用TextureView);
2、实例化mFile(照片保存路径);
3、在AutoFitTextureView的方法onSufaceTextureViewAvailable中执行openCamera(width,height)方法;
4、在setUpCameraOutputs(width,height)中设置摄像头ID、图片格式、图片大小、前置/后置摄像头等信息;
5、通过takePicture()方法来执行拍照;
6、最后在onCaptureCompleted回调方法中操作mFile即可,此时mFile已指向拍照好的图片。
有了以上几点,其他代码不懂也没关系,可以获得照片是目的,接下来就是实现我的需求了。想了下两个思路,一是将预览控件设置到最小,放在activity上,设置为不可见。二是将预览控件设置到最小,添加到系统主界面上。由于需要后台拍照,即需要即使activity已经销毁了,还能继续运行,因此思路一似乎不可行,于是采用了思路二。把自己研究一个多小时的成果记录下,当作笔记,勿喷。
AndroidManifest.xml,比较简单,几个权限,一个服务,一个界面,第一个权限为了能将预览控件添加到系统界面上,外部读写权限为是能保存图片。
MainActivity,主要是相关权限的动态申请以及启动服务
package app.test.com.takepictrueinbackground;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
public class MainActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//检查相机权限,未获取权限的提示用户获取,已获取权限则检查是否设置允许在其他应用上显示的权限
if(!myCheckPermission(this,android.Manifest.permission.CAMERA))
{
openPermission(this,android.Manifest.permission.CAMERA,0x101);
}
else
{
setDrawOverlays();
}
}
@Override
protected void onDestroy()
{
super.onDestroy();
}
@SuppressLint("NewApi")
private void setDrawOverlays()
{
//检查是否设置允许在其他应用上显示的权限,未设置跳转到设置界面,已设置则启动服务
if (! Settings.canDrawOverlays(MainActivity.this)) {
AlertDialog alertDialog = new AlertDialog.Builder(this).create();
alertDialog.setMessage("为更好显示通知,请设置允许出现在其他应用上!是否前往设置?");
alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "是", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
//跳转设置
startActivityForResult(intent,0x102);
}
});
alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "否", new DialogInterface.OnClickListener()
{
@Override
public void onClick(DialogInterface dialog, int which)
{
}
});
alertDialog.show();
}
else
{
startTakePictrueService();
}
}
private void startTakePictrueService()
{
try
{
startService(new Intent(this, TakePictrueService.class));
}catch (Exception e){}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
//用户设置允许在其他应用上显示的权限,启动服务
if(requestCode == 0x102)
{
startTakePictrueService();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)
{
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if(grantResults[0] == PackageManager.PERMISSION_GRANTED)
{
//用户设置允许使用相机的权限,则检查是否设置允许在其他应用上显示的权限
if(requestCode == 0x101)
{
setDrawOverlays();
}
}
}
private void openPermission(Activity activity, String permission, int requestCode)
{
try
{
ActivityCompat.requestPermissions(activity, new String[]{permission}, requestCode);
}catch (Exception e){}
}
private boolean myCheckPermission(Context context, String permission)
{
try
{
if (ContextCompat.checkSelfPermission(context, permission)
== PackageManager.PERMISSION_GRANTED)
{
return true;
}
}
catch (Exception e){}
return false;
}
}
AutoFitTextureView
package app.test.com.takepictrueinbackground;
import android.content.Context;
import android.util.AttributeSet;
import android.view.TextureView;
/**
* A {@link TextureView} that can be adjusted to a specified aspect ratio.
*/
public class AutoFitTextureView extends TextureView
{
private int mRatioWidth = 0;
private int mRatioHeight = 0;
public AutoFitTextureView(Context context) {
this(context, null);
}
public AutoFitTextureView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoFitTextureView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
/**
* Sets the aspect ratio for this view. The size of the view will be measured based on the ratio
* calculated from the parameters. Note that the actual sizes of parameters don't matter, that
* is, calling setAspectRatio(2, 3) and setAspectRatio(4, 6) make the same result.
*
* @param width Relative horizontal size
* @param height Relative vertical size
*/
public void setAspectRatio(int width, int height) {
if (width < 0 || height < 0) {
throw new IllegalArgumentException("Size cannot be negative.");
}
mRatioWidth = width;
mRatioHeight = height;
requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
if (0 == mRatioWidth || 0 == mRatioHeight) {
setMeasuredDimension(width, height);
} else {
if (width < height * mRatioWidth / mRatioHeight) {
setMeasuredDimension(width, width * mRatioHeight / mRatioWidth);
} else {
setMeasuredDimension(height * mRatioWidth / mRatioHeight, height);
}
}
}
}
TakePictrueService,类名好像拼写有误,懒得改了。由于Camera2官方demo有点长,不便都贴出来。demo中的代码我修改了几个地方:
1、mImageReader = ImageReader.newInstance(largest.getWidth(), largest.getHeight(), ImageFormat.JPEG, 2); 改为mImageReader = ImageReader.newInstance(pictrueWidth, pictrueHeight, ImageFormat.JPEG, 2);
2、if (facing != null && facing !=CameraCharacteristics.LENS_FACING_FRONT) 改为if (facing != null && facing != mFacing),demo禁用了前置摄像头,修改后,mFacing自定义的摄像头,即使用的摄像头
3、将demo预览控件改为我定义的mTextureView
4、删除了demo中所有与activity相关的代码
需要源码请下载TakePictrueInBackground
public class TakePictrueService extends IntentService implements TextureView.SurfaceTextureListener
{
private static final String TAG = "TakePictrueService";
private WindowManager windowManager;
private boolean isDestroy = false;
/**
* 默认使用前置摄像头: CameraCharacteristics.LENS_FACING_FRONT,0
* 背后摄像头为:CameraCharacteristics.LENS_FACING_BACK,1
*/
private int mFacing = CameraCharacteristics.LENS_FACING_FRONT;
/**
* 输出图片宽度
*/
private int pictrueWidth = 480;
/**
* 输出图片高度
*/
private int pictrueHeight = 680;
public TakePictrueService()
{
super(TAG);
}
@Override
public void onCreate()
{
super.onCreate();
try
{
Log.i(TAG, "onCreate");
windowManager = (WindowManager) TakePictrueService.this.getSystemService(Context.WINDOW_SERVICE);
mTextureView = new AutoFitTextureView(this);
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
1, 1,
WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
PixelFormat.TRANSLUCENT
);
layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
windowManager.addView(mTextureView, layoutParams);
mTextureView.setSurfaceTextureListener(this);
startBackgroundThread();
}catch (Exception e){}
}
@Override
public void onStart(@Nullable Intent intent, int startId)
{
Log.i(TAG, "onStart");
super.onStart(intent, startId);
}
@Override
protected void onHandleIntent(@Nullable Intent intent)
{
Log.i(TAG, "onHandleIntent");
while (!isDestroy)
{
try
{
Thread.sleep(10000);
if(allreadyOpen)
{
String name = new Date().getTime()+".jpg";
File dir = new File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS) + "/pictrue");
if(!dir.exists())
{
dir.mkdir();
}
mFile = new File(dir.getAbsolutePath(),name);
takePicture();
}
} catch (InterruptedException e)
{
Log.i(TAG,e.toString());
}
}
//takePicture();
}
private boolean allreadyOpen = false;
@Override
public void onDestroy()
{
super.onDestroy();
Log.i(TAG, "onDestroy");
isDestroy = true;
closeCamera();
stopBackgroundThread();
windowManager.removeView(mTextureView);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)
{
Log.i(TAG, "onSurfaceTextureAvailable");
openCamera(width, height);
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)
{
Log.i(TAG, "onSurfaceTextureSizeChanged");
configureTransform(width, height);
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface)
{
Log.i(TAG, "onSurfaceTextureDestroyed");
return true;
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface)
{
Log.i(TAG, "onSurfaceTextureUpdated");
}
//.......
//此处demo的相关代码已删除
}