Android实现自动点击无障碍服务功能的实例代码
作者:请叫我公子 发布时间:2022-08-31 12:14:26
标签:android,无障碍,自动点击
ps: 不想看代码的滑到最下面有apk包百度网盘下载地址
1. 先看效果图 不然都是耍流氓
2.项目目录
3.一些配置
build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "com.znan.autoclick"
minSdkVersion 24
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
//协程
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3"
}
accessibility.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackAllMask"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true"
android:description="@string/accessibility_desc" />
AndroidManifest.xml 注册权限和服务
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.znan.autoclick">
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AutoClick">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".AutoClickService"
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>
</application>
</manifest>
4.代码
AutoClickService.kt 无障碍服务
import android.accessibilityservice.AccessibilityService
import android.accessibilityservice.GestureDescription
import android.app.Notification
import android.content.Intent
import android.graphics.Path
import android.os.Build
import android.util.Log
import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import kotlinx.coroutines.*
class AutoClickService : AccessibilityService() {
private val TAG = javaClass.canonicalName
var mainScope: CoroutineScope? = null
//点击间隔
private var mInterval = -1L
//点击坐标xy
private var mPointX = -1f
private var mPointY = -1f
//悬浮窗视图
private lateinit var mFloatingView: FloatingClickView
companion object {
val FLAG_ACTION = "flag_action"
//打开悬浮窗
val ACTION_SHOW = "action_show"
//自动点击事件 开启/关闭
val ACTION_PLAY = "action_play"
val ACTION_STOP = "action_stop"
//关闭悬浮窗
val ACTION_CLOSE = "action_close"
}
override fun onCreate() {
super.onCreate()
startForegroundNotification()
mFloatingView = FloatingClickView(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand " + intent?.extras)
intent?.apply {
val action = getStringExtra(FLAG_ACTION)
Log.d(TAG, "action " + action)
when (action) {
ACTION_SHOW -> {
mInterval = getLongExtra("interval", 5000)
mFloatingView.show()
}
ACTION_PLAY -> {
mPointX = getFloatExtra("pointX", 0f)
mPointY = getFloatExtra("pointY", 0f)
mainScope = MainScope()
autoClickView(mPointX, mPointY)
}
ACTION_STOP -> {
mainScope?.cancel()
}
ACTION_CLOSE -> {
mFloatingView.remove()
mainScope?.cancel()
}
else -> {
Log.e(TAG, "action error")
}
}
}
return super.onStartCommand(intent, flags, startId)
}
private fun startForegroundNotification() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val notificationBuilder =
NotificationCompat.Builder(this, NotificationConstants.CHANNEL_ID)
val notification = notificationBuilder.setOngoing(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setCategory(Notification.CATEGORY_SERVICE)
.build()
startForeground(-1, notification)
} else {
startForeground(-1, Notification())
}
}
@RequiresApi(Build.VERSION_CODES.N)
private fun autoClickView(x: Float, y: Float) {
mainScope?.launch {
while (true) {
delay(mInterval)
Log.d(TAG, "auto click x:$x y:$y")
val path = Path()
path.moveTo(x, y)
val gestureDescription = GestureDescription.Builder()
.addStroke(GestureDescription.StrokeDescription(path, 100L, 100L))
.build()
dispatchGesture(
gestureDescription,
object : AccessibilityService.GestureResultCallback() {
override fun onCompleted(gestureDescription: GestureDescription?) {
super.onCompleted(gestureDescription)
Log.d(TAG, "自动点击完成")
}
override fun onCancelled(gestureDescription: GestureDescription?) {
super.onCancelled(gestureDescription)
Log.d(TAG, "自动点击取消")
}
},
null
)
}
}
}
override fun onInterrupt() {
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
}
override fun onDestroy() {
super.onDestroy()
mainScope?.cancel()
}
}
悬浮窗
SingletonHolder.kt
open class SingletonHolder<out T, in A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
@Volatile private var instance: T? = null
fun getInstance(arg: A): T {
val i = instance
if (i != null) {
return i
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}
FloatingManager.kt
import android.content.Context
import android.view.View
import android.view.WindowManager
class FloatingManager private constructor(context: Context) {
//获得WindowManager对象
private var mWindowManager: WindowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
companion object : SingletonHolder<FloatingManager, Context>(::FloatingManager)
/**
* 添加悬浮窗
* @param view
* @param params
* @return
*/
fun addView(view: View, params: WindowManager.LayoutParams): Boolean {
try {
mWindowManager.addView(view, params)
return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
/**
* 移除悬浮窗
*
* @param view
* @return
*/
fun removeView(view: View): Boolean {
try {
mWindowManager.removeView(view)
return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
/**
* 更新悬浮窗参数
*
* @param view
* @param params
* @return
*/
fun updateView(view: View, params: WindowManager.LayoutParams): Boolean {
try {
mWindowManager.updateViewLayout(view, params)
return true
} catch (e: Exception) {
e.printStackTrace()
}
return false
}
}
FloatingClickView.kt
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.graphics.PixelFormat
import android.os.Build
import android.view.*
import android.widget.FrameLayout
import androidx.appcompat.widget.AppCompatImageView
class FloatingClickView(private val mContext: Context) : FrameLayout(mContext) {
private lateinit var mWindowManager: FloatingManager
private var mParams: WindowManager.LayoutParams? = null
private lateinit var mView: View
//按下坐标
private var mTouchStartX = -1f
private var mTouchStartY = -1f
val STATE_CLICKING = "state_clicking"
val STATE_NORMAL = "state_normal"
private var mCurrentState = STATE_NORMAL
private var ivIcon: AppCompatImageView? = null
init {
initView()
}
private fun initView() {
mView = LayoutInflater.from(context).inflate(R.layout.view_floating_click, null)
ivIcon = mView.findViewById(R.id.iv_icon)
mWindowManager = FloatingManager.getInstance(mContext)
initListener()
}
@SuppressLint("ClickableViewAccessibility")
private fun initListener() {
mView.setOnTouchListener { v, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> {
mTouchStartX = event.rawX
mTouchStartY = event.rawY
}
MotionEvent.ACTION_MOVE -> {
mParams?.let {
it.x += (event.rawX - mTouchStartX).toInt()
it.y += (event.rawY - mTouchStartY).toInt()
mWindowManager.updateView(mView, it)
}
mTouchStartX = event.rawX
mTouchStartY = event.rawY
}
}
false
}
mView.setOnClickListener {
val location = IntArray(2)
it.getLocationOnScreen(location)
val intent = Intent(context, AutoClickService::class.java)
when (mCurrentState) {
STATE_NORMAL -> {
mCurrentState = STATE_CLICKING
intent.putExtra(AutoClickService.FLAG_ACTION, AutoClickService.ACTION_PLAY)
intent.putExtra("pointX", (location[0] - 1).toFloat())
intent.putExtra("pointY", (location[1] - 1).toFloat())
ivIcon?.setImageResource(R.drawable.ic_auto_click_icon_green_24)
}
STATE_CLICKING -> {
mCurrentState = STATE_NORMAL
intent.putExtra(AutoClickService.FLAG_ACTION, AutoClickService.ACTION_STOP)
ivIcon?.setImageResource(R.drawable.ic_auto_click_icon_gray_24)
}
}
context.startService(intent)
}
}
fun show() {
mParams = WindowManager.LayoutParams()
mParams?.apply {
gravity = Gravity.CENTER
//总是出现在应用程序窗口之上
type = if (Build.VERSION.SDK_INT >= 26) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
}
//设置图片格式,效果为背景透明
format = PixelFormat.RGBA_8888
flags =
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR or
WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
width = LayoutParams.WRAP_CONTENT
height = LayoutParams.WRAP_CONTENT
if (mView.isAttachedToWindow) {
mWindowManager.removeView(mView)
}
mWindowManager.addView(mView, this)
}
}
fun remove() {
mWindowManager.removeView(mView)
}
}
页面事件
MainActivity.kt
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Build
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings
import android.text.TextUtils
import android.util.Log
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : AppCompatActivity() {
private val TAG = javaClass::class.java.canonicalName
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initNotification()
initListener()
}
private fun initListener() {
btn_accessibility.setOnClickListener {
checkAccessibility()
}
btn_floating_window.setOnClickListener {
checkFloatingWindow()
}
btn_show_window.setOnClickListener {
hideKeyboard()
if (TextUtils.isEmpty(et_interval.text.toString())) {
Snackbar.make(et_interval, "请输入间隔", Snackbar.LENGTH_SHORT).show()
return@setOnClickListener
}
showFloatingWindow()
}
btn_close_window.setOnClickListener {
closeFloatWindow()
}
btn_test.setOnClickListener {
Log.d(TAG, "btn_test on click")
}
}
/**
* 跳转设置开启无障碍
*/
private fun checkAccessibility() {
val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
startActivity(intent)
}
/**
* 跳转设置顶层悬浮窗
*/
private fun checkFloatingWindow() {
if (Build.VERSION.SDK_INT >= 23) {
if (Settings.canDrawOverlays(this)) {
Toast.makeText(this, "已开启", Toast.LENGTH_SHORT).show()
} else {
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
startActivity(intent)
}
}
}
private fun showFloatingWindow() {
val intent = Intent(this, AutoClickService::class.java)
intent.apply {
putExtra(AutoClickService.FLAG_ACTION, AutoClickService.ACTION_SHOW)
putExtra("interval", et_interval.text.toString().toLong())
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}
private fun closeFloatWindow() {
val intent = Intent(this, AutoClickService::class.java)
intent.putExtra(AutoClickService.FLAG_ACTION, AutoClickService.ACTION_CLOSE)
startService(intent)
}
private fun initNotification() {
//注册渠道id
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val name = NotificationConstants.CHANNEl_NAME
val descriptionText = NotificationConstants.CHANNEL_DES
val importance = NotificationManager.IMPORTANCE_DEFAULT
val channel =
NotificationChannel(NotificationConstants.CHANNEL_ID, name, importance).apply {
description = descriptionText
}
channel.enableLights(true)
channel.lightColor = Color.GREEN
// Register the channel with the system
val notificationManager: NotificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
}
override fun onDestroy() {
val intent = Intent(this, AutoClickService::class.java)
stopService(intent)
super.onDestroy()
}
//收起输入法
fun hideKeyboard() {
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
if (imm.isActive && currentFocus != null) {
imm.hideSoftInputFromWindow(currentFocus!!.windowToken, InputMethodManager.HIDE_NOT_ALWAYS)
}
}
}
object NotificationConstants {
val CHANNEL_ID = "auto_channel_id"
val CHANNEl_NAME ="Auto Click"
val CHANNEL_DES = "Auto Click Service"
}
5.布局
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginTop="20dp"
android:text="先打开无障碍权限 和 悬浮窗顶层权限\n点击位置在图标左上角xy偏移一个px\n程序将会在延迟一个间隔后开始执行"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_accessibility"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="50dp"
android:layout_marginTop="20dp"
android:text="无障碍选项"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tv_message" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_floating_window"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:text="悬浮窗选项"
app:layout_constraintStart_toEndOf="@id/btn_accessibility"
app:layout_constraintTop_toTopOf="@id/btn_accessibility" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/tv_unit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="50dp"
android:text="ms"
android:textSize="24sp"
app:layout_constraintBottom_toBottomOf="@id/et_interval"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/et_interval" />
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/et_interval"
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_marginStart="50dp"
android:layout_marginTop="50dp"
android:layout_marginEnd="10dp"
android:hint="点击的间隔(建议大于100)(毫秒)"
android:inputType="number"
android:textColor="#FF0000"
android:textSize="14sp"
app:layout_constraintEnd_toStartOf="@id/tv_unit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_accessibility" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_show_window"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:text="打开悬浮视图"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/et_interval" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_close_window"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:text="关闭悬浮视图"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/btn_show_window" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btn_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="100dp"
android:text="测试点击按钮"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/iv_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_auto_click_icon_gray_24" />
</FrameLayout>
6.debug.apk
链接: https://pan.baidu.com/s/1ukuJofO3SOfdOw5vgfMG4g
提取码: va43
来源:https://blog.csdn.net/qq_30837235/article/details/115902248
0
投稿
猜你喜欢
- 前言Swing是一个用于开发Java应用程序用户界面的开发工具包。它以抽象窗口工具包(AWT)为基础使跨平台应用程序可以使用任何可插拔的外观
- 本篇文章介绍selenium 操作浏览器阅读目录浏览器最大化 前进,后退, 刷新截图操作模拟鼠标操作杀掉Windows浏览器进程浏览器最大化
- 前言本篇文章主要讲述的是SpringBoot整合Mybatis、Druid和PageHelper 并实现多数据源和分页。其中SpringBo
- Java中文件与目录管理目录是管理文件的特殊机制,同类文件保存在同一个目录下不仅可以简化文件管理,而且还可以提高工作效率。Java 语言在
- 大部分Java开发者都在使用Map,特别是HashMap。HashMap是一种简单但强大的方式去存储和获取数据。但有多少开发者知道HashM
- 1.XxlJob简介官方网址:https://www.xuxueli.com/xxl-jobXXL-JOB是一个分布式任务调度平台,其核心设
- 1. 什么是AOPAOP (Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期 * 实现在
- 1. 需要准备:apache-maven-3.2.3.zip包 Repository.rar本地仓库
- 历史原因当系统启动一个APP时,zygote进程会首先创建一个新的进程去运行这个APP,但是进程的创建是需要时间的,在创建完成之前,界面是呈
- java中 Set与Map排序输出到Writer详解及实例一般来说java.util.Set,java.util.Map输出的内
- 实现步骤:工具:IDEA数据库版本:mysql5.7一、环境搭建1.创建springboot项目pom.xml2.pom.xml : spr
- 我就废话不多说了,大家还是直接看代码吧~ public static void main(String[] args) { &n
- 一、Nuget安装log4net --> Install-Package log4net二、在AssemblyInfo.cs文件中添加
- 1 框架组成SpringSpringMVCMyBatis2 所需工具Mysql 8.0.15数据库管理系统,创建数据库Tomcat 8.5.
- settings.xml有什么用?如果在Eclipse中使用过Maven插件,想必会有这个经验:配置settings.xml文件的路径。se
- 1. 概述在 Spring Security 5.2 中增强了 DSL 的功能:允许使用 Lambda 表达式来配置 HTTP securi
- springboot默认的打包方式为jar包,jar包的有优点是不需要更改的配置,并且内嵌了tomcat,如果打成jar包直接可以丢到doc
- 本文实例讲述了Java采用循环链表结构求解约瑟夫问题的方法。分享给大家供大家参考。具体分析如下:这是第一次java考试的试题,对于没看过链表
- 需求: 给定一个URL地址, 例如: http://www.cncounter.com/tools/shorturl.php, 解析对应的I
- java模拟银行ATM机操作(基础版),供大家参考,具体内容如下实现的功能需求:修改密码之后,就会自动退出登录,再重新登录,若登录成功才能验