Android AccessibilityService學習之分析Auto.js實現自動雙指捏合,三指下滑

Auto.js是什麼?

Github鏈接:https://github.com/hyb1996/Auto.js

1.由無障礙服務實現的簡單易用的自動操作函數。
2.懸浮窗錄製和運行更專業&強大的選擇器API,提供對屏幕上的控件的尋找、遍歷、獲取信息、操作等。類似於Google的UI測試框架UiAutomator,您也可以把他當做移動版UI測試框架使用。

以上摘自Github簡介,通俗來說它是通過AccessibilityService的服務,結合javascript的腳本來實現類似於按鍵精靈的一款可編寫腳本的安卓輔助軟件。

這篇文章的目的?

這篇文章將通過分析Auto.js 與 AccessibilityService相關部分的代碼來學習AccessibilityService的進階知識,通過這篇文章的學習,我們來更好的掌握AccessibilityService的主動操作知識,比如實現手勢軌跡,模擬下拉通知欄(非調用系統API),雙指捏合,三指下滑等。

First Step

瞭解AccessibilityService,我們可以通過其他博主的文章來了解,不過我更推薦使用官方文檔來看,附上鍊接:https://developer.android.com/reference/android/accessibilityservice/AccessibilityService
也可以學習我的微信自動回覆的文章來了解,去博客裏找一下不附鏈接了。
通過學習需要掌握關於AccessibilityService的基本使用,可以自己寫一個Demo安裝在自己的手機中,在onServiceConnected()方法中實現點擊開啓服務後彈出一個Toast,這樣就算走完的第一步。

Second Step

做完了第一步,接下來我們再回到官方文檔,來看看幾個重要的方法和類。
Public methods

返回值 方法名(描述)
final boolean dispatchGesture(GestureDescription gesture, AccessibilityService.GestureResultCallback callback, Handler handler)Dispatch a gesture to the touch screen.
解釋 上面這個dispatchGesture方法是把手勢分發,手勢可以模擬用戶全部操作,如手指點擊、滑動。進行中的手勢只允許有一個,多次發送手勢,之前正在進行的手勢會被取消。留下一個問題,這個可以實現三指下滑嗎?
final boolean performGlobalAction(int action)Performs a global action.
解釋 發送點擊,發送文本改變,發送獲取焦點,發送home、back,複製粘貼等操作,具體的可以AccessibilityNodeInfo類中查找。

瞭解了以上知識,我們已經可以開始對Auto.js下手了。下載源碼後我們找到GlobalActionAutomator.class,這裏Auto.js的作者通過AccessibilityService實現了類似按鍵精靈的操作,用戶在不用root的情況下也可以使用模擬點擊,我們來看看代碼。

學習dispatchGesture

//這句話要求api版本是N(7.0)以上
@RequiresApi(api = Build.VERSION_CODES.N)
	//函數gesture傳入了三個參數分別是延時,執行時間和一個int的二維數組。=
    public boolean gesture(long start, long duration, int[]... points) {
    	//這裏通過下面的pointsToPath方法將points數組轉換爲路徑。
        Path path = pointsToPath(points);
        這裏調用gestures函數,這個函數等下來看
        return gestures(new GestureDescription.StrokeDescription(path, start, duration));
    }
	//解析函數傳入一個二維數組,事實上是一個int[n][2]的函數。
    private Path pointsToPath(int[][] points) {
        Path path = new Path();
        //moveTo(float x, float y) Set the beginning of the next contour to the point (x,y).
        //這個方法是將觸點移動到下個動作的開始位置
        path.moveTo(scaleX(points[0][0]), scaleY(points[0][1]));
        //lineTo(float x, float y)Add a line from the last point to the specified point (x,y).
        //這個方法是從上個點到這個點直接劃線,相當於在屏幕上劃一條直線。
        //通過循環將各個點之間連起來
        for (int i = 1; i < points.length; i++) {
            int[] point = points[i];
            path.lineTo(scaleX(point[0]), scaleY(point[1]));
        }
       //最後返回路徑。
        return path;
    }

上面的代碼很簡單,其實就是將int的二維數組轉化爲路徑然後調用了gestures函數,這個函數我們還沒有看,接着走。

