前段时间在做一个Android项目,其中涉及到需要实现“后台”静默打开前置摄像头连续拍照进行人脸检测。
尝试过网上各种方法,主要做法就是将预览透明+变小(0.1x0.1),但是这种做法并不能实现所谓的“后台”效果,你只能在一个透明界面啥也干不了,不能进行交互,或者出现闪退等问题。
这个问题基本困扰了我一个多月,在此感谢 https://blog.csdn.net/qq_31530015/article/details/52015170 ,让我一下有了新思路,一天就搞定了这个问题。
项目源码指路:https://github.com/SleepyRae/PhoneSavior
在activity中启动一个service
aSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean f) {
//控制开关字体颜色
// Intent face = new Intent(FaceDetectActivity.this, CameraService.class);
// Intent face = new Intent(FaceDetectActivity.this, CameraActivity.class);
Intent face = new Intent(FaceDetectActivity.this, MyService.class);
if (f) {
aSwitch.setSwitchTextAppearance(FaceDetectActivity.this, R.style.s_true);
startService(face);
// startService(face);
ServiceIsOn = true;
Toast.makeText(getApplicationContext(), "开启人脸检测啦", Toast.LENGTH_SHORT).show();
} else {
aSwitch.setSwitchTextAppearance(FaceDetectActivity.this, R.style.s_false);
stopService(face);
ServiceIsOn = false;
//CameraActivity.finishActivity();
Toast.makeText(getApplicationContext(), "关掉人脸检测啦", Toast.LENGTH_SHORT).show();
}
}
});
MyService:
package com.example.inspiron.phonesavior.Service;
import android.app.ActivityManager;
import android.app.ActivityManager.RunningTaskInfo;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.provider.Settings;
import android.util.Log;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import java.util.ArrayList;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
public class MyService extends Service {
/**
* 自定义窗口
*/
private MyWindow myWindow;
/**
* 窗口管理者
*/
private WindowManager mWindowManager;
/**
* 窗口布局参数
*/
private LayoutParams Params;
private Handler handler = new Handler() {
public void handleMessage(Message msg) {
if (isHome()&& myWindow!=null) {
// 如果回到桌面,则显示悬浮窗
if (!myWindow.isAttachedToWindow()) {
mWindowManager.addView(myWindow, Params);
}
} else {
// 如果在非桌面,则去掉悬浮窗
/*if (myWindow.isAttachedToWindow()) {
mWindowManager.removeViewImmediate(myWindow);
}*/
}
super.handleMessage(msg);
};
};
@Override
public IBinder onBind(Intent arg0) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
Log.d("MyService", "onCreate");
// 定时器类
Timer timer = new Timer();
timer.schedule(task, 1000, 1000); // 1s后执行task,经过1s再次执行
//对于6.0以上的设备
if (Build.VERSION.SDK_INT >= 23) {
//如果支持悬浮窗功能
if (Settings.canDrawOverlays(getApplicationContext())) {
showWindow();
} else {
//手动去开启悬浮窗
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
getApplicationContext().startActivity(intent);
}
} else {
//6.0以下的设备直接开启
showWindow();
}
}
private void showWindow() {
//创建MyWindow的实例
myWindow = new MyWindow(getApplicationContext());
//窗口管理者
mWindowManager = (WindowManager) getSystemService(Service.WINDOW_SERVICE);
//窗口布局参数
Params = new WindowManager.LayoutParams();
//布局坐标,以屏幕左上角为(0,0)
Params.x = 0;
Params.y = 0;
//布局类型
Params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; // 系统提示类型,重要
//布局flags
Params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; // 不能抢占聚焦点
Params.flags = Params.flags | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
Params.flags = Params.flags | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; // 排版不受限制
Params.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
//布局的gravity
Params.gravity = Gravity.LEFT | Gravity.TOP;
//布局的宽和高
Params.width = 1;
Params.height = 1;
myWindow.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE:
Params.x = (int) event.getRawX() - myWindow.getWidth() / 2;
Params.y = (int) event.getRawY() - myWindow.getHeight() / 2;
//更新布局位置
mWindowManager.updateViewLayout(myWindow, Params);
break;
}
return false;
}
});
}
//定时发送message给Handler
TimerTask task = new TimerTask() {
@Override
public void run() {
Message message = new Message();
handler.sendMessage(message);
}
};
/**
* @return 获取桌面(Launcher)的包名
*/
private List getHomes() {
List names = new ArrayList();
PackageManager packageManager = this.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List resolveInfo = packageManager.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo info : resolveInfo) {
names.add(info.activityInfo.packageName);
}
return names;
}
/**
* @return 判断当前是否是桌面
*/
public boolean isHome() {
ActivityManager mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List rti = mActivityManager.getRunningTasks(1);
List strs = getHomes();
if (strs != null && strs.size() > 0) {
return strs.contains(rti.get(0).topActivity.getPackageName());
} else {
return false;
}
}
@Override
public void onDestroy() {
super.onDestroy();
myWindow = null;
}
}
MyWindow:
package com.example.inspiron.phonesavior.Service;
import android.app.Service;
import android.content.Context;
import android.graphics.*;
import android.hardware.Camera;
import android.media.FaceDetector;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.TextureView.SurfaceTextureListener;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.example.inspiron.phonesavior.R;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
public class MyWindow extends LinearLayout implements SurfaceTextureListener {
private TextureView textureView;
/**
* 相机类
*/
private Camera myCamera;
private Context context;
private WindowManager mWindowManager;
private int num = 0;
private int curnum = 0;
private Bitmap bitmap_get = null;
private int count = 0;
public MyWindow(Context context) {
super(context);
LayoutInflater.from(context).inflate(R.layout.window, this);
this.context = context;
initView();
}
private void initView() {
textureView = (TextureView) findViewById(R.id.textureView);
textureView.setSurfaceTextureListener(this);
mWindowManager = (WindowManager) context.getSystemService(Service.WINDOW_SERVICE);
}
@Override
public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
if (myCamera == null) {
// 创建Camera实例
//尝试开启前置摄像头
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
for (int camIdx = 0, cameraCount = Camera.getNumberOfCameras(); camIdx < cameraCount; camIdx++) {
Camera.getCameraInfo(camIdx, cameraInfo);
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
try {
Log.d("Demo", "tryToOpenCamera");
myCamera = Camera.open(camIdx);
} catch (RuntimeException e) {
e.printStackTrace();
}
}
}
try {
// 设置预览在textureView上
myCamera.setPreviewTexture(surface);
myCamera.setDisplayOrientation(SetDegree(MyWindow.this));
// 开始预览
myCamera.startPreview();
handler.sendEmptyMessage(BUFFERTAG);
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void getPreViewImage() {
if (myCamera != null){
myCamera.setPreviewCallback(new Camera.PreviewCallback(){
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Camera.Size size = camera.getParameters().getPreviewSize();
try{
YuvImage image = new YuvImage(data, ImageFormat.NV21, size.width, size.height, null);
if(image!=null){
ByteArrayOutputStream stream = new ByteArrayOutputStream();
image.compressToJpeg(new Rect(0, 0, size.width, size.height), 80, stream);
bitmap_get = BitmapFactory.decodeByteArray(stream.toByteArray(), 0, stream.size());
//**********************
//因为图片会放生旋转,因此要对图片进行旋转到和手机在一个方向上
bitmap_get = rotateMyBitmap(bitmap_get);
//**********************************
stream.close();
}
}catch(Exception ex){
Log.e("Sys","Error:"+ex.getMessage());
}
}
});
}
}
private void myFace(Bitmap bitmap) {
bitmap = bitmap.copy(Bitmap.Config.RGB_565, true);
//假设最多有1张脸
int MAXfFaces = 1;
int numOfFaces = 0;
FaceDetector mFaceDetector = new FaceDetector(bitmap.getWidth(),bitmap.getHeight(),MAXfFaces);
FaceDetector.Face[] mFace = new FaceDetector.Face[MAXfFaces];
//获取实际上有多少张脸
numOfFaces = mFaceDetector.findFaces(bitmap, mFace);
Log.v("------------->", "pic num:" + num + " face num:"+numOfFaces +" count:"+count);
if(numOfFaces == 1 && num!=curnum){
count++;
curnum = num;
Log.d("pic num:" + num, " eyesDistance:"+ mFace[0].eyesDistance() +" confidence:"+ mFace[0].confidence());
}
}
public Bitmap rotateMyBitmap(Bitmap mybmp){
//*****旋转一下
Matrix matrix = new Matrix();
matrix.postRotate(270);
Bitmap bitmap = Bitmap.createBitmap(mybmp.getWidth(), mybmp.getHeight(), Bitmap.Config.ARGB_8888);
Bitmap nbmp2 = Bitmap.createBitmap(mybmp, 0,0, mybmp.getWidth(), mybmp.getHeight(), matrix, true);
saveImage(nbmp2);
return nbmp2;
};
public void saveImage(Bitmap bmp) {
myFace(bmp);
/*String fileName ="Camera"+ num +".jpg";
File file = new File(getExternalStorageDirectory(), fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
bmp.compress(Bitmap.CompressFormat.JPEG, 100, fos);
fos.flush();
fos.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}*/
}
public static final int BUFFERTAG = 100;
public static final int BUFFERTAG1 = 101;
private boolean isGetBuffer = true;
Handler handler = new Handler(){
public void handleMessage(android.os.Message msg) {
switch(msg.what){
case BUFFERTAG:
if(count > 60){ //十分钟提示
count = 0;
Toast.makeText(context.getApplicationContext(), "检测到您持续用眼,请注意用眼", Toast.LENGTH_SHORT).show();
}
if(isGetBuffer){
num++;
getPreViewImage();
handler.sendEmptyMessageDelayed(BUFFERTAG1, 3000);
}else{
myCamera.setPreviewCallback(null);
}
break;
case BUFFERTAG1:
myCamera.setPreviewCallback(null);
handler.sendEmptyMessageDelayed(BUFFERTAG, 5000);
break ;
}
};
};
Runnable runnable=new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
//要做的事情,这里再次调用此Runnable对象,以实现每两秒实现一次的定时器操作
handler.postDelayed(this, 10000);
Log.d("test", "running!!!");
}
};
private int SetDegree(MyWindow myWindow) {
// 获得手机的方向
int rotation = mWindowManager.getDefaultDisplay().getRotation();
int degree = 0;
// 根据手机的方向计算相机预览画面应该选择的角度
switch (rotation) {
case Surface.ROTATION_0:
degree = 90;
break;
case Surface.ROTATION_90:
degree = 0;
break;
case Surface.ROTATION_180:
degree = 270;
break;
case Surface.ROTATION_270:
degree = 180;
break;
}
return degree;
}
@Override
public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
myCamera.stopPreview(); //停止预览
myCamera.release(); // 释放相机资源
myCamera = null;
return false;
}
@Override
public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
}
@Override
public void onSurfaceTextureUpdated(SurfaceTexture surface) {
}
}
悬浮窗: https://blog.csdn.net/qq_31530015/article/details/52015170
抓取预览帧:https://blog.csdn.net/u010277233/article/details/52193068