Android后台服务拍照的解决方式
Posted yutingliuyl
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android后台服务拍照的解决方式相关的知识,希望对你有一定的参考价值。
一、背景介绍
近期在项目中遇到一个需求。实现一个后台拍照的功能。
一開始在网上寻找解决方式。也尝试了非常多种实现方式,都没有惬意的方案。只是确定了难点:即拍照要先预览,然后再调用拍照方法。问题也随之而来。既然是要实现后台拍照,就希望能在Service中或者是异步的线程中进行,这和预览这个步骤有点相矛盾。
那有什么方式可以既能正常的实现预览、拍照,又不让使用者察觉呢?想必大家也会想到一个取巧的办法:隐藏预览界面。
说明一下,这仅仅是我在摸索中想到的一种解决方式。能非常好的解决业务上的需求。
对于像非常多手机厂商提供的“找回手机”功能时提供的拍照。我不确定他们的实现方式。假设大家有更好的实现方案。最好还是交流一下。
关于这个功能是否侵犯了用户的隐私,影响用户的安全等等问题,不在我们的考虑和讨论范围之内。
二、方案介绍
方案实现步骤大致例如以下:
先大概介绍下业务需求:从用户登录到注销这段时间内,收到后台拍照的指令后完毕拍照、保存、上传。
下面会基于这个业务场景来具体介绍各步骤的实现。
1.初始化拍照的预览界面
这个全局窗体要是不可见的。不影响后面的界面正常交互。所以。就想到用全局的context来获取WindowManager对象管理这个全局窗体。
接下来直接看代码:
package com.yuexunit.zjjk.service; import com.yuexunit.zjjk.util.Logger; import android.content.Context; import android.view.SurfaceView; import android.view.WindowManager; import android.view.WindowManager.LayoutParams; /** * 隐藏的全局窗体。用于后台拍照 * * @author WuRS */ public class CameraWindow { private static final String TAG = CameraWindow.class.getSimpleName(); private static WindowManager windowManager; private static Context applicationContext; private static SurfaceView dummyCameraView; /** * 显示全局窗体 * * @param context */ public static void show(Context context) { if (applicationContext == null) { applicationContext = context.getApplicationContext(); windowManager = (WindowManager) applicationContext .getSystemService(Context.WINDOW_SERVICE); dummyCameraView = new SurfaceView(applicationContext); LayoutParams params = new LayoutParams(); params.width = 1; params.height = 1; params.alpha = 0; params.type = LayoutParams.TYPE_SYSTEM_ALERT; // 屏蔽点击事件 params.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL | LayoutParams.FLAG_NOT_FOCUSABLE | LayoutParams.FLAG_NOT_TOUCHABLE; windowManager.addView(dummyCameraView, params); Logger.d(TAG, TAG + " showing"); } } /** * @return 获取窗体视图 */ public static SurfaceView getDummyCameraView() { return dummyCameraView; } /** * 隐藏窗体 */ public static void dismiss() { try { if (windowManager != null && dummyCameraView != null) { windowManager.removeView(dummyCameraView); Logger.d(TAG, TAG + " dismissed"); } } catch (Exception e) { e.printStackTrace(); } } }
可能很多人遇到的问题就是卡在没有预览界面该怎样拍照这里,希望这样一种取巧的方式能够帮助大家在以后的项目中遇到无法直接解决这个问题时。能够考虑从另外的角度切入去解决这个问题。
2.完毕Service拍照功能
这里将对上面的兴许步骤进行合并。先上代码:
package com.yuexunit.zjjk.service; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import android.app.Service; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapFactory.Options; import android.hardware.Camera; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.PictureCallback; import android.os.IBinder; import android.os.Message; import android.text.TextUtils; import android.view.SurfaceView; import com.yuexunit.sortnetwork.android4task.UiHandler; import com.yuexunit.sortnetwork.task.TaskStatus; import com.yuexunit.zjjk.network.RequestHttp; import com.yuexunit.zjjk.util.FilePathUtil; import com.yuexunit.zjjk.util.ImageCompressUtil; import com.yuexunit.zjjk.util.Logger; import com.yuexunit.zjjk.util.WakeLockManager; /** * 后台拍照服务。配合全局窗体使用 * * @author WuRS */ public class CameraService extends Service implements PictureCallback { private static final String TAG = CameraService.class.getSimpleName(); private Camera mCamera; private boolean isRunning; // 是否已在监控拍照 private String commandId; // 指令ID @Override public void onCreate() { Logger.d(TAG, "onCreate..."); super.onCreate(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { WakeLockManager.acquire(this); Logger.d(TAG, "onStartCommand..."); startTakePic(intent); return START_NOT_STICKY; } private void startTakePic(Intent intent) { if (!isRunning) { commandId = intent.getStringExtra("commandId"); SurfaceView preview = CameraWindow.getDummyCameraView(); if (!TextUtils.isEmpty(commandId) && preview != null) { autoTakePic(preview); } else { stopSelf(); } } } private void autoTakePic(SurfaceView preview) { Logger.d(TAG, "autoTakePic..."); isRunning = true; mCamera = getFacingFrontCamera(); if (mCamera == null) { Logger.w(TAG, "getFacingFrontCamera return null"); stopSelf(); return; } try { mCamera.setPreviewDisplay(preview.getHolder()); mCamera.startPreview();// 開始预览 // 防止某些手机拍摄的照片亮度不够 Thread.sleep(200); takePicture(); } catch (Exception e) { e.printStackTrace(); releaseCamera(); stopSelf(); } } private void takePicture() throws Exception { Logger.d(TAG, "takePicture..."); try { mCamera.takePicture(null, null, this); } catch (Exception e) { Logger.d(TAG, "takePicture failed!"); e.printStackTrace(); throw e; } } private Camera getFacingFrontCamera() { CameraInfo cameraInfo = new CameraInfo(); int numberOfCameras = Camera.getNumberOfCameras(); for (int i = 0; i < numberOfCameras; i++) { Camera.getCameraInfo(i, cameraInfo); if (cameraInfo.facing == CameraInfo.CAMERA_FACING_FRONT) { try { return Camera.open(i); } catch (Exception e) { e.printStackTrace(); } } } return null; } @Override public void onPictureTaken(byte[] data, Camera camera) { Logger.d(TAG, "onPictureTaken..."); releaseCamera(); try { // 大于500K,压缩预防内存溢出 Options opts = null; if (data.length > 500 * 1024) { opts = new Options(); opts.inSampleSize = 2; } Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, opts); // 旋转270度 Bitmap newBitmap = ImageCompressUtil.rotateBitmap(bitmap, 270); // 保存 String fullFileName = FilePathUtil.getMonitorPicPath() + System.currentTimeMillis() + ".jpeg"; File saveFile = ImageCompressUtil.convertBmpToFile(newBitmap, fullFileName); ImageCompressUtil.recyleBitmap(newBitmap); if (saveFile != null) { // 上传 RequestHttp.uploadMonitorPic(callbackHandler, commandId, saveFile); } else { // 保存失败。关闭 stopSelf(); } } catch (Exception e) { e.printStackTrace(); stopSelf(); } } private UiHandler callbackHandler = new UiHandler() { @Override public void receiverMessage(Message msg) { switch (msg.arg1) { case TaskStatus.LISTENNERTIMEOUT: case TaskStatus.ERROR: case TaskStatus.FINISHED: // 请求结束,关闭服务 stopSelf(); break; } } }; // 保存照片 private boolean savePic(byte[] data, File savefile) { FileOutputStream fos = null; try { fos = new FileOutputStream(savefile); fos.write(data); fos.flush(); fos.close(); return true; } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } return false; } private void releaseCamera() { if (mCamera != null) { Logger.d(TAG, "releaseCamera..."); mCamera.stopPreview(); mCamera.release(); mCamera = null; } } @Override public void onDestroy() { super.onDestroy(); Logger.d(TAG, "onDestroy..."); commandId = null; isRunning = false; FilePathUtil.deleteMonitorUploadFiles(); releaseCamera(); WakeLockManager.release(); } @Override public IBinder onBind(Intent intent) { return null; } }代码也不多。只是有几个点须要特别注意下,
代码大家能够优化下。 把非正常业务逻辑统一处理掉。或者是。使用自己定义的UncaughtExceptionHandler去处理未捕获的异常。
三、总结
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
以上是关于Android后台服务拍照的解决方式的主要内容,如果未能解决你的问题,请参考以下文章
如何使用相机在 Android 上的后台服务中拍照? [复制]
Android项目实战 | 从零开始写app(14)实现图片发布模块 | 必知必会之调用系统相机拍照相册一一解决android7 打开相机闪退奔溃问题