虚拟点击
自己练手顺便写了一个简单的炉石传说脚本:一键投降+下一局
代码核心主要是两个Service,一个是悬浮窗,一个是虚拟点击。目前悬浮窗只适配了Android 8.0及之后的版本。
由于炉石传说游戏不是用java写的,监控不到其界面变化,就拿不到id,所以这里通过模拟坐标点击来实现的,不同手机分辨率不同,坐标会有差异。
代码放在github上了:传送门
效果图
AccessibilityService
AccessibilityService-无障碍服务,设计初衷在于帮助残障用户使用android设备和应用,在后台运行,可以监听用户界面的一些状态转换,例如页面切换、焦点改变、通知等,并在触发AccessibilityEvents时由系统接收回调。后来被开发者另辟蹊径,用于一些插件开发,比如微信红包助手,还有一些需要监听第三方应用的插件。
class MyAccessibilityService : AccessibilityService() {
override fun onServiceConnected() {
super.onServiceConnected()
serviceInfo = AccessibilityServiceInfo()
}
override fun onInterrupt() { }
override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
}
AccessibilityEvent
//接收到系统发送AccessibilityEvent时的回调
override fun onAccessibilityEvent(event: AccessibilityEvent) {
// "onAccessibilityEvent:event:$event".d(TAG)
analyzeEvent(event, rootInActiveWindow)
event.recycle()
}
AccessibilityNodeInfo
private fun analyzeNode(nodeInfo: AccessibilityNodeInfo){
val viewId = nodeInfo.viewIdResourceName
val nodeInfoListById = nodeInfo.findAccessibilityNodeInfosByViewId("")
val nodeInfoListByText = nodeInfo.findAccessibilityNodeInfosByText("")
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
使用
创建服务类
class MyAccessibilityService : AccessibilityService() {
override fun onServiceConnected() {
super.onServiceConnected()
serviceInfo = AccessibilityServiceInfo()
}
override fun onInterrupt() { }
override fun onAccessibilityEvent(event: AccessibilityEvent?) {}
}
声明服务
<service
android:name=".service.MyAccessibilityService"
android:label="虚拟点击测试"
android:exported="true"
android:enabled="true"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
申请权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
if(isAccessibilitySettingsOn(this@MainActivity, MyAccessibilityService::class.java)){
"已开启无障碍权限!".showToast(this@MainActivity)
}else{
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
}
/**
* 判断有没有无障碍权限
* */
private fun isAccessibilitySettingsOn(mContext: Context, clazz: Class<out AccessibilityService?>): Boolean {
var accessibilityEnabled = 0
val service = mContext.packageName + "/" + clazz.canonicalName
try {
accessibilityEnabled = Settings.Secure.getInt(
mContext.applicationContext.contentResolver,
Settings.Secure.ACCESSIBILITY_ENABLED
)
} catch (e: SettingNotFoundException) {
e.printStackTrace()
}
val mStringColonSplitter = SimpleStringSplitter(':')
if (accessibilityEnabled == 1) {
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()
if (accessibilityService.equals(service, ignoreCase = true)) {
return true
}
}
}
}
return false
}
服务参数配置
accessibility_service_config.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault|flagReportViewIds|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews|flagRequestFilterKeyEvents"
android:canRetrieveWindowContent="true"
android:canRequestFilterKeyEvents="true"
android:canPerformGestures="true"
android:canRequestTouchExplorationMode="true"
android:canRequestEnhancedWebAccessibility="true"
android:description="@string/accessibility_service_description"
android:notificationTimeout="100"/>
<!--accessibilityEventTypes:表示该服务对界面中的哪些变化感兴趣,即哪些事件通知,比如窗口打开,滑动,
焦点变化,长按等.具体的值可以在AccessibilityEvent类中查到,如typeAllMask表示接受所有的事件通知.-->
<!--accessibilityFeedbackType:表示反馈方式,比如是语音播放,还是震动-->
<!--canRetrieveWindowContent:表示该服务能否访问活动窗口中的内容.也就是如果你希望在服务中获取窗体内容的化,则需要设置其值为true.-->
<!--notificationTimeout:接受事件的时间间隔,通常将其设置为100即可.-->
<!--packageNames:表示对该服务是用来监听哪个包的产生的事件-->
点击:
val clickPath = Path()
clickPath.moveTo(x, y)
val builder = GestureDescription.Builder()
val gestureDescription = builder.addStroke(GestureDescription.StrokeDescription(clickPath, 100, 50)).build()
dispatchGesture(gestureDescription, object : GestureResultCallback(){}, null)
滑动:
val slidePath = Path()
slidePath.moveTo(x, y)
slidePath.lineTo(x2, y2)
val builder = GestureDescription.Builder()
val gestureDescription = builder.addStroke(GestureDescription.StrokeDescription(slidePath, 100, 1000)).build()
dispatchGesture(gestureDescription, object : GestureResultCallback(){}, null)