public boolean gestures(GestureDescription.StrokeDescription... strokes) {
		//這裏的mService其實就是我們的AccessibilityService
        if (mService == null)
            return false;
        //使用GestureDescription.Builder().addStroke()方法添加GestureDescription.StrokeDescription類。
        //這裏addStroke()方法是可以多次調用的,可以加入不同的路徑同時模擬。
        //這樣就可以通過這個方法傳入多個path來實現雙指捏合,三指下滑。
        GestureDescription.Builder builder = new GestureDescription.Builder();
        for (GestureDescription.StrokeDescription stroke : strokes) {
            builder.addStroke(stroke);
        }
        //下面都是不同的構造方法,自己看看就行了。
        if (mHandler == null) {
            return gesturesWithoutHandler(builder.build());
        } else {
            return gesturesWithHandler(builder.build());
        }
    }

這個類其實就分析完了,很簡單,Auto.js的作者通過一個int二維數組轉路徑的方法就實現了路徑的轉存工作。下面附上一個路徑執行的代碼轉換,可以更好的理解。

accessibilityService
	.dispatchGesture(new GestureDescription.Builder()
						.addStroke(new GestureDescription.
							StrokeDescription(path, 0, 500)).build()
							, 回調函數(可以爲null), 回調的線程(null表示主線程));

上面代碼等價於下面的代碼,我是從Auto.js作者的角度進行分解的。

GestureDescription.Builder builder = new GestureDescription.Builder();
Path path = new Path();
GestureDescription.StrokeDescription stroke = 
			new GestureDescription.StrokeDescription(path,0,500);
builder.addStroke(stroke);
accessibilityService.dispatchGesture(builder.build(),null,null);

學習performGlobalAction

這個部分的相關內容在SimpleActionAutomator.java文件內,我們可以通過學習這個類來學習performGlobalAction,話不多說看代碼。

public boolean back() {
        return performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
    }

先來個簡單的,這個方法很簡單,就是直接調用了系統給返回方法,使用performGlobalAction,就可以模擬點擊返回按鈕。當然了,系統提供了很多方法,比如點擊Home鍵,下拉通知欄等等,這裏大家可以去看官方文檔,就不多說了。

public boolean click(ActionTarget target) {
        return performAction(target.createAction(AccessibilityNodeInfo.ACTION_CLICK));
    }

看看這段代碼,好像很簡單啊,其實不然,Auto.js作者在這裏把控件的方法執行進行的分裝,我們要一步一步去找,那就開始找吧。我們的着手點事target,也就是ActionTarget類。打開之後可以看見其實是個接口,裏面封裝了幾個內部類。隨便找一個來看看。

class TextActionTarget implements ActionTarget {
        String mText;
        int mIndex;

        public TextActionTarget(String text, int i) {
            mText = text;
            mIndex = i;
        }

        @Override
        public SimpleAction createAction(int action, Object... params) {
            return ActionFactory.createActionWithTextFilter(action, mText, mIndex);
        }
    }

這是接口ActionTarget的一個內部類,它實現了ActionTarget方法,我們看createAction方法,事實上它又調用了ActionFactory的createActionWithTextFilter方法,那接着找唄。

public static SimpleAction createActionWithTextFilter(int action, String text, int index) {
        if (searchUpAction.containsKey(action))
            return new SearchUpTargetAction(action, new FilterAction.TextFilter(text, index));
        else
            return new DepthFirstSearchTargetAction(action, new FilterAction.TextFilter(text, index));
    }

找到了ActionFactory類裏的createActionWithTextFilter方法,它判斷了我們要執行的Action來判斷新建哪個類,下面是執行new一個SearchUpTargetAction類時Action的類型列表

private static Map<Integer, Object> searchUpAction = new MapEntries<Integer, Object>()
            .entry(AccessibilityNodeInfo.ACTION_CLICK, null)
            .entry(AccessibilityNodeInfo.ACTION_LONG_CLICK, null)
            .entry(AccessibilityNodeInfo.ACTION_SELECT, null)
            .entry(AccessibilityNodeInfo.ACTION_FOCUS, null)
            .entry(AccessibilityNodeInfo.ACTION_SELECT, null)
            .entry(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, null)
            .entry(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, null)
            .map();
Ⅰ 、路線SearchUpTargetAction

當執行這些Action時會new SearchUpTargetAction(),我們去找這個類,看看它是如何做的。

@Override
    public boolean perform(List<UiObject> nodes) {
        boolean performed = false;
        for (UiObject node : nodes) {
            node = searchTarget(node);
            if (node != null && performAction(node)) {
                performed = true;
            }
        }
        return performed;
    }

    protected boolean performAction(UiObject node) {
        return node.performAction(mAction);
    }

    public int getAction() {
        return mAction;
    }

    public UiObject searchTarget(UiObject node) {
        return node;
    }

