Android进阶——多线程间的通信之调用系统标准摄像头自动对焦并自动完成隐蔽拍照

引言

最近项目中需要做一个不启动预览界面,自动完成拍照的功能(嘿嘿,不要误会我没有做什么坏事),其实是正常的人脸识别功能,机器人自动扫描当有人来的时候,自动走上正前方迎宾并播放欢迎词,由于这一功能是无限循环的耗时且消耗资源,自然得放到子线程中,才不会影响主线程之间的人机交互。于是就有了一个小小小的较通用点的小模板——可以应用于简单常驻任务的监控(一直运行于APP的生命周期)吧,分享下希望大家给点改进建议。

一、功能及模块概述

1、自动对焦并拍照

我们知道系统为我们提供了拍照Activity,但是这样的话就启动了预览界面,而且无法在不影响交互的情况下拍照,所以只能是自己写利用Camera(也可以用Camera2这个高版本中的类),由于实时刷新的所以SurfaceView可以用作是预览的取景界面,再有一个就是自动对焦,如果不自动对焦的话就会出现拍出来的文字很模糊根本看不清。

 //自动对焦
 camera.autoFocus(new Camera.AutoFocusCallback() {
     @Override
     public void onAutoFocus(boolean success, Camera camera) {
         if(success){
             initCamera();//实现相机的参数初始化
             camera.cancelAutoFocus();//只有加上了这一句,才会自动对焦。
         }
     }
 });

2、全周期驻活监控并自动识别

前面也说了这是个耗时且无限循环的工作,明显不可能写在主线程中,但是如果直接写在主线程里的话可能容易被杀死,所以我选择的是白色驻活手段(具体的看下面的代码)基本能满足我的需求,写在自定义的Service里,并封装其他相关回调接口(主要用于调用Activity里的方法)。

3、同步请求

由于人脸识别过程可以分为两个步骤:拍照,拍照成功之后上传到服务器进行识别,这里我采用的是同步请求,因为采用异步的话就有可能导致上一条请求还没响应,新的又发送了,这根本没有必要,采用同步正是基于此,完成了一个人脸识别流程之后才进行下一个流程,如此循环,所以必须严格控制业务流程,先拍照成功之后才能上传,返回响应结果之后,再根据结果进行新的识别流程。

二、实现步骤

1、首先定义业务类,主要用于完成拍照和HTTP请求,分别传递的是一个CallBack回调类,用于在相应业务执行完毕之后触发下一业务的执行。

package crazymo.train.notifyacititydo.biz;

import static crazymo.train.notifyacititydo.biz.DetectService.listener;

/**
 * auther: CrazyMo_
 * Date: 2017/2/24
 * Time:10:48
 * Des:
 */

public class DetectFaceBiz {

    ///这里定义了自定义的CallBack类,原打算用于监听在拍照完成之后执行相关操作
    public void takePictrue(DetectService.OnFinishTakePicCallBack callBack){
        boolean istakePic=listener.onTakePhoto();
        /*if(istakePic) {
            Log.e("DetectFace","已结成功拍照");
            callBack.onTakePicFinish();
        }else{
            Log.e("DetectFace","TakePictrue failed");
        }*/
    }

    public void startDetect(DetectService.OnFinishDetectCallBack callBack){
        ///发起网络请求
        //do some network
        callBack.onDetectFinish();
    }
}

2、核心监控业务流程类

package crazymo.train.notifyacititydo.biz;

import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;

/**
 * auther: CrazyMo_
 * Date: 2017/2/24
 * Time:10:24
 * Des:
 */

public class DetectService extends Service {

    private DetectThread detectThread = null;
    public final static DetectFaceBiz biz = new DetectFaceBiz();
    private Context context;
    private Looper looper;
    private DetectHandler handler;
    public static OnTakePhotoListener listener;

    public DetectService(Context context) {
        //在构造方法里初始化并启动线程
        super();
        this.context = context;
        HandlerThread thread = new HandlerThread("LedControlService");
        thread.start();
        looper = context.getMainLooper();
        this.handler = new DetectHandler(looper);
        this.detectThread = new DetectThread();
        detectThread.start();
    }

    /**
     * 职责是用于改变线程的其他状态机,这里没有写出来。。
     */
    private class DetectHandler extends Handler {
        public DetectHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            DetectThread.setBizTag(msg.what);
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;//START_STICKY:如果service进程被kill掉,保留service的状态为开始状态,但不保留递送的intent对象。随后系统会尝试重新创建service,由于服务状态为开始状态,所以创建服务后一定会调用onStartCommand(Intent,int,int)方法。如果在此期间没有任何启动命令被传递到service,那么参数Intent将为null。 
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    /**
     * 启动服务
     */
    public void start() {
        if (context != null) {
            Intent service = new Intent(context, DetectService.class);
            context.startService(service);
        }
    }

