拓冰建站拓冰建站
首页 / 资讯中心 / 正文

安卓定位开发实战:LocationManager后台服务与无障碍监听

简介这是一份面向安卓开发初学者与逆向/自动化测试爱好者的实战学习项目基于已停运手游《一起来捉妖》开发的辅助定位与自动捉妖工具聚焦Android平台虚拟定位、屏幕图像识别与游戏协议交互等进阶技能。资源包共226个文件含163张PNG/19张JPEG界面截图用于模型训练与UI识别16个XML布局与配置文件体现典型Android工程结构5个SO库支持底层操作4个Java核心逻辑类及Gradle构建脚本完整呈现从SDK集成腾讯定位SDK、Airtest、WebSocket妖灵位置订阅gwgo接口到模拟点击/路径移动的全链路实现。压缩包大小13.43MB结构清晰含README说明、LICENSE授权声明及实操MP4演示。目前已有77人学习下载适合希望深入理解安卓自动化原理、游戏辅助技术边界与合规开发实践的学习者参考源码、复现实验并拓展模块功能。1. 这不是外挂而是一次完整的安卓定位服务开发实践从“捉妖雷达”需求出发理解位置感知、后台任务与 UI 自动化边界“一起来捉妖”作为一款基于真实地理围栏Geo-fencing与 AR 呈现的 LBS 游戏其核心交互依赖设备持续获取高精度位置、识别周边 POI 并触发 UI 反馈。标题中“捉妖雷达.zip”并非指代非法辅助工具而是典型的学习型项目压缩包——它封装了一个以android.location体系为基础、结合ForegroundService管理生命周期、用AccessibilityService实现界面状态监听非点击/模拟操作的完整安卓定位感知模块。这类项目常出现在高校移动开发课程大作业、安卓进阶训练营或个人技术栈补全场景中目标不是绕过游戏反作弊机制而是掌握「如何在合规前提下构建一个可长期运行的位置响应系统」。它直面安卓 10 的后台定位限制、Android 12 对 AccessibilityService 的权限收紧、以及build.gradle中 targetSdkVersion 升级引发的ACCESS_BACKGROUND_LOCATION动态申请逻辑变更。对初学者这是理解LocationManager与FusedLocationProviderClient差异的第一课对有经验者这是检验是否真正吃透WorkManager替代方案、PendingIntent与BroadcastReceiver协同机制的试金石。2. 定位能力选型与 AndroidManifest 配置为什么不用 FusedLocationProviderClient 而坚持 LocationManager2.1 两种定位 API 的适用边界与本项目选择依据安卓定位能力演进中FusedLocationProviderClientFLP因融合 GPS/WiFi/基站数据、功耗低、API 简洁成为官方推荐首选。但“捉妖雷达”类学习项目需明确暴露底层行为细节比如LocationManager可直接注册GpsStatusListener监听卫星状态通过getGpsStatus()获取当前可见卫星数、信噪比SNR这对理解“为何在室内雷达无响应”至关重要而 FLP 封装过深无法获取此类诊断信息。此外LocationManager的requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener)方法参数含义直白——minTime5000表示至少间隔 5 秒触发一次回调minDistance10表示位移超 10 米才更新这种显式控制是教学场景的核心价值。FLP 的setInterval()和setFastestInterval()则需配合LocationRequest的setPriority()才生效抽象层级过高不利于建立基础认知。2.2 AndroidManifest.xml 中必须声明的权限与服务组件定位功能需在AndroidManifest.xml中声明以下权限与组件缺一不可!-- 必须声明的基础定位权限 -- uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION / !-- 安卓 10 后台定位必需 -- uses-permission android:nameandroid.permission.ACCESS_BACKGROUND_LOCATION / !-- 前台服务通知渠道安卓 8.0 强制要求 -- uses-permission android:nameandroid.permission.FOREGROUND_SERVICE / !-- 声明前台服务组件 -- service android:name.service.RadarLocationService android:enabledtrue android:exportedfalse android:foregroundServiceTypelocation / !-- 声明无障碍服务仅用于监听界面状态非模拟操作 -- service android:name.accessibility.RadarAccessibilityService android:permissionandroid.permission.BIND_ACCESSIBILITY_SERVICE android:exportedtrue intent-filter action android:nameandroid.accessibilityservice.AccessibilityService / /intent-filter meta-data android:nameandroid.accessibilityservice android:resourcexml/accessibility_service_config / /service注意android:foregroundServiceTypelocation是安卓 10API 29引入的强制属性若遗漏会导致startForegroundService()调用失败并抛出IllegalStateException。BIND_ACCESSIBILITY_SERVICE权限声明不可省略否则无障碍服务无法绑定。2.3 settings.gradle 与 build.gradle 的协同配置要点项目根目录settings.gradle需正确包含模块常见错误是遗漏:app或拼写错误// settings.gradle include :app rootProject.name ZhuoYaoRadarapp/build.gradle中的关键配置需匹配定位需求android { compileSdk 34 // 推荐使用最新稳定版 defaultConfig { applicationId com.example.zhuoyaoradar minSdk 21 // 定位服务最低支持 API 21 targetSdk 34 // 必须 ≥30 才能申请后台定位权限 versionCode 1 versionName 1.0 } // 定位相关依赖LocationManager 不需额外依赖但需兼容库 dependencies { implementation androidx.core:core:1.12.0 // 提供 ActivityCompat 等兼容工具 implementation androidx.work:work-runtime-ktx:2.9.0 // 若后续改用 WorkManager 替代前台服务 } }targetSdk 34意味着必须处理安卓 12 的ACCESS_BACKGROUND_LOCATION动态申请流程且LocationManager的getLastKnownLocation()在后台可能返回 null——这正是教学重点引导开发者理解“最后已知位置”不可靠必须主动请求更新。3. 核心服务实现RadarLocationService 如何在前台持续获取位置并规避系统休眠3.1 前台服务启动与 NotificationChannel 创建安卓 8.0 要求所有前台服务必须关联 NotificationChannel。RadarLocationService的onCreate()中需初始化通道// RadarLocationService.java Override public void onCreate() { super.onCreate(); if (Build.VERSION.SDK_INT Build.VERSION_CODES.O) { String channelId radar_location_channel; String channelName 捉妖雷达位置服务; NotificationChannel channel new NotificationChannel( channelId, channelName, NotificationManager.IMPORTANCE_LOW); channel.setDescription(持续获取位置以显示附近妖怪); NotificationManager manager getSystemService(NotificationManager.class); manager.createNotificationChannel(channel); // 构建前台通知 Intent notificationIntent new Intent(this, MainActivity.class); PendingIntent pendingIntent PendingIntent.getActivity( this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE); Notification notification new NotificationCompat.Builder(this, channelId) .setContentTitle(捉妖雷达运行中) .setContentText(正在后台获取位置...) .setSmallIcon(R.drawable.ic_radar) .setContentIntent(pendingIntent) .build(); startForeground(1, notification); // ID1必须与 stopForeground() 一致 } }提示PendingIntent.FLAG_IMMUTABLE是安卓 12 强制要求若使用FLAG_MUTABLE会触发SecurityException。图标R.drawable.ic_radar需提前放入res/drawable目录。3.2 LocationManager 初始化与位置更新注册服务onStartCommand()中完成定位器初始化与监听注册// RadarLocationService.java private LocationManager locationManager; private final LocationListener locationListener new LocationListener() { Override public void onLocationChanged(NonNull Location location) { // 位置更新回调此处可广播给 UI 层或存入本地数据库 Log.d(Radar, Lat: location.getLatitude() , Lng: location.getLongitude()); // 发送广播通知 MainActivity 更新雷达 UI Intent intent new Intent(ACTION_RADAR_UPDATE); intent.putExtra(latitude, location.getLatitude()); intent.putExtra(longitude, location.getLongitude()); sendBroadcast(intent); } Override public void onStatusChanged(String provider, int status, Bundle extras) { // 处理 GPS 状态变化如 STATUS_TEMPORARILY_UNAVAILABLE Log.d(Radar, Provider provider status: status); } }; Override public int onStartCommand(Intent intent, int flags, int startId) { locationManager (LocationManager) getSystemService(Context.LOCATION_SERVICE); // 检查权限安卓 6.0 动态权限 if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) PackageManager.PERMISSION_GRANTED) { // 优先使用 GPS 提供者高精度适合户外捉妖 if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { try { locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER, 5000, // minTime: 5秒 10, // minDistance: 10米 locationListener); } catch (SecurityException e) { Log.e(Radar, GPS provider access denied, e); } } else { // GPS 不可用时降级到网络定位 if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { locationManager.requestLocationUpdates( LocationManager.NETWORK_PROVIDER, 10000, // 网络定位更新间隔放宽至10秒 50, // 位移阈值放宽至50米 locationListener); } } } return START_STICKY; // 系统内存不足时重启服务 }3.3 位置更新的可靠性保障与降级策略START_STICKY仅保证服务被系统杀死后尝试重启但不保证requestLocationUpdates()持续有效。实际开发中需加入心跳检测// 在 onStartCommand() 后添加心跳检查 private Handler heartbeatHandler; private Runnable heartbeatRunnable new Runnable() { Override public void run() { if (locationManager ! null !locationManager.getAllProviders().isEmpty()) { // 检查是否已注册监听器 ListString providers locationManager.getAllProviders(); for (String provider : providers) { if (locationManager.isProviderEnabled(provider)) { try { Location lastLoc locationManager.getLastKnownLocation(provider); if (lastLoc ! null System.currentTimeMillis() - lastLoc.getTime() 60000) { // 1分钟内有有效位置视为健康 Log.d(Radar, Heartbeat OK: provider); break; } } catch (SecurityException ignored) {} } } } heartbeatHandler.postDelayed(this, 30000); // 30秒心跳 } }; // onStartCommand() 末尾启动心跳 heartbeatHandler new Handler(Looper.getMainLooper()); heartbeatHandler.post(heartbeatRunnable);此机制确保即使 GPS 信号短暂丢失服务仍能通过网络定位维持基本能力并在日志中留下可追溯的健康状态线索。4. AccessibilityService 辅助状态监听如何安全地读取游戏界面信息而不触发反作弊4.1 AccessibilityService 的设计定位与合规边界标题中“自动捉妖”易被误解为自动化点击但本项目中的RadarAccessibilityService严格限定于只读监听它通过AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED事件捕获游戏主 Activity 的窗口焦点变化通过getRootInActiveWindow()获取当前界面节点树进而解析TextView的文本内容如“附近有1只小灵妖”。它不调用performAction(AccessibilityNodeInfo.ACTION_CLICK)不模拟触摸因此不违反 Google Play 政策及游戏用户协议。其存在意义是解决“位置服务在后台运行但用户切出游戏时需暂停雷达扫描”的场景——这是Activity生命周期无法覆盖的盲区。4.2 accessibility_service_config.xml 配置详解res/xml/accessibility_service_config.xml决定服务能力范围?xml version1.0 encodingutf-8? accessibility-service xmlns:androidhttp://schemas.android.com/apk/res/android android:descriptionstring/accessibility_service_desc android:packageNamescom.netease.hyxd !-- “一起来捉妖”包名需确认实际值 -- android:accessibilityEventTypestypeWindowStateChanged|typeViewTextChanged android:accessibilityFlagsflagDefault|flagIncludeNotImportantViews android:canRetrieveWindowContenttrue !-- 关键允许读取窗口内容 -- android:notificationTimeout100 /android:packageNames必须精确填写目标游戏包名可通过adb shell pm list packages | grep yao获取填错则无法监听。android:canRetrieveWindowContenttrue是读取TextView文本的前提但会触发系统权限警告需在应用设置中手动开启。typeViewTextChanged事件用于捕获妖怪列表动态刷新避免轮询。4.3 节点遍历与文本提取的健壮性写法onAccessibilityEvent()中的节点处理需防空指针与结构变更// RadarAccessibilityService.java Override public void onAccessibilityEvent(AccessibilityEvent event) { if (event.getEventType() AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { AccessibilityNodeInfo rootNode getRootInActiveWindow(); if (rootNode null) return; // 查找包含“妖怪”关键词的 TextView ListAccessibilityNodeInfo nodes rootNode.findAccessibilityNodeInfosByText(妖怪); if (!nodes.isEmpty()) { for (AccessibilityNodeInfo node : nodes) { CharSequence text node.getText(); if (text ! null text.toString().contains(附近有)) { // 提取数字附近有[1]只小灵妖 Pattern pattern Pattern.compile(附近有(\\d)只); Matcher matcher pattern.matcher(text.toString()); if (matcher.find()) { int nearbyCount Integer.parseInt(matcher.group(1)); Log.d(Radar, Detected nearbyCount nearby yaos); // 通过 LocalBroadcastManager 通知雷达服务调整扫描强度 Intent intent new Intent(ACTION_NEARBY_YAO_COUNT); intent.putExtra(count, nearbyCount); LocalBroadcastManager.getInstance(this).sendBroadcast(intent); } } node.recycle(); // 必须回收否则内存泄漏 } } rootNode.recycle(); // 根节点也必须回收 } }提示node.recycle()和rootNode.recycle()是强制要求未调用将导致OutOfMemoryError。LocalBroadcastManager用于进程内通信比全局sendBroadcast()更安全高效。5. gradlew 构建与调试技巧如何快速验证定位服务在真机上的行为5.1 使用 gradlew 执行 clean-build-install 的最小命令链脱离 Android Studio纯命令行验证是工程化能力的体现。进入项目根目录后执行# 清理旧构建避免缓存干扰 ./gradlew clean # 构建 debug APK生成路径app/build/outputs/apk/debug/app-debug.apk ./gradlew assembleDebug # 安装到已连接真机需开启 USB 调试 adb install -r app/build/outputs/apk/debug/app-debug.apk # 启动主 Activity 并授予必要权限 adb shell am start -n com.example.zhuoyaoradar/.MainActivity adb shell pm grant com.example.zhuoyaoradar android.permission.ACCESS_FINE_LOCATION adb shell pm grant com.example.zhuoyaoradar android.permission.ACCESS_BACKGROUND_LOCATION-r参数表示覆盖安装pm grant是安卓 6.0 动态权限的命令行授予方式替代手动点击授权弹窗。5.2 实时日志过滤与关键字段监控定位服务调试高度依赖logcat需精准过滤# 监控所有 Radar 相关日志含服务、Accessibility、位置回调 adb logcat | grep -E (Radar|LocationManager|Accessibility) # 仅显示 ERROR 级别及以上快速定位崩溃 adb logcat *:S Radar:D LocationManager:D Accessibility:D # 捕获位置更新频率每5秒应有一条 adb logcat | grep Lat:当发现onLocationChanged回调停止时立即检查adb shell dumpsys location查看当前 provider 状态adb shell dumpsys activity services RadarLocationService确认服务是否存活adb shell dumpsys battery排查是否因省电模式限制后台活动。5.3 模拟位置测试的三种可靠方法真机测试需可控位置源方法命令/操作适用场景注意事项ADB 模拟adb shell settings put secure mock_location 1adb shell am broadcast -a com.google.android.apps.location.nearby.NEARBY_MOCK_LOCATION --es latitude 31.2304 --es longitude 121.4737快速验证基础流程需先在开发者选项中启用“模拟位置信息”第三方 Mock App安装Fake GPS Location需授予android.permission.ACCESS_MOCK_LOCATION长时间轨迹模拟部分国产 ROM 需在“应用管理→权限→位置→模拟位置”单独开启硬件串口注入使用 UBLOX GPS 模块通过 USB-Serial 向/dev/ttyUSB0发送 NMEA 语句高保真环境仿真需 root 权限及自定义 HAL 层支持学习成本高对初学者ADB 模拟法最直接它绕过 UI直接向LocationManager注入坐标能第一时间验证onLocationChanged是否被触发是排除“代码逻辑正确但权限/服务未启动”类问题的黄金步骤。6. 安卓 12 后台定位适配与 AccessibilityService 权限收紧应对策略6.1ACCESS_BACKGROUND_LOCATION的三阶段申请流程安卓 12API 31起后台定位权限必须分两步申请先获前台定位权限再单独申请后台权限。MainActivity中需实现// 请求前台定位第一步 private void requestForegroundLocation() { String[] permissions {Manifest.permission.ACCESS_FINE_LOCATION}; ActivityCompat.requestPermissions(this, permissions, REQUEST_CODE_FOREGROUND); } // onRequestPermissionsResult 中处理 Override public void onRequestPermissionsResult(int requestCode, NonNull String[] permissions, NonNull int[] grantResults) { if (requestCode REQUEST_CODE_FOREGROUND) { if (grantResults.length 0 grantResults[0] PackageManager.PERMISSION_GRANTED) { // 前台权限已获下一步申请后台权限 requestBackgroundLocation(); } } else if (requestCode REQUEST_CODE_BACKGROUND) { // 后台权限结果处理 if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) PackageManager.PERMISSION_GRANTED) { startService(new Intent(this, RadarLocationService.class)); } } } // 申请后台定位第二步仅安卓 10 private void requestBackgroundLocation() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.Q) { String[] bgPermissions {Manifest.permission.ACCESS_BACKGROUND_LOCATION}; ActivityCompat.requestPermissions(this, bgPermissions, REQUEST_CODE_BACKGROUND); } }此流程确保用户明确知晓“后台持续定位”的意图避免静默获取位置引发隐私投诉。6.2 AccessibilityService 的安卓 12 兼容性开关安卓 12 引入android:canPerformGestures属性若设为true则需额外声明BIND_ACCESSIBILITY_SERVICE权限并处理手势事件。本项目为只读监听应显式设为false并在accessibility_service_config.xml中添加accessibility-service ... android:canPerformGesturesfalse /同时在AndroidManifest.xml的 service 声明中补充service android:name.accessibility.RadarAccessibilityService android:permissionandroid.permission.BIND_ACCESSIBILITY_SERVICE android:exportedtrue android:enabledtrue !-- intent-filter 与 meta-data 不变 -- /serviceandroid:enabledtrue确保服务默认激活避免因系统优化被禁用。6.3build.gradle中 targetSdkVersion 升级的连锁反应检查表将targetSdkVersion从 29 升至 34 时必须同步检查以下项检查项正确做法错误示例验证方式后台定位权限ACCESS_BACKGROUND_LOCATION动态申请仅申请ACCESS_FINE_LOCATION安装后进入设置→应用→权限→位置确认“后台”开关可手动开启前台服务类型android:foregroundServiceTypelocation缺失该属性或值为other启动服务时报IllegalStateException无障碍服务导出android:exportedtrue安卓 12 强制exportedfalse或缺失adb shell dumpsys accessibility查看服务是否在列表中PendingIntent 标志FLAG_IMMUTABLE安卓 12FLAG_MUTABLE且未处理PendingIntent重用点击通知无响应logcat 报SecurityException执行./gradlew app:dependencies可检查是否有过时依赖如support-v4应替换为androidx.core:core避免因依赖冲突导致AccessibilityService绑定失败。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门