這個類裏的關鍵方法就是perform()它又調用了performAction()方法,其實到這裏思路已經基本清楚了,performAction()方法相當於執行了我們target組件的相關方法,包括點擊,長點擊等操作。

Ⅱ 路線DepthFirstSearchTargetAction
public UiObject searchTarget(UiObject n) {
        if (n == null)
            return null;
        if (mAble.isAble(n))
            return n;
        for (int i = 0; i < n.getChildCount(); i++) {
            UiObject child = n.child(i);
            if (child == null)
                continue;
            UiObject node = searchTarget(child);
            if (node != null)
                return node;
        }
        return null;
    }

這個類的主要方法就是這個,其實就是判斷別的操作是否可以在這個控件上執行。

Third

到這裏其實就把Auto.js裏的有關AccessibilityService的基礎部分進行了分析,那我們就根據第二部分學的相關知識來寫一個AutoAc的Demo來鞏固一下相關知識。一共有四個類附上代碼:
MainActivity:

public class MainActivity extends AppCompatActivity {
    private Button button, button1;
    private AccessibilityService accessibilityService;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initAutoAcService();
        accessibilityService = AutoAcService.getAutoACService();
        Log.d("accessibilityService", accessibilityService.toString());
        button = (Button) findViewById(R.id.button);
        button1 = (Button) findViewById(R.id.button2);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                pullDownNotification();
            }
        });
        button1.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                viewClick();
            }
        });
    }
    private void initAutoAcService() {
        if (!AutoAcUtil.isServiceON(this, AutoAcService.class.getName())) {
            Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            startActivity(intent);
        }
    }
    private void pullDownNotification() {
        Path path = new Path();
        Path path1 = new Path();
        Path path2 = new Path();
        path.moveTo(300, 20);
        path.lineTo(300, 1500);
        for (int i = 0; i < 1; i++) {
            if (accessibilityService != null) {
                accessibilityService.dispatchGesture(new GestureDescription.Builder().addStroke(new GestureDescription.StrokeDescription(path, 0
                        , 500)).build(), null, null);
            }
        }
    }

    private void viewClick() {
        AccessibilityNodeInfo nodeInfo = AutoAcService.getNodeInfo();
        if (nodeInfo == null) {
            AutoAcUtil.toastShow(this,"nodeInfo is null",1);
            return;
        }
        AccessibilityNodeInfo target = findNodeInfosByText(nodeInfo, "BUTTON");

        target.performAction(AccessibilityNodeInfo.ACTION_CLICK);
    }

    public AccessibilityNodeInfo findNodeInfosByText(AccessibilityNodeInfo nodeInfo, String text) {
        List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(text);
        if (list == null || list.isEmpty()) {
            return null;
        }
        return list.get(0);
    }
}

有兩個按鈕第一個實現了dispatchGesture方法來實現一個簡單的下拉通知欄的操作。第二個按鈕這是實現了performAction方法,通過查找找到第一個按鈕進行點擊,也會下拉通知欄。附上剩下的幾個類。
AutoAcService類:

public class AutoAcService extends AccessibilityService {
    private static AutoAcService autoACService;
    public static AccessibilityNodeInfo getNodeInfo() {
        return nodeInfo;
    }
    private static AccessibilityNodeInfo nodeInfo;
    public AutoAcService() {
        super();
        autoACService = this;
    }
    public static AutoAcService getAutoACService(){
        if (autoACService != null){
            return autoACService;
        }
        return null;
    }
    @Override
    public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
        nodeInfo = getRootInActiveWindow();
    }
    @Override
    public void onInterrupt() {
    }
}

AutoAcUtil類:

public class AutoAcUtil {
    public static boolean isServiceON(Context context,String className) {
        ActivityManager activityManager = (ActivityManager) context.getSystemService(context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo>
                runningServices = activityManager.getRunningServices(100);
        if (runningServices.size() < 0) {
            return false;
        }
        for (int i = 0; i < runningServices.size(); i++) {
            ComponentName service = runningServices.get(i).service;
            if (service.getClassName().contains(className)) {
                return true;
            }
        }
        return false;
    }

    public static void toastShow(Context context, String msg, int time) {
        if (time == 0) {
            Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
        } else if (time == 1) {
            Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
        }
    }
}

當然了這些類提供的方法你們也可以自己看一下,最後在Manifest裏聲明一下服務和權限就可以運行了。

Fourth

以上就是通過分析Auto.js來學習AccessibilityService,當然了這裏的思路並不一定是Auto.js作者的,但是我們能實現相關方法瞭解AccessibilityService就可以了。

Demo代碼下載:https://download.csdn.net/download/qq_35928566/10828033

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章