Android 辅助服务实战-游戏点击器
Android 辅助服务实战-游戏点击器
背景:
前几年我一直在玩一款氪金养成类手游<<末日危机>>,每天都有任务需要完成,那时游戏里面还没有一键收菜,我去年(接手了公会里面不少弃坑号,越玩越是耗时间)就想做了一些辅助软件减轻我的负担,先是在PC做了个自动点击完成关卡战斗,后面觉得天天开虚拟机太麻烦,不如直接在手机上完成。
思路:
Android系统有提供一个无障碍功能:AccessbilityService,在Android6.0之前能访问第三方应用的当前布局和控件结点,现在Android手机系统等级动辄10以上,已经不能用这种直接获取控件的方式,要使用截图再进行图片识别目标位置,再进行坐标位置上的点击。
首先手机上需要开启辅助服务,该服务在手机截图API实现要求手机版本至少要Android10。具体代码如下
private fun isAccessibilitySettingsOn(mContext: Context): Boolean {var accessibilityEnabled = 0// TestService为对应的服务val service = packageName + "/" + AccessbilityServiceImp::class.java.canonicalNameLog.i(TAG, "service:$service")try {accessibilityEnabled = Settings.Secure.getInt(mContext.applicationContext.contentResolver,Settings.Secure.ACCESSIBILITY_ENABLED)Log.v(TAG, "accessibilityEnabled = $accessibilityEnabled")} catch (e: SettingNotFoundException) {Log.e(TAG, "Error finding setting, default accessibility to not found: " + e.message)}val mStringColonSplitter = SimpleStringSplitter(':')if (accessibilityEnabled == 1) {Log.v(TAG, "***ACCESSIBILITY IS ENABLED*** -----------------")val settingValue = Settings.Secure.getString(mContext.applicationContext.contentResolver,Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES)if (settingValue != null) {mStringColonSplitter.setString(settingValue)while (mStringColonSplitter.hasNext()) {val accessibilityService = mStringColonSplitter.next()Log.v(TAG,"-------------- > accessibilityService :: $accessibilityService $service")if (accessibilityService.equals(service, ignoreCase = true)) {Log.v(TAG,"We've found the correct setting - accessibility is switched on!")return true}}}} else {Log.v(TAG, "***ACCESSIBILITY IS DISABLED***")}return false
}
具体实现:
开启了辅助服务后,我们需要一个媒介和该服务进行交互,并且不能影响后续的截图效果,一种能在屏幕上进行收缩的工具栏。
我采用系统消息通知栏+广播来解决这个问题,它会和辅助服务进行交互:
object GameNotification {private var manager: NotificationManager? = nullprivate var remoteViews: RemoteViews? = nullvar notify: Notification? = nullfun createNotificaton(context: Context) {manager = context.getSystemService(AccessibilityService.NOTIFICATION_SERVICE) as NotificationManager// 设置通知栏的图片文字remoteViews = RemoteViews(context.packageName,R.layout.custom_notice)remoteViews!!.setImageViewResource(R.id.widget_play, R.drawable.ic_baseline_play_arrow_24)remoteViews!!.setImageViewResource(R.id.widget_stop, R.drawable.ic_baseline_stop_24)val builder = NotificationCompat.Builder(context)val intent = Intent(context, MainActivity::class.java)// 点击跳转到主界面val intent_go = PendingIntent.getActivity(context, 0, intent,PendingIntent.FLAG_UPDATE_CURRENT)remoteViews!!.setOnClickPendingIntent(R.id.notice, intent_go)// 设置开始按键val start = Intent()start.action = Command.ACTION_STARTval intent_start = PendingIntent.getBroadcast(context, 0, start,PendingIntent.FLAG_UPDATE_CURRENT)remoteViews!!.setOnClickPendingIntent(R.id.widget_play, intent_start)// 设置停止按键val stop = Intent()stop.action = Command.ACTION_STOPval intent_stop = PendingIntent.getBroadcast(context, 1, stop,PendingIntent.FLAG_UPDATE_CURRENT)remoteViews!!.setOnClickPendingIntent(R.id.widget_stop, intent_stop)builder.setSmallIcon(R.drawable.ic_launcher_background) // 设置顶部图标if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {val mChannel = NotificationChannel("123", "147", NotificationManager.IMPORTANCE_LOW)manager!!.createNotificationChannel(mChannel)notify = Notification.Builder(context).setChannelId("123")// .setContentTitle("5 new messages")// .setContentText("hahaha").setContent(remoteViews).setSmallIcon(R.mipmap.ic_launcher).build()} else {notify = builder.build()notify!!.contentView = remoteViews // 设置下拉图标notify!!.bigContentView = remoteViews // 防止显示不完全,需要添加apisupportnotify!!.flags = Notification.FLAG_ONGOING_EVENT}manager!!.notify(100, notify)}}
通过广播进行消息传输,告诉辅助服务可以执行或者停止当前选择的任务。
识别图片:
屏幕截图获取代码片段:
执行截图:
@RequiresApi(Build.VERSION_CODES.R)
fun startSnapShoot() {accessbilityServiceImp!!.mHandler.postDelayed({accessbilityServiceImp!!.takeScreenshot(Display.DEFAULT_DISPLAY,accessbilityServiceImp!!.mainExecutor,mCallBack)},1000)
}
接收截图数据:
@RequiresApi(Build.VERSION_CODES.R)var mCallBack:AccessibilityService.TakeScreenshotCallback = object :AccessibilityService.TakeScreenshotCallback{override fun onSuccess(screenshotResult: AccessibilityService.ScreenshotResult) {val nodeInfo = accessbilityServiceImp?.rootInActiveWindowval result = nodeInfo?.let { accessbilityServiceImp?.findNodeInfosByText(it,"任务") }Log.e("AccessbilityServiceImp","onSuccess = "+screenshotResult+",result = "+result)var timespec = 500LaccessbilityServiceImp!!.timespec = 1000Lval hardwareBuffer = screenshotResult.hardwareBufferval colorSpace = screenshotResult.colorSpaceif (hardwareBuffer.width > 0 && hardwareBuffer.height > 0 && colorSpace != null) {val bitmap = Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace)...}}}...
把获取的数据转换成bitmap,后面使用opencv进行图片识别。
竞技场挑战任务:
竞技场页面获取当前在场队伍的战力值:
fun detectNumberRect(bitmap: Bitmap, list: List<Mat>): List<ComplyPlayInfo> {val bitmapNew: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)var resultPlays :List<ComplyPlayInfo>val dectectedPlays = ArrayList<ComplyPlayInfo>()var myPlayInfo = ComplyPlayInfo(0,0)//opencv初始化if (!OpenCVLoader.initDebug()) {Log.d("--------opencv--------", "OpenCVLoader error")}//Mat変換val src = Mat(bitmapNew.height, bitmapNew.width, CvType.CV_8UC4)Utils.bitmapToMat(bitmapNew, src)//获取战力值所在的坐标位置集合val rects = detectPowerNumberRect(bitmap)rects.mapIndexed {index,it->val mRgb = Mat(src, it)val b = Bitmap.createBitmap(mRgb!!.cols(), mRgb.rows(),Bitmap.Config.ARGB_8888)Utils.matToBitmap(mRgb, b)val dst = Mat.zeros(Size(src.width().toDouble(), src.height().toDouble()), CvType.CV_8UC3)//绘制轮廓 (Yellow)var color = Scalar(255.0, 255.0, 0.0)//灰色化Imgproc.cvtColor(mRgb, mRgb, Imgproc.COLOR_RGB2GRAY)bitwise_not(mRgb, mRgb);//二値化Imgproc.threshold(mRgb, mRgb, 0.0, 250.0,Imgproc.THRESH_BINARY or Imgproc.THRESH_OTSU)Utils.matToBitmap(mRgb, b)//比特反转val hierarchy = Mat.zeros(Size(0.0, 0.0), CvType.CV_8UC1)// 輪郭抽出val contours: List<MatOfPoint> = ArrayList()Imgproc.findContours(mRgb,contours,hierarchy,Imgproc.RETR_TREE,Imgproc.CHAIN_APPROX_SIMPLE)Imgproc.drawContours(dst, contours, -1, color, 1)var box: Rect?var boxs = ArrayList<Rect>()for (i in contours.indices) {val ptmat: MatOfPoint = contours[i]// 轮廓的重心的绘制 (Red)color = Scalar(255.0, 0.0, 0.0)val ptmat2 = MatOfPoint2f(*ptmat.toArray())val bbox = Imgproc.minAreaRect(ptmat2)box = bbox.boundingRect()if (box.width < box.height && box.height < 50 && box.height > 25&& box.y < 10) {Imgproc.circle(dst, bbox.center, 5, color, -1)// 周围轮廓四角形绘制 (Green)color = Scalar(0.0, 255.0, 0.0)Imgproc.rectangle(dst, box.tl(), box.br(), color, 2)boxs.add(box)}}boxs.sortBy {it.x}val result = ArrayList<NumberInfo>()var num = 0Lboxs.mapIndexed { index, it ->if (it.x+it.width>mRgb.width()){it.width = mRgb.width()-it.x}val tmp = Mat(mRgb, it)val tmpBitmap = Bitmap.createBitmap(tmp!!.cols(), tmp.rows(),Bitmap.Config.ARGB_8888)Utils.matToBitmap(tmp, tmpBitmap)list.mapIndexed { index, it ->val result_cols: Int = abs(tmp.cols() - it.cols()) + 1val result_rows: Int = abs(tmp.rows() - it.rows()) + 1val res = Mat(result_rows, result_cols, CvType.CV_32FC1)//归一化var item = itval bitmap2 = Bitmap.createBitmap(it!!.cols(), it!!.rows(),Bitmap.Config.ARGB_8888)Utils.matToBitmap(it, bitmap2)Imgproc.resize(it, it, Size(tmp.width().toDouble(), tmp.height().toDouble()))Imgproc.matchTemplate(tmp, item, res, Imgproc.TM_SQDIFF)val bitmap = Bitmap.createBitmap(tmp!!.cols(), tmp!!.rows(),Bitmap.Config.ARGB_8888)Utils.matToBitmap(tmp, bitmap)val bitmap1 = Bitmap.createBitmap(item!!.cols(), item!!.rows(),Bitmap.Config.ARGB_8888)Utils.matToBitmap(item, bitmap1)//获得最可能点,MinMaxLocResult是其数据格式,包括了最大、最小点的位置x、yval mlr = Core.minMaxLoc(res)result.add(NumberInfo(mlr.minVal, index))Log.e("AccessbilityServiceImp", "mlr result minVal="+ mlr.minVal + ",maxVal=" + mlr.maxVal + ",minLoc=" + mlr.minLoc + ",maxLoc=" + mlr.maxLoc)}result.sortBy {it.minValue}num += result[0].index * 10.0.pow(boxs.size.toDouble() - 1 - index).toLong()result.clear()}if (index == 0){myPlayInfo = ComplyPlayInfo(num,index)}else{dectectedPlays.add(ComplyPlayInfo(num,index))}Log.e("AccessbilityServiceImp", "num result =" + num)}resultPlays = dectectedPlays.filter { myPlayInfo.strength > it.strength }Log.e("AccessbilityServiceImp", "resultPlays =" + resultPlays)return resultPlays}
调用 Imgproc.matchTemplate(),使用数字模板对比,相似度高的就认为是该阿拉伯数字,逐个数字叠加后获得战力值,返回战力值比自己队伍低的集合,上层获取到该值后挑选还能挑战的队伍进行战斗,(相同的队伍当天只能挑战它8次)。
任务栏刷任务:
在任务栏进行刷任务操作需要进行的步骤:识别当前视图所有的任务的星数(1到7星)->挑选符合条件的任务刷新->向右滑动任务->重新识别还没有操作过的任务->刷新完后点击一键领取已经完成的任务。
识别当前任务的星数代码片段:
private fun cutStarsPictrue(bitmap: Bitmap, list: ArrayList<Rect>): List<TaskInfo> {val bitmapNew: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)//opencv初始化if (!OpenCVLoader.initDebug()) {Log.d("--------opencv--------", "OpenCVLoader error")}//Mat变换val mRgb = Mat(bitmapNew.height, bitmapNew.width, CvType.CV_8UC4)Utils.bitmapToMat(bitmapNew, mRgb)var starsNum = ArrayList<TaskInfo>()list.map {val rect = Rect(it.x + 15, it.y + 8, it.width - 35, it.height - 18)val src = Mat(mRgb, rect)val b = Bitmap.createBitmap(src!!.cols(), src.rows(),Bitmap.Config.ARGB_8888)Utils.matToBitmap(src, b)val num = returnStarsNum(b)starsNum.add(TaskInfo(rect,num))Log.e("AccessbilityServiceImp", " num =" + num)}return starsNum}
识别当前任务的内容代码片段:(主要是识别当前任务是否符合条件,三星任务只需要英雄碎片,4星任务抛弃竞技场挑战券,4星以上任务全部完成)
fun cutTaskContent(bitmap: Bitmap, list: List<TaskInfo>): List<TaskInfo> {val bitmapNew: Bitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true)//opencv初始化if (!OpenCVLoader.initDebug()) {Log.d("--------opencv--------", "OpenCVLoader error")}//Mat変換val mRgb = Mat(bitmapNew.height, bitmapNew.width, CvType.CV_8UC4)Utils.bitmapToMat(bitmapNew, mRgb)var starsNum = ArrayList<TaskInfo>()var rect:Rectlist.map {//如果是3星任务截图区域if (it.stars < 4){rect = Rect(it.rect.x + bitmapNew.width/23, it.rect.y - 3 + bitmapNew.height/32*9, it.rect.width/3 , it.rect.height/2 )}else {//如果是4-5星任务截图区域rect = Rect(it.rect.x + bitmapNew.width / 60,it.rect.y + bitmapNew.height / 33 * 6,it.rect.width / 5,it.rect.width / 3)}val src = Mat(mRgb, rect)val b = Bitmap.createBitmap(src!!.cols(), src.rows(),Bitmap.Config.ARGB_8888)Utils.matToBitmap(src, b)val num = returnTaskStarsNum(b)/*如果是3星任务返回5是英雄碎片如果是4,5星任务返回5是竞技场挑战券*/if (it.stars < 4) {if (num >= 4) {starsNum.add(it)}}else{if (num in 5..9) {starsNum.add(it)}}Log.e("AccessbilityServiceImp", " starsNum =" + starsNum)}return starsNum
}
指令执行:
复杂的点击和滑动指令操作需要一个执行者来完成:Handler
val mHandler:TaskHandler = TaskHandler()class TaskHandler:Handler(Looper.getMainLooper()){override fun handleMessage(msg: Message) {val callback = msg.obj as ()->Unitcallback.invoke()}}
用消息把不同的指令传给Handler去处理,可以延时执行,也可以随时取消没有完成的指令任务。
//滑动fun slidingScreen(scrollData: ScrollData, delayTime: Long){val callback = {if (height != 0f) {if (width != 0f) {ActionDeal(width/2,height/2,scrollData,62)}}}timespec = delayTimemHandler.postDelayed(callback,-1,delayTime)}//点击fun clickScreen(x:Float,y: Float){val callback = {ActionDeal(x,y,null,62)}mHandler.postDelayed(callback,-1,timespec)timespec+=1000Log.e("AccessbilityServiceImp","timespec = "+timespec)}//点击(带时间差)fun clickScreen(x:Float,y: Float,delayTime: Long){val callback = {if (height != 0f) {if (width != 0f) {ActionDeal(x,y,null,62)}}}mHandler.postDelayed(callback,62,delayTime)}
不用指令之间的等待时间各不相同,需要给根据实际的时间差来完成一连串指令。
创建任务实体类:
data class TaskInfo(val callback: () -> Unit, val timespec: Long, val id:Int)
创建任务集合:
var taskList = ArrayList<TaskInfo>()
Handler执行所有的指令任务:
taskList.add(TaskInfo({localBroadcastReceiver.startSnapShoot()},timespec,48))taskList.map {Log.e("AccessbilityServiceImp","timespec = "+it.timespec)mHandler.postDelayed(it.callback,it.timespec)
}
传送门:项目源码地址
创造不易,欢迎点赞starred me
Android 辅助服务实战-游戏点击器相关推荐
- android动态评论功能,Android辅助权限实战之微信自动评论与点赞
Android辅助权限实战之微信自动评论与点赞 当我们把辅助权限玩的比较熟悉 的时候,就可以释放我们的双手做一些有趣的事情了,例如之前网上流传的微信自动抢红包插件,就是使用的这个服务,不过我们今儿讲的 ...
- android 辅助服务 简书,Android AccessibilityService使用
测试demo主要使用了Android 的无障碍辅助服务(AccessibilityService),主要注意是 1. 开启服务,绑定目标app的监听(demo中根据包命绑定了体积计算的app),需要在 ...
- 注意android辅助服务事件不能用于保存
本来希望把来自辅助服务的事件,像epoll那样暂存在队列进行调度,或者做成事件堆栈,从而将辅助服务事件加入到容器.但是一直不能达到预期的后果.最后才发现一个坑人的事实,辅助服务事件被释放(或者说重置) ...
- 使用android studio时酷狗音乐,17 Android Studio开发实战:音乐播放器——浪花音乐...
手机上的多媒体内容讲究声情并茂.悦目且悦耳,这样才能让用户的感官得到最大享受.影视播放器由于存在视频自身的画面,反而限制了开发者的施展空间:而音乐播放器允许定制播放画面,开发者有足够空间施展拳脚.本节 ...
- Android辅助权限实战之微信自动评论与点赞
当我们把辅助权限玩的比较熟悉 的时候,就可以释放我们的双手做一些有趣的事情了,例如之前网上流传的微信自动抢红包插件,就是使用的这个服务,不过我们今儿讲的是微信自动评论与点赞功能(抢红包的文章网上已经有 ...
- Android辅助工具助手-keep 自动点赞器
0.运行界面 源码下载 https://github.com/sufadi/AccessibilityServiceMonitor 1.需求点赞界面,进行自动点击 注意:要clickable事件为tu ...
- android 辅助服务自动右滑,我的手机启用辅助功能后怎么滑动屏幕,是什么盲人的什么功能,怎么取消?...
满意答案 xt0e 2017.01.11 采纳率:50% 等级:12 已帮助:8005人 看这个,一招学会使用VoiceOver功能. 输入密码:先选择你要输入的数字,然后双击这个字母就可以了. ...
- Android辅助服务监听dialog,Android开发中对话框辅助类——DialogHelper
写在前面: 对话框在平时的开发工作中使用率很频繁,但是很多开发者每次使用都去写一堆代码,如此,不单单效率不高,而且代码也不优...为此,写了简单的封装. 效果如: 关键类DialogHelper.ja ...
- Android 中 利用 AccessibilityService 辅助服务 模拟点击事件
在 Android 中想要执行一些模拟点击操作,在无法修改页面源码的情况下,通常只能使用 adb 和借助辅助功能两种方式. Adb 方式 借助 adb shell 的命令,我们可以使用下面的方式模拟一 ...
最新文章
- bzoj 1189 紧急疏散 网络流
- canvas动画3:交互
- popwindow弹窗
- Windows恶搞脚本,太实用了医院WiFi很快
- [JNI] 开发基础(6)字符串相关操作
- 求素数平均值c语言,C 输入10个正整数到a数组中,求a数组中素数的平均值.
- MVP2006成都聚会图片
- 人工智能的马克思主义审视
- 武汉凭什么被列为国家超大城市?
- 显卡+cuda+cudnn+tensorflow安装教程
- cad图纸怎么看懂_如何看懂CAD图?
- 美国探亲签证面签时一定要用英语吗?
- Keil MDK使用第7篇---Go To Definition 和 Go To referebce的区别
- C# 20行代码解析KRC歌词内容
- 改善技术简历的47条原则
- Android学习——5个UI界面设计
- 【开源】司马编译器 Smart Compiler 符号表
- 特斯拉的市场策略在中国面临“失效”
- 【vn.py学习笔记(二)】vn.py底层接口 学习笔记
- Garch模型Stata实例