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

到此這篇關於Android實現自動點擊無障礙服務功能的文章就介紹到這瞭,更多相關android無障礙自動點擊內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀:

    None Found