Andriod Camera2 后台服务静默定时无预览拍照实现方式

最近在做一个项目,需要用到无预览拍照功能,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的相关代码已删除

}

 

你可能感兴趣的:(Andriod Camera2 后台服务静默定时无预览拍照实现方式)