使用SurfaceView实现手机息屏状态下的静默拍照保存,上传服务器
注意:本文章只适用于技术交流,请你友好交流净化开发环境
思考
- 由于谷歌强制在Android应用开发中编写拍照程序是必需要有图像预览的。这对那些恶意程序比如Android中泛滥的Service在后台偷偷记录手机用户的行为与周边信息。这样的门槛还包括手机厂商自带的相机软件在拍照时必须是有声音,这样要避免一些偷拍的情况;据说oppo find系列及vivo Nex系列可以检测出那些流氓软件这么做了。
步骤
创建一个surfaView对象
preview = new SurfaceView(this);holder = preview.getHolder();// deprecated setting, but required on Android versions prior to 3.0holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);holder.addCallback(new SurfaceHolder.Callback() {@Override//The preview must happen at or after this point or takePicture failspublic void surfaceCreated(SurfaceHolder holder) {//创建成功以后打开相机/*** camaraType: Camera.CameraInfo.CAMERA_FACING_FRONT :打开前置摄像头* camaraType: Camera.CameraInfo.CAMERA_FACING_BACK :打开后置摄像头*/try {camera = Camera.open(camaraType);try {camera.setPreviewDisplay(holder);} catch (IOException e) {throw new RuntimeException(e);}camera.startPreview();Log.d(TAG, "Started preview");camera.takePicture(null, null, pictureCallback);} catch (Exception e) {if (camera != null)camera.release();throw new RuntimeException(e);}}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}});
- 使用WindowManager增加一个1*1px的悬浮框
wm = (WindowManager)getSystemService(Context.WINDOW_SERVICE);//设置悬浮框为:1 * 1 :记得用后将其remove否则其他界面得不到交点,并且下拉框会有提示:应用在他应用的上层显示的应用,关于这个设置选项WindowManager.LayoutParams params = new WindowManager.LayoutParams(1, 1, //设置成宽:1px , 高:1pxWindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY,0, PixelFormat.UNKNOWN);/*** 根据不同的版本设置:TYPE_APPLICATION_OVERLAY 在低于26版本中报错崩溃* 但是在Android O的系统中,google规定申请android.permission.SYSTEM_ALERT_WINDOW权限的应用需要给悬浮窗口设置如下params.type*/if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;}
将SurfaceView添加到WindowManager中
wm.addView(preview, params);
拍照结果的回掉保存(
注意: 上层的1*1px的拍照布局 : 一定要移除,一定要移除,一定要移除,重要的妖怪打三遍---不然切换点击屏幕其他位置手机是获取不到焦点,没有反应的
)/*** 拍照开始后结果的回调*/ private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {@Overridepublic void onPictureTaken(byte[] data, Camera camera) {Log.d(TAG, "onPictureTaken");if(null == data){return;}Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);camera.stopPreview();Matrix matrix = new Matrix();matrix.postRotate((float) 270.0); //旋转拍照结果,可能方向不正确bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),bitmap.getHeight(), matrix, false);Log.d(TAG, "original bitmap width: " + bitmap.getWidth() +" height: " + bitmap.getHeight());Bitmap sizeBitmap = Bitmap.createScaledBitmap(bitmap,bitmap.getWidth()/3, bitmap.getHeight()/3, true);Log.d(TAG,"size bitmap width "+sizeBitmap.getWidth()+" height "+sizeBitmap.getHeight());//裁剪bitmapint leftOffset = (int)(sizeBitmap.getWidth() * 0.25);int topOffset = (int)(sizeBitmap.getHeight() * 0.25);Rect rect = new Rect(leftOffset, topOffset, sizeBitmap.getWidth() - leftOffset,sizeBitmap.getHeight() - topOffset);Bitmap rectBitmap = Bitmap.createBitmap(sizeBitmap,rect.left, rect.top, rect.width(), rect.height());try {//保存图片FileOutputStream outputStream = new FileOutputStream(Environment.getExternalStorageDirectory().toString()+"/photoResize.jpg");sizeBitmap.compress(Bitmap.CompressFormat.JPEG, 30, outputStream);outputStream.close();FileOutputStream outputStreamOriginal = new FileOutputStream(Environment.getExternalStorageDirectory().toString()+"/photoOriginal.jpg");bitmap.compress(Bitmap.CompressFormat.JPEG, 20, outputStreamOriginal);outputStreamOriginal.close();FileOutputStream outputStreamCut = new FileOutputStream(Environment.getExternalStorageDirectory().toString()+"/photoCut.jpg");rectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStreamCut);outputStreamCut.close();//通知系统相册更新sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+ Environment.getExternalStorageDirectory().toString()+"/photoResize.jpg")));sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://"+ Environment.getExternalStorageDirectory().toString()+"/photoOriginal.jpg")));sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment.getExternalStorageDirectory().toString() + "/photoCut.jpg")));Log.d(TAG,"picture saved!");if (camera != null) {camera.release();//移除上层的1*1px的拍照布局 : 一定要移除,不然点击屏幕其他位置手机没有反应wm.removeView(preview);}Toast.makeText(TakePhotoActy.this , "照片保存成功,可以开启服务上传照片然后注意删除本地相册!" , Toast.LENGTH_SHORT).show(); // System.exit(0); //如果没有设置removeView()方法,可以通过强制退出实现焦点回归} catch(Exception e) {e.printStackTrace();}} };
- 添加权限(导入三方权限管理) 点击查看: xxpermissions
//android6.0以上系统需要动态申请拍照及存储权限,对于测试可以手动打开权限管理给与对应权限即可
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" /> 权限管理
//使用三方控件:https://github.com/getActivity/XXPermissions 简单演示 //build中导入 implementation 'com.hjq:xxpermissions:5.0' if (Build.VERSION.SDK_INT >= 23) {requestPermission();} private void requestPermission() {//1. 检查是否已经有该权限XXPermissions.with(this).constantRequest() //可设置被拒绝后继续申请,直到用户授权或者永久拒绝.permission(Permission.WRITE_EXTERNAL_STORAGE , Permission.CAMERA) //不指定权限则自动获取清单中的危险权限.request(new OnPermission() {@Overridepublic void hasPermission(List<String> granted, boolean isAll) {if (isAll) { // Toast.makeText(TakePhotoActy.this, "获取权限成功", Toast.LENGTH_SHORT).show();}else { // Toast.makeText(TakePhotoActy.this, "获取权限成功,部分权限未正常授予", Toast.LENGTH_SHORT).show();}}@Overridepublic void noPermission(List<String> denied, boolean quick) {if(quick) {Toast.makeText(TakePhotoActy.this, "拍照需要你授权,否则不能正常使用", Toast.LENGTH_SHORT).show();//如果是被永久拒绝就跳转到应用权限系统设置页面 // XXPermissions.gotoPermissionSettings(TakePhotoActy.this);}else {Toast.makeText(TakePhotoActy.this, "获取权限失败", Toast.LENGTH_SHORT).show();}}});}
全部示例代码(在小米8:8.0系统,魅族6.0上均测试通过)
- activity代码:
/*** created by shi on 2018/9/10/010*/
public class TakePhotoActy extends Activity implements View.OnClickListener {private static final String TAG = "shiq";private SurfaceView preview;private SurfaceHolder holder;private final int MESSAGE_LOGIN = 1;private int camaraType = Camera.CameraInfo.CAMERA_FACING_FRONT;/*** 测试息屏状态10s拍照效果*/private Handler handler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {if (msg.what == MESSAGE_LOGIN) {Log.e(TAG, "我是收到的拍照界面");setTakePhoto();}return true;}});private WindowManager wm;@Overrideprotected void onCreate(@Nullable Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_take);initView();}private void initView() {Button bt_photo_force = findViewById(R.id.bt_photo_force);bt_photo_force.setOnClickListener(this);Button bt_photo_back = findViewById(R.id.bt_photo_back);bt_photo_back.setOnClickListener(this);if (Build.VERSION.SDK_INT >= 23) {requestPermission();}//验证息屏状态下的拍照:五秒后发送//handler.sendEmptyMessageDelayed(MESSAGE_LOGIN, 10000);}/*** 权限管理请求:需要存储及相机权限,如果上传到服务器后删除,加上请求读取权限*/private void requestPermission() {//1. 检查是否已经有该权限XXPermissions.with(this).constantRequest() //可设置被拒绝后继续申请,直到用户授权或者永久拒绝.permission(Permission.WRITE_EXTERNAL_STORAGE , Permission.CAMERA) //不指定权限则自动获取清单中的危险权限.request(new OnPermission() {@Overridepublic void hasPermission(List<String> granted, boolean isAll) {if (isAll) {
// Toast.makeText(TakePhotoActy.this, "获取权限成功", Toast.LENGTH_SHORT).show();}else {
// Toast.makeText(TakePhotoActy.this, "获取权限成功,部分权限未正常授予", Toast.LENGTH_SHORT).show();}}@Overridepublic void noPermission(List<String> denied, boolean quick) {if(quick) {Toast.makeText(TakePhotoActy.this, "拍照需要你授权,否则不能正常使用", Toast.LENGTH_SHORT).show();//如果是被永久拒绝就跳转到应用权限系统设置页面
// XXPermissions.gotoPermissionSettings(TakePhotoActy.this);}else {Toast.makeText(TakePhotoActy.this, "获取权限失败", Toast.LENGTH_SHORT).show();}}});}private Camera camera = null;@Overridepublic void onClick(View v) {switch (v.getId()) {case R.id.bt_photo_force:camaraType = Camera.CameraInfo.CAMERA_FACING_FRONT;break;case R.id.bt_photo_back:camaraType = Camera.CameraInfo.CAMERA_FACING_BACK;break;}setTakePhoto();}private void setTakePhoto() {preview = new SurfaceView(this);holder = preview.getHolder();// deprecated setting, but required on Android versions prior to 3.0holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);holder.addCallback(new SurfaceHolder.Callback() {@Override//The preview must happen at or after this point or takePicture failspublic void surfaceCreated(SurfaceHolder holder) {//创建成功以后打开相机/*** camaraType: Camera.CameraInfo.CAMERA_FACING_FRONT :打开前置摄像头* camaraType: Camera.CameraInfo.CAMERA_FACING_BACK :打开后置摄像头*/try {camera = Camera.open(camaraType);try {camera.setPreviewDisplay(holder);} catch (IOException e) {throw new RuntimeException(e);}camera.startPreview();Log.d(TAG, "Started preview");camera.takePicture(null, null, pictureCallback);} catch (Exception e) {if (camera != null)camera.release();throw new RuntimeException(e);}}@Overridepublic void surfaceDestroyed(SurfaceHolder holder) {}@Overridepublic void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {}});wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);//设置悬浮框为:1 * 1 :记得用后将其remove否则其他界面得不到交点,并且下拉框会有提示:应用在他应用的上层显示的应用,关于这个设置选项WindowManager.LayoutParams params = new WindowManager.LayoutParams(1, 1, //设置成宽:1px , 高:1pxWindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, 0, PixelFormat.UNKNOWN);/*** 根据不同的版本设置:TYPE_APPLICATION_OVERLAY 在低于26版本中报错崩溃* 但是在Android O的系统中,google规定申请android.permission.SYSTEM_ALERT_WINDOW权限的应用需要给悬浮窗口设置如下params.type*/if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {params.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;}wm.addView(preview, params);}/*** 拍照开始后结果的回调*/private Camera.PictureCallback pictureCallback = new Camera.PictureCallback() {@Overridepublic void onPictureTaken(byte[] data, Camera camera) {Log.d(TAG, "onPictureTaken");if (null == data) {return;}Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);camera.stopPreview();Matrix matrix = new Matrix();matrix.postRotate((float) 270.0); //旋转拍照结果,可能方向不正确bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),bitmap.getHeight(), matrix, false);Log.d(TAG, "original bitmap width: " + bitmap.getWidth() +" height: " + bitmap.getHeight());Bitmap sizeBitmap = Bitmap.createScaledBitmap(bitmap,bitmap.getWidth() / 3, bitmap.getHeight() / 3, true);Log.d(TAG, "size bitmap width " + sizeBitmap.getWidth() + " height " + sizeBitmap.getHeight());//裁剪bitmapint leftOffset = (int) (sizeBitmap.getWidth() * 0.25);int topOffset = (int) (sizeBitmap.getHeight() * 0.25);Rect rect = new Rect(leftOffset, topOffset, sizeBitmap.getWidth() - leftOffset,sizeBitmap.getHeight() - topOffset);Bitmap rectBitmap = Bitmap.createBitmap(sizeBitmap,rect.left, rect.top, rect.width(), rect.height());try {//保存图片FileOutputStream outputStream = new FileOutputStream(Environment.getExternalStorageDirectory().toString() + "/photoResize.jpg");sizeBitmap.compress(Bitmap.CompressFormat.JPEG, 30, outputStream);outputStream.close();FileOutputStream outputStreamOriginal = new FileOutputStream(Environment.getExternalStorageDirectory().toString() + "/photoOriginal.jpg");bitmap.compress(Bitmap.CompressFormat.JPEG, 20, outputStreamOriginal);outputStreamOriginal.close();FileOutputStream outputStreamCut = new FileOutputStream(Environment.getExternalStorageDirectory().toString() + "/photoCut.jpg");rectBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStreamCut);outputStreamCut.close();//通知系统相册更新sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment.getExternalStorageDirectory().toString() + "/photoResize.jpg")));sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment.getExternalStorageDirectory().toString() + "/photoOriginal.jpg")));sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + Environment.getExternalStorageDirectory().toString() + "/photoCut.jpg")));Log.d(TAG, "picture saved!");if (camera != null) {camera.release();//移除上层的1*1px的拍照布局 : 一定要移除,不然点击屏幕其他位置手机没有反应wm.removeView(preview);}Toast.makeText(TakePhotoActy.this, "照片保存成功,可以开启服务上传照片然后注意删除本地相册!", Toast.LENGTH_SHORT).show();//System.exit(0); //如果没有设置removeView()方法,可以通过强制退出实现焦点回归} catch (Exception e) {e.printStackTrace();}}};
}
布局文件很简单就是普通的两个button测试
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"><Button android:id="@+id/bt_photo_force"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="前置拍照"/><Button android:id="@+id/bt_photo_back"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="后置拍照" /></LinearLayout
使用SurfaceView实现手机息屏状态下的静默拍照保存,上传服务器相关推荐
- 计算机锁屏之后QQ音乐停止播放了,MAC电脑如何在息屏状态下让QQ音乐能继续播放音乐...
MAC电脑如何在息屏状态下让QQ音乐能继续播放音乐 一般MAC电脑息屏之后,大部分的应用都是会停止运行的,比如QQ音乐,就不会再播放音乐.今天小编就跟大家分享下MAC电脑如何在息屏状态下让QQ音乐能继 ...
- Android息屏状态下启动App
Android息屏状态下启动App 需求简介 分析 1.点亮屏幕 2.通过startActivity方法启动App 启动失败问题 原因 解决 需求简介 设备在息屏的状态下,通过特定的物理按键去启动Ca ...
- 锁屏界面提示某些设置已隐藏_分享华为手机锁屏状态下几大隐藏小功能,快来体验...
分享华为手机锁屏状态下几大隐藏小功能. 一.熄屏显示 平常想要在手机上看时间.日期等信息,需要点亮屏幕,很费时费电,还损耗手机按键. 熄屏显示,支持在熄屏状态下显示时间.日期.手机电量等基础信息,更有 ...
- Android APP息屏状态下收到通知解决方案
1.问题 最近负责的Android APP,用户反馈无法收到通知,尤其是息屏状态下无法收到通知. 这些APP,笔者以前都测试过,可以收到推送的.但测试以后,发现在新的Android上,确实收不到通知和 ...
- android熄屏微信收到原理,求助,如何才能在息屏状态下显示收到的微信消息内容?...
5楼搞错了...重发给你. 本帖最后由 散闲游人 于 2018-4-14 14:18 编辑 如P20的设置和手机管家和m10是一样的,请参考如下设置: 一:点击手机管家,点启动管理,以进入手动管理为例 ...
- Android 后台实现录像,无页面息屏状态下后台录像
用的是电量图标,前台服务,华为手机验证OK public class VideoRecorderService extends Service implements SurfaceHolder.Cal ...
- Android app开发:息屏状态下唤醒屏幕
方式1:申请wake_lock唤醒屏幕 //权限: <uses-permission android:name="android.permission.WAKE_LOCK" ...
- 2022-06-30 Android app WakeLock息屏状态下唤醒屏幕并且解锁demo
一.解锁 //屏锁管理器 KeyguardManager km= (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE ...
- android shell检查是否锁屏_锁屏状态下的华为手机不显示消息?这样设置!
点击蓝字关注我们 最近两天同事小张总是一副闷闷不乐的样子,小编琢磨着,刚过完情人节,这状态不对呀. 后来一问才知道,原来是小张晚上打游戏,女朋友微信找他,小张没看到,所以俩人就吵了起来.果然,男人们都 ...
最新文章
- 影响网站转化率的10大误区(上)
- 码这么多字只为搞懂如何在Python和Java中使用JSON
- 详解Python垃圾回收机制
- Linux下C++连MySQL数据库
- APP测试流程和测试点
- Linux Ubuntu 安装编译Opencv 3.4.3 C++开发环境
- 【实用工具】之CSDN表格模板
- mongodb处理库 php_MongoDB数据库常用操作PHP代码
- [数据可视化] 饼图(Pie Chart)
- 计划行为理论(TPB,Theory of Planned Behavior)
- ubuntu如何安装libz库
- 详解Unity中的粒子系统Particle System (八)
- php拼音首拼,PHP 汉字转拼音(可首字母)
- 如何设置迪文T5L串口屏的防盗版功能?
- 量化交易入门笔记-KD指标策略
- Ocean Color数据批量下载——海洋物理分布式活动档案中心PO.DAAC
- 【SQL Server】10分钟快速安装SQL Server
- Appium中使用swipe方法时候出现的问题建议使用flick方法
- 超简单集成ML kit 实现听写单词播报
- 微信搜一搜下拉词自动化批量采集机器人
热门文章
- CTF Crypto RSA合集(新生赛难度)
- 余额宝漏洞 可绕过用户登录 5W奖励“白帽子”
- 记录一次联通cdn劫持的请求响应报文
- 22西北大学网络和数据中心软件专硕845考研上岸备考经
- 骆昊python100天百度网盘_GitHub - wnxy/Python-100-Days: Python - 100天从新手到大师
- SecureFX for Mac(跨平台文件传输客户端)
- Fluent UDF【8】:编译型UDF
- tradingview中绘制市盈率曲选
- 连界创新获翊翎资本、连界资本8000万元融资,专注赋能产业升级...
- 网关和IP地址不在同一个网段下