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-feature标签。其他经常使用权限这里就不赘述。
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
Android后台服务拍照的解决方式的更多相关文章
- Android后台服务拍照
原文:https://blog.csdn.net/wurensen/article/details/47024961 一.背景介绍最近在项目中遇到一个需求,实现一个后台拍照的功能.一开始在网上寻找解决 ...
- BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第13章节--使用业务连接服务创建业务线解决方式 SP Apps中的BCS
BEGINNING SHAREPOINT® 2013 DEVELOPMENT 第13章节--使用业务连接服务创建业务线解决方式 SP Apps中的BCS 之前的联系中,你安装了一个业 ...
- Android之——常见Bug及其解决方式
转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46942139 1.android.view.WindowManager$BadTo ...
- android 后台服务定时通知
最近有个项目的要求是在程序退出之后,任然可以每天定时发通知,我们可以想下,其实就是后台开一个服务,然后时间到了就发下通知. 1.首先我们需要用到Service类. 先上代码在慢慢解释 package ...
- Android Studio安装更新终极解决方式
之前写过一篇Android SDK无法更新的博文,其实该方式对Android Studio同样有效,大伙可以下载网盘中分享的小软件,若搜索到通道后提示需要更细,也可以选择更新.参考:http://bl ...
- Android 常见内存泄漏的解决方式
在Android程序开发中.当一个对象已经不须要再使用了,本该被回收时.而另外一个正在使用的对象持有它的引用从而导致它不能被回收.这就导致本该被回收的对象不能被回收而停留在堆内存中,内存泄漏就产生了. ...
- 关于Cocos2d-x 3.0正式版 粒子问题在IOS上正常显示,在Android下有问题的解决方式
前几个在Cocos2d-x论坛上,有人提到粒子系统的问题..这里列举一下解决的方法: 或许到时候大家用粒子效果的时候也会发现这个问题,如今把这个问题的解决办法说出来.至于原因我也不知道是引擎的问题还是 ...
- 升级后开机就提示“android.process.acore”停止执行 --分析 解决方式
OTA升级的,升级引发的全部问题都是能够解释的,有的能解决,有的不能解决. 一个项目报了这个问题. 升级后开机就提示"android.process.acore"停止执行 抓取 a ...
- mac 系统开发android,真机调试解决方式(无数的坑之后吐血总结)
近期学习android开发,安装了ADT开发环境之后,启动模拟器,慢的要死啊,全然不如苹果的好用,没法,自己买个android手机,准备联机调试程序.没想到在这个过程中,遇到了好多的坑,作为一个新人, ...
随机推荐
- 优先队列 + 并查集 + 字典树 + 欧拉回路 + 树状数组 + 线段树 + 线段树点更新 + KMP +AC自动机 + 扫描线
这里给出基本思想和实现代码 . 优先队列 : 曾经做过的一道例题 坦克大战 struct node { int x,y,step; friend bool operator <(no ...
- iOS 代理设计模式
在项目中经常会用到代理的设计模式,这是iOS中一种消息传递的方式,也可以通过这种方式传递一些参数. 在项目中,刚开始我是用一些代理来传递参数的,但是慢慢觉得代理的代码比较block多,所以就更多的使用 ...
- golang闭包,传统斐波那契
package main import ( "fmt") func main() { f := fibonacci() for i := 0; i < 10 ...
- python实践
https://www.cnblogs.com/smallSevens/p/10016045.html --二手房交易记录 https://www.cnblogs.com/xiangyuecn/p/1 ...
- 计算某个时间段(2017-10-01 2017-12-01)内svn更新文件的MD5
#!/bin/sh svn up svn log -v -r {$1}:{$2} | grep / | grep -v xxx | sort -f -u | uniq | awk -F 'xxxx' ...
- POJ 1946 DP
折腾了一晚上 明天再写.. 2016.5.17 23:59 -> -> #include <cstdio> #include <cstring> #include ...
- [转]line-height1.5和line-height:150%的区别
line-height1.5和line-height:150%的区别 一.区别 区别体现在子元素继承时,如下: 父元素设置line-height:1.5会直接继承给子元素,子元素根据自己的font ...
- usaco 过路费 Cow Toll Paths, 2009 Dec
Description 翰家有 N 片草地,编号为 1 到 N ,彼此之间由 M 条双向道路连接,第 i 条道路连接了 Ai 和Bi,两片草地之间可能有多条道路,但没有道路会连接同一片草地,现有的道路 ...
- ★Java语法(二)——————————数据类型及装换
整数类型: 1.byte型:8位(1字节) 范围:-128~127 用法:byte x = 35 : 2.short型:16位(2字节) 范围:-32768~32767 用法:short x = ...
- C++对象的内存模型
1. 普通对象模型 对象是如何在内存中布局的? 成员 存放位置 访问范围 非静态数据成员 每一个对象体内 为该对象专有 静态数据成员 程序的静态存储区内,只有一份实体 为该类所有对象共享 成员函数(静 ...