    /**
     * 用于设置挂起或开始DetectThread 的正常工作
     * @param flag true 正常工作  false  挂起
     */
    public void setDetectingState(boolean flag){
        detectThread.setDetectState(flag);
    }

    public static class DetectThread extends Thread {
        public static boolean needDetect = false;
        public static int bizTag = 1;//用于标识业务类型的

        @Override
        public void run() {
            while (!(Thread.currentThread().isInterrupted())) {
                synchronized (this) {
                    while (!needDetect) {
                        try {
                            Log.e("DetectFace","DetectThread 启动并挂起ing");
                            wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                try {
                    switch (bizTag) {
                        case 1:
                            Log.e("DetectFace","预准备开始拍照流程");
                            biz.takePictrue(new OnFinishTakePicCallBack() {
                                @Override
                                public void onTakePicFinish() {
                                    ///拍照成功之后再调检测 bizTag=2;
                                    Log.e("DetectFace","已经拍照成功带上传到Face++");
                                    setBizTag(2);
                                }
                            });
                            break;
                        case 2:
                            Log.e("DetectFace","预准备开始检测流程");
                            biz.startDetect(new OnFinishDetectCallBack() {
                                @Override
                                public void onDetectFinish() {
                                    ///检测成功之后,根据结果再拍照进行下一轮的脸部识别 bizTag=1
                                    Log.e("DetectFace","已经检测成功,待重新拍照进行下一次检测流程");
                                    setBizTag(1);
                                }
                            });
                            break;
                        case -1:
                        default:
                            break;
                    }
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public synchronized void setDetectState(boolean isNeed) {
            needDetect = isNeed;
            if (needDetect){
                notify();
            }else {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        public synchronized static void setBizTag(int tag) {
            bizTag = tag;
        }
    }

    public void setOnDetectedListener(OnTakePhotoListener listener) {
        this.listener = listener;
    }

    /**
     * 用于给其他业务发送Handler消息
     *
     * @param msg
     */
    public void sendMessage(@NonNull int msg) {
        if (handler != null) {
            handler.sendEmptyMessage(msg);
        }
    }

    //用于调用Activity的方法而提供的回调接口
    public interface OnTakePhotoListener {
        boolean onTakePhoto();
    }
    //拍照成功之后触发的回调
    public interface OnFinishTakePicCallBack {
        void onTakePicFinish();
    }
    //上传服务器并返回响应结果时触发的回调
    public interface OnFinishDetectCallBack {
        void onDetectFinish();
    }
}

3、在Application中初始化

public class DetectApplication extends Application {
    private static DetectService detectService;

    @Override
    public void onCreate() {
        super.onCreate();
        init();
    }
    private void init(){
        if(detectService==null) {
            detectService = new DetectService(this);
        }
        detectService.start();
    }
    public static DetectService getDetectService(){
        return detectService;
    }
}

5、配置相关权限

尤其注意当targetSdkVersion大于等于24时,需要动态申请权限

 <!--读取网络信息状态 -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <!--获取当前wifi状态 -->
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    <!--允许程序改变网络连接状态 -->
    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE"/>
<uses-permission android:name="android.permission.CAMERA" />
    <!-- 在SDCard中创建与删除文件权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <!-- 往SDCard写入数据权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

5、在MainActivity里实现

package crazymo.train.notifyacititydo;

import android.app.Activity;
import android.content.Context;
import android.graphics.ImageFormat;
import android.hardware.Camera;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.Toast;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

import crazymo.train.notifyacititydo.biz.DetectService;

public class MainActivity extends AppCompatActivity implements DetectService.OnTakePhotoListener, View.OnClickListener {
    private DetectService service;
    private SurfaceView surfaceView;
    private Button takePickBtn;
    private Camera camera;
    private Camera.Parameters parameters = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init(){
        getView();
        initCamera();
        service=DetectApplication.getDetectService();
        Log.e("DetectFace","setOnDetectedListener 正常工作ing");
        service.setOnDetectedListener(MainActivity.this);
        service.setDetectingState(true);//开启Detect流程
        Log.e("DetectFace","setDetectState true 正常工作ing");
    }
    private void getView(){
        surfaceView= (SurfaceView) findViewById(R.id.camera_surfv);
        takePickBtn= (Button) findViewById(R.id.take_pic_btn);
        takePickBtn.setOnClickListener(this);
    }
    private void initCamera(){
        WindowManager manager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);//获取WM对象
        DisplayMetrics dm = new DisplayMetrics();
        manager.getDefaultDisplay().getMetrics(dm);
        surfaceView.getHolder()
                .setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        surfaceView.getHolder().setFixedSize(dm.widthPixels, dm.heightPixels);    //设置Surface分辨率
        surfaceView.getHolder().setSizeFromLayout();
        surfaceView.getHolder().setKeepScreenOn(true);// 屏幕常亮
        surfaceView.getHolder().addCallback(new SurfaceCallback());//为SurfaceView的句柄添加一个回调函数
    }
    @Override
    public boolean onTakePhoto() {
        takePhoto();
        return true;
    }

    @Override
    public void onClick(View v) {
        if(v.getId()==R.id.take_pic_btn){
            if (camera != null) {
                // 拍照
                camera.takePicture(null, null, new MyPictureCallback());
            }
        }
    }

    public void takePhoto(){
        if (camera != null) {
            // 拍照
            camera.takePicture(null, null, new MyPictureCallback());
        }
    }
    private final class MyPictureCallback implements Camera.PictureCallback {

        @Override
        public void onPictureTaken(byte[] data, Camera camera) {
            try {
               // bundle = new Bundle();
                //bundle.putByteArray("bytes", data);    //将图片字节数据保存在bundle当中,实现数据交换
                saveToSDCard(data); // 保存图片到sd卡中
                Toast.makeText(getApplicationContext(), "Take photo Success",
                        Toast.LENGTH_SHORT).show();
                DetectService.DetectThread.setBizTag(2);
                camera.startPreview(); // 拍完照后,重新开始预览

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 将拍下来的照片存放在SD卡中
     * @param data
     * @throws IOException
     */
    public static void saveToSDCard(byte[] data) throws IOException {
        Date date = new Date();
        SimpleDateFormat format = new SimpleDateFormat("yyyyMMddHHmmss"); // 格式化时间
        String filename = format.format(date) + ".jpg";
        File fileFolder = new File(Environment.getExternalStorageDirectory()
                + "/rebot/cache/");
        if (!fileFolder.exists()) { // 如果目录不存在,则创建一个名为"finger"的目录
            fileFolder.mkdir();
        }
        File jpgFile = new File(fileFolder, filename);
        FileOutputStream outputStream = new FileOutputStream(jpgFile); // 文件输出流
        outputStream.write(data); // 写入sd卡中
        outputStream.close(); // 关闭输出流
    }

    private final class SurfaceCallback implements SurfaceHolder.Callback {

        // 拍照状态变化时调用该方法
        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                                   int height) {
            //实现自动对焦

            if(camera!=null) {
                parameters = camera.getParameters(); // 获取各项参数
                parameters.setPreviewSize(width, height); // 设置预览大小
                // 设置预览照片时每秒显示多少帧的最小值和最大值
                parameters.setPreviewFpsRange(4, 10);
                parameters.setPictureFormat(ImageFormat.JPEG); // 设置图片格式
                parameters.set("jpeg-quality", 100); // 设置JPG照片的质量
                parameters.setPictureSize(width, height); // 设置保存的图片尺寸

            }
        }

        // 开始拍照时调用该方法
        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            try {
                camera = Camera.open(); // 打开摄像头
                camera.setPreviewDisplay(holder); // 设置用于显示拍照影像的SurfaceHolder对象
                camera.setDisplayOrientation(getPreviewDegree(MainActivity.this));
                //自动对焦
                camera.autoFocus(new Camera.AutoFocusCallback() {
                    @Override
                    public void onAutoFocus(boolean success, Camera camera) {
                        if(success){
                            initCamera();//实现相机的参数初始化
                            camera.cancelAutoFocus();//只有加上了这一句,才会自动对焦。
                        }
                    }

                });
                camera.startPreview(); // 开始预览
            } catch (Exception e) {
                e.printStackTrace();
            }

        }

        // 停止拍照时调用该方法
        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            if (camera != null) {
                camera.release(); // 释放照相机
                camera = null;
            }
        }
    }

    /**
     *   提供一个静态方法,用于根据手机方向获得相机预览画面旋转的角度
     */
    public static int getPreviewDegree(Activity activity) {
        // 获得手机的方向
        int rotation = activity.getWindowManager().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
    protected void onStop() {
        super.onStop();
        service.setDetectingState(false);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        service.setDetectingState(false);
        DetectService.DetectThread.setBizTag(-1);
    }
}

activity_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">


    <SurfaceView
        android:id="@+id/camera_surfv"
        android:layout_width="1dp"
        android:layout_height="1dp"
        />
    <Button
        android:id="@+id/take_pic_btn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="拍照"/>

</LinearLayout>

这里写图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值