Android实现后台连续静默拍照(无预览 不闪退)

简单的背景(一堆废话)
前段时间在做一个Android项目,其中涉及到需要实现“后台”静默打开前置摄像头连续拍照进行人脸检测。

尝试过网上各种方法,主要做法就是将预览透明+变小(0.1x0.1),但是这种做法并不能实现所谓的“后台”效果,你只能在一个透明界面啥也干不了,不能进行交互,或者出现闪退等问题。

这个问题基本困扰了我一个多月,在此感谢 https://blog.csdn.net/qq_31530015/article/details/52015170 ,让我一下有了新思路,一天就搞定了这个问题。

总体思路

      mini悬浮窗 + 间隔2s抓取一次预览帧 + 处理(人脸检测/保存图片)

 

 话不多说,上代码

项目源码指路: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; // 系统提示类型,重要
		Params.type = LayoutParams.TYPE_TOAST; // 系统提示类型,重要 上面的类型会报错:permission denied for this window type
 
		//布局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<String> getHomes() {
		List<String> names = new ArrayList<String>();
		PackageManager packageManager = this.getPackageManager();
		 
		Intent intent = new Intent(Intent.ACTION_MAIN);
		intent.addCategory(Intent.CATEGORY_HOME);
		List<ResolveInfo> 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<RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
		List<String> 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
————————————————
版权声明:本文为CSDN博主「CodingRae」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/CodingRae/article/details/89679758

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值