Android監聽使用者行為操作(AccessibilityService)

DeMonnnnnn發表於2018-08-13

前言

今天我們將使用AccessibilityService實現:

  1. 監聽第三方程式的介面變化(監聽第三方程式的啟動的實現原理)。
  2. 模擬點選第三方應用的按鈕(自動搶紅包程式的實現原理)。
  3. 監聽第三方程式的點選事件。

如果要測試的第三方應用不為自己的,則需要獲取第三方應用的包名,當前Acvtivity等資訊。可以參考:
Android獲取第三方程式的包名

模擬程式

我們先寫一個模擬程式,該模擬程式只有一個按鈕用於模擬點選事件。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //Toast.makeText(MainActivity.this, "我被點選了!!!", Toast.LENGTH_SHORT).show();
                Log.i(TAG, "onClick: 我被點選了!!!");
            }
        });
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <Button
        android:id="@+id/btn_click"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="模擬點選" />
</LinearLayout>

監聽程式

AccessibilityService

程式碼具體的詳情請看註釋。

public class ListeningService extends AccessibilityService {
    private static final String TAG = "WindowChange";

    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
        AccessibilityServiceInfo config = new AccessibilityServiceInfo();
        //配置監聽的事件型別為介面變化|點選事件
        config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED | AccessibilityEvent.TYPE_VIEW_CLICKED;
        config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
        if (Build.VERSION.SDK_INT >= 16) {
            config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
        }
        setServiceInfo(config);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        AccessibilityNodeInfo nodeInfo = event.getSource();//當前介面的可訪問節點資訊
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {//介面變化事件
            ComponentName componentName = new ComponentName(event.getPackageName().toString(), event.getClassName().toString());
            ActivityInfo activityInfo = tryGetActivity(componentName);
            boolean isActivity = activityInfo != null;
            if (isActivity) {
                Log.i(TAG, componentName.flattenToShortString());
                //格式為:(包名/.+當前Activity所在包的類名)
                //如果是模擬程式的操作介面
                if (componentName.flattenToShortString().equals("com.demon.simulationclick/.MainActivity")) {
                    //當前是模擬程式的主頁面,則模擬點選按鈕
                    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
                        //通過id尋找控制元件,id格式為:(包名:id/+制定控制元件的id)
                        //一般除非第三方應該是自己的,否則我們很難通過這種方式找到控制元件
                        //List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("com.demon.simulationclick:id/btn_click");
                        //通過控制元件的text尋找控制元件
                        List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("模擬點選");
                        if (list != null && list.size() > 0) {
                            list.get(0).performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        }
                    }
                }
            }
        }
        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED) {//View點選事件
            //Log.i(TAG, "onAccessibilityEvent: " + nodeInfo.getText());
            if ((nodeInfo.getText() + "").equals("模擬點選")) {
                //Toast.makeText(this, "這是來自監聽Service的響應!", Toast.LENGTH_SHORT).show();
                Log.i(TAG, "onAccessibilityEvent: 這是來自監聽Service的響應!");
            }
        }
    }

    private ActivityInfo tryGetActivity(ComponentName componentName) {
        try {
            return getPackageManager().getActivityInfo(componentName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }

    @Override
    public void onInterrupt() {
    }

}

配置Service

AndroidManifest.xml
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
···
<service
            android:name=".ListeningService"
            android:label="@string/app_name"
            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/accessibilityservice" />
        </service>
 ···
accessibilityservice.xml
方法 說明
android:accessibilityEventTypes 設定響應事件的型別typeAllMask,typeViewClicked,typeViewFocused,typeNotificationStateChanged,typeWindowStateChanged等
android:accessibilityFeedbackType 設定反饋給使用者的方式
android:canRetrieveWindowContent 是否檢索當前視窗的內容,即是否可以獲取當前視窗的View
android:description 服務申請的許可權描述說明
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagIncludeNotImportantViews"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_service_description"
    tools:ignore="UnusedAttribute" />

MainActivity

使用AccessibilityService服務需要申請輔助功能(小米手機叫:無障礙)的服務支援,無法主動給與,需要到指定介面使用者手動開啟。

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    private Intent intent;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                if (!isAccessibilitySettingsOn(MainActivity.this, ListeningService.class.getCanonicalName())) {
                    Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
                    startActivity(intent);
                } else {
                    intent = new Intent(MainActivity.this, ListeningService.class);
                    startService(intent);
                }
            }
        });

        findViewById(R.id.stop).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (intent != null) {
                    stopService(intent);
                }
            }
        });
    }


    /**
     * 檢測輔助功能是否開啟
     *
     * @param mContext
     * @return boolean
     */
    private boolean isAccessibilitySettingsOn(Context mContext, String serviceName) {
        int accessibilityEnabled = 0;
        // 對應的服務
        final String service = getPackageName() + "/" + serviceName;
        //Log.i(TAG, "service:" + service);
        try {
            accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),
                    android.provider.Settings.Secure.ACCESSIBILITY_ENABLED);
            Log.v(TAG, "accessibilityEnabled = " + accessibilityEnabled);
        } catch (Settings.SettingNotFoundException e) {
            Log.e(TAG, "Error finding setting, default accessibility to not found: " + e.getMessage());
        }
        TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');

        if (accessibilityEnabled == 1) {
            Log.v(TAG, "***ACCESSIBILITY IS ENABLED*** -----------------");
            String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (settingValue != null) {
                mStringColonSplitter.setString(settingValue);
                while (mStringColonSplitter.hasNext()) {
                    String accessibilityService = mStringColonSplitter.next();

                    Log.v(TAG, "-------------- > accessibilityService :: " + accessibilityService + " " + service);
                    if (accessibilityService.equalsIgnoreCase(service)) {
                        Log.v(TAG, "We've found the correct setting - accessibility is switched on!");
                        return true;
                    }
                }
            }
        } else {
            Log.v(TAG, "***ACCESSIBILITY IS DISABLED***");
        }
        return false;
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <Button
        android:id="@+id/start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="開啟監聽服務" />

    <Button
        android:id="@+id/stop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="關閉監聽服務" />
</LinearLayout>

效果

執行監聽程式,給與服務支援後,開啟監聽程式。
然後執行模擬程式,效果如下。

監聽到com.demon.simulationclick/.MainActivity——-模擬點選按鈕—–監聽到按鈕被點選——從監聽Service發出響應
這裡寫圖片描述

程式碼

https://github.com/Demo-DeMon/ActivtyChange

參考

https://blog.csdn.net/c794904140/article/details/52153148

相關文章