文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android实现自动点击无障碍服务功能的实例代码

2024-04-02 19:55

关注

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)
 
 
    
    fun addView(view: View, params: WindowManager.LayoutParams): Boolean {
        try {
            mWindowManager.addView(view, params)
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
 
        return false
    }
 
    
    fun removeView(view: View): Boolean {
        try {
            mWindowManager.removeView(view)
            return true
        } catch (e: Exception) {
            e.printStackTrace()
        }
 
        return false
    }
 
    
    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

到此这篇关于Android实现自动点击无障碍服务功能的文章就介绍到这了,更多相关android无障碍自动点击内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