2026山东大学软件学院创新实训——IntelliHealth(一)
文章目录
- 主要内容
- 一、核心思路
- 二、具体步骤
- 1.开发环境
- 2.权限申请与环境配置
- 3.动态申请权限
- 4.管理 CameraX 生命周期
- 5.CameraX 数据流:从像素到文件
- 总结
- 成果展示
主要内容
在开发健康监测应用时,我们经常需要程序在后台静默运行,并能够获取摄像头的图像进行分析。为了实现技术的可能性,我通过一个简单的 Demo 模拟实现Android端 “悬浮窗 + 后台服务 + CameraX 图像捕获” 的全过程。
一、核心思路
在 Android 系统中,为了隐私保护,应用通常不能在完全无界面的情况下访问摄像头。我们的解决方案是:
Foreground Service(前台服务):保证应用在后台不被系统轻易杀掉,并明确告知用户相机正在运行。
System Alert Window(悬浮窗权限):创建一个极其微小的(1x1 像素)悬浮窗来承载相机预览,绕过“必须有可见界面”的限制。
CameraX 框架:使用 Google 推荐的Jetpack组件,通过ImageAnalysis实时抓取帧数据。
二、具体步骤
1.开发环境
Android Studio
入门步骤参考
2.权限申请与环境配置
我们需要在 AndroidManifest.xml 中声明必要的权限:
<uses-permissionandroid:name="android.permission.CAMERA"/><uses-permissionandroid:name="android.permission.SYSTEM_ALERT_WINDOW"/><uses-permissionandroid:name="android.permission.FOREGROUND_SERVICE"/><uses-permissionandroid:name="android.permission.FOREGROUND_SERVICE_CAMERA"/>- android.permission.CAMERA : 允许应用访问设备的摄像头(相机权限) - android.permission.SYSTEM_ALERT_WINDOW : 允许应用在其他应用之上显示窗口。(悬浮窗权限) - android.permission.FOREGROUND_SERVICE : 前台服务权限 - android.permission.FOREGROUND_SERVICE_CAMERA : 前台相机服务权限;3.动态申请权限
在MainActivity文件中以请求流的形式动态申请相机和悬浮窗权限。
检查相机权限→ \rightarrow→检查悬浮窗权限(跳转系统设置页引导用户手动开启)→ \rightarrow→启动后台服务
// 1. 检查并申请相机权限privatefuncheckCameraPermission(){if(ContextCompat.checkSelfPermission(this,Manifest.permission.CAMERA)!=PackageManager.PERMISSION_GRANTED){// 申请相机权限ActivityCompat.requestPermissions(this,arrayOf(Manifest.permission.CAMERA),CAMERA_PERMISSION_REQ_CODE)}else{// 已有相机权限,接着检查悬浮窗权限checkOverlayPermission()}}// 2. 检查并申请悬浮窗权限privatefuncheckOverlayPermission(){if(!Settings.canDrawOverlays(this)){Toast.makeText(this,"请开启悬浮窗权限以运行监测服务",Toast.LENGTH_LONG).show()valintent=Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,Uri.parse("package:$packageName"))startActivityForResult(intent,OVERLAY_PERMISSION_REQ_CODE)}else{// 权限全齐,启动服务startCameraService()}}// 3. 启动后台服务privatefunstartCameraService(){valintent=Intent(this,FloatingCameraService::class.java)if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){startForegroundService(intent)}else{startService(intent)}Toast.makeText(this,"健康监测服务已启动",Toast.LENGTH_SHORT).show()}4.管理 CameraX 生命周期
在 FloatingCameraService 文件中采用 LifecycleService,使其能感知生命周期,自动开启和关闭相机。
① 使前台服务具有合规性
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.UPSIDE_DOWN_CAKE){startForeground(1,notification,ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA);}如果没有 FOREGROUND_SERVICE_TYPE_CAMERA 这个参数,系统会认为我们的app服务在对用户进行偷窥,并直接杀掉进程。② 悬浮窗的实现机制
- 窗口类型的声明与适配
Build.VERSION.SDK_INT>=Build.VERSION_CODES.O?WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY:WindowManager.LayoutParams.TYPE_PHONETYPE_APPLICATION_OVERLAY:此类窗口位于所有应用窗口之上,但受系统权限(SYSTEM_ALERT_WINDOW)的严格管控。该步骤的意义在于,由于 Android 隐私策略规定相机访问通常需要关联一个可见的 Surface,该悬浮窗在底层逻辑上满足了这一合规性校验。即:只有创建一个悬浮窗才能使得用户在退出app时也能使用系统相机权限。
- 交互行为控制
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,PixelFormat.TRANSLUCENTFLAG_NOT_FOCUSABLE:指定窗口不获取输入焦点。这确保了当悬浮窗存在时,用户仍能与下层界面进行交互,且软键盘等系统组件不会被该窗口拦截。 FLAG_NOT_TOUCHABLE:使窗口对触摸事件透明。所有的点击、滑动操作将直接穿透该层,传递至背景应用。该步骤的目的是使得该悬浮窗不会影响用户对其他app的使用。
- 视觉效果
// 定义窗口物理参数WindowManager.LayoutParamsparams=newWindowManager.LayoutParams(1,1,Build.VERSION.SDK_INT>=Build.VERSION_CODES.O?WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY:WindowManager.LayoutParams.TYPE_PHONE,WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,PixelFormat.TRANSLUCENT//支持 Alpha 通道实现透明效果);// 空间定位:锚定在左上角params.gravity=Gravity.TOP|Gravity.START;该步骤的目的是将悬浮窗大小设为1 × 1 1 \times 11×1像素并将其锚定在左上角。
5.CameraX 数据流:从像素到文件
- 第一步:绑定分析器
ImageAnalysisimageAnalysis=newImageAnalysis.Builder().setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST).setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888).build();显示设置相机输出格式为RGBA_8888,以便后续转 Bitmap。
- 第二步:异步保存与采样
imageAnalysis.setAnalyzer(cameraExecutor,image->{longcurrentTime=System.currentTimeMillis();if(currentTime-lastSaveTime>=2000){// 2秒采样一次lastSaveTime=currentTime;processAndSaveImage(image);}else{image.close();// 记得关门!}});这里使用了 cameraExecutor(单线程池),即便保存图片很慢,也不影响主线程。
- 第三步:图像旋转修正
Matrixmatrix=newMatrix();matrix.postRotate(90);// 修正前置摄像头的方向matrix.postScale(-1,1);// 镜像翻转但是在测试过程中发现由于设备的不同,可能需要修改的角度不同,需要调试得到准确参数。
总结
通过上述方式的组合,成功绕过了后台限制,获得了一个合法的、常驻的视觉处理方案。成功实现了在用户使用其他app的同时,仍能够以固定频率获取前置摄像头的内容,为项目后续开发提供了有力保障。
成果展示
- 下图左上角为app的悬浮窗(这里为了方便观察,设置大小为200*200)
- 退出app使其在后台运行时,我们打开系统盘可以看到已存储从前置摄像头获取的瞬时帧
