當我們把輔助權限玩的比較熟悉 的時候三圆,就可以釋放我們的雙手做一些有趣的事情了券盅,例如之前網(wǎng)上流傳的微信自動搶紅包插件钾唬,就是使用的這個服務十办,不過我們今兒講的是微信自動評論與點贊功能(搶紅包的文章網(wǎng)上已經(jīng)有比較多)。
一嗅绰、懸浮窗引導開啟提示
為了更好的用戶體驗舍肠,我們需要給我們每一步操作一個明確的提示,讓用戶知道需要做些什么办陷,特別是引導開啟系統(tǒng)權限的時候貌夕。
關于懸浮窗的開啟,之前有寫過一篇文章民镜,Android 懸浮窗踩坑體驗啡专,里面有介紹關于懸浮窗的開啟、權限以及自定義懸浮窗制圈。不過這里我要介紹的是另一種特殊的技巧们童,在沒有開啟懸浮窗權限的情況下畔况,用一個特殊的 Activity 來代替懸浮窗。先介紹兩個 Activity 在 AndroidManifest 屬性:
1慧库、taskAffinity
簡單講一下這個屬性的意思:默認情況下跷跪,我們啟動的 Activity 都是歸屬于同包名的任務棧里面,但如果配置這個屬性齐板,則該 Activity 會在新的任務棧里面(棧名是你配置的)
android:taskAffinity=".guide"
可以通過以下命令去查看當前任務棧的信息:
adb shell dumpsys activity activities
2吵瞻、excludeFromRecents
當配置這個屬性,可以讓你的 Activity 不會出現(xiàn)在最近任務列表里面
android:excludeFromRecents="true"
3甘磨、配置 Activity 主題是全屏透明
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen"
為什么要配置這兩個屬性呢橡羞? 因為我們不希望這個特殊的Activity出現(xiàn)在最近的使用列表里面,同時配置 taskAffinity 是為了讓這個 Activity 在新的任務棧里面济舆,使得它在 finish 的時候卿泽,不是回到我們之前啟動過的前一個 Activity (并不想影響我們之前的任務棧),這樣的做法就能夠在其他 App 界面顯示我們的 Activity滋觉,需要特別說明的的是:啟動該 Acitivity 需要配合 Intent.FLAG_ACTIVITY_NEW_TASK 標識啟動签夭。代碼如下:
<activity
android:name="com.czc.ui.act.GuideActivity"
android:taskAffinity=".guide"
android:excludeFromRecents="true"
android:theme="@android:style/Theme.Translucent.NoTitleBar.Fullscreen">
</activity>
完整代碼:
public class GuideActivity extends Activity {
public static void start(Activity act, String message) {
Intent intent = new Intent(act, GuideActivity.class);
intent.putExtra("message", message);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
act.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_guide);
//設置Activity界面大小
Window window = getWindow();
window.setGravity(Gravity.LEFT | Gravity.TOP);
WindowManager.LayoutParams params = window.getAttributes();
params.x = 0;
params.y = 0;
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
params.height = ScreenUtil.dip2px(80);
params.width = WindowManager.LayoutParams.MATCH_PARENT;
window.setAttributes(params);
TextView tvMessage = findViewById(R.id.tv_message);
tvMessage.setText(getIntent().getStringExtra("message"));
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
// 5s 后自動關閉提示
finish();
}
}, 5000);
}
}
3、界面呈現(xiàn)的效果
1.檢測到?jīng)]有【懸浮窗權限】或者【輔助權限】椎侠,彈出權限設置頁面 PermissionActivity
2.跳轉(zhuǎn)系統(tǒng)設置里面的同時【彈出】 GuideActivity
二第租、自動化邏輯代碼實現(xiàn)
說明:我們通過 Monitor 工具,取到的節(jié)點 id 肺蔚,在微信的每個版本是不一樣的 (微信動態(tài)生成節(jié)點 id煌妈,我是通過服務器后臺對不同的微信版本儡羔,維護一份 id 配置宣羊,根據(jù)請求的微信版本,返回對應得 id 值)汰蜘,所以本文以微信 v6.7.2 的版本作為例子仇冯,如下代碼可作參考。
這里只是提供了微信自動點贊與自動評論的示例族操,當然本人也寫了類似于微信自動加好友苛坚,自動加附近的人,檢測死粉的功能色难,釘釘自動打卡... 這里只是拋轉(zhuǎn)引玉泼舱,大家根據(jù)我提供的思路,去實現(xiàn)枷莉,由于源碼涉及服務器相關操作娇昙,不太方便開源,有問題可以加我QQ:1029226002笤妙,
{
"tasks": [
{
"nodes": [
{
"action": "scrllor",
"className": "android.widget.ListView",
"id": "com.tencent.mm:id/cno",
"key": "nearHumanList",
"text": "@附近的人列表"
},
{
"className": "android.widget.TextView",
"id": "com.tencent.mm:id/b3i",
"key": "nearHumanListName",
"text": "@附近的人列表名字"
},
{
"className": "android.widget.TextView",
"id": "com.tencent.mm:id/sm",
"key": "detailName",
"text": "@附近的人詳情名字"
},
{
"className": "android.widget.ListView",
"id": "com.tencent.mm:id/bcs",
"key": "phoneHumanList",
"text": "@手機聯(lián)系人列表"
},
{
"className": "android.widget.TextView",
"id": "com.tencent.mm:id/bgl",
"key": "phoneHumanListName",
"text": "@手機聯(lián)系人列表名字"
},
{
"className": "android.widget.TextView",
"id": "com.tencent.mm:id/bgm",
"key": "phoneHumanListAccount",
"text": "@手機聯(lián)系人列表昵稱"
},
{
"className": "android.widget.TextView",
"id": "com.tencent.mm:id/sm",
"key": "phoneHumandetailName",
"text": "@手機聯(lián)系人詳情名字"
},
{
"className": "android.widget.ListView",
"id": "com.tencent.mm:id/doq",
"key": "momentList",
"text": "@朋友圈列表"
}
],
"pages": [ ],
"taskName": "微信腳本",
"version": "6.7.2"
}
]
}
1冒掌、微信朋友圈自動點贊
效果如圖(希望我朋友不會打我噪裕,這里就不視頻打碼了,┭┮﹏┭┮)股毫,這里面的懸浮窗可以有效阻擋用戶操作膳音,只有點擊停止之后,才能操作微信界面铃诬。
/**
* 朋友圈點贊
* Created by czc on 2018/8/23.
*/
public class LikeStrategy extends BaseStrategy {
@Override
protected boolean handleEvent() {
/**
* 匹配每個界面祭陷,根據(jù)當前界面執(zhí)行相關操作
* 1、在微信首頁點擊【發(fā)現(xiàn)】按鈕
* 2趣席、然后點擊【朋友圈】颗胡,進入朋友圈界面
* 3、滾動朋友圈【動態(tài)列表】吩坝,對每個動態(tài)進行點贊
*/
if (matchPage(Page.LauncherUI) || matchPage(Page.WxViewPager)) {
clickFindBtn(getRoot());
clickCommentTv(getRoot());
} else if (checkWxScroller(Page.SnsTimeLineUI)) {
scrollerList(getRoot());
} else if (matchPage(Page.BaseP)) {
} else {
return false;
}
return true;
}
private void clickFindBtn(AccessibilityNodeInfo root) {
NodeUtil.findNodeByTextAndClick(root, "發(fā)現(xiàn)");
}
private void clickCommentTv(AccessibilityNodeInfo root) {
NodeUtil.findNodeByTextAndClick(root, "朋友圈");
}
private void scrollerList(AccessibilityNodeInfo root) {
//這里的滾動控件對應于朋友圈動態(tài)的 ListView
AccessibilityNodeInfo scrollerNode = findNodeByTaskNode(root, getNode("momentList"));
if (scrollerNode == null) {
Log.e(TAG, "scroller is null");
return;
}
if (scrollerNode != null) {
final int count = scrollerNode.getChildCount();
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo child = scrollerNode.getChild(i);
if (child != null && child.isVisibleToUser()) {
AccessibilityNodeInfo commentNode = NodeUtil.findNodeByFilter(child, new NodeUtil.NodeFilter() {
@Override
public String text() {
return "評論";
}
@Override
public boolean filter(AccessibilityNodeInfo node) {
return node.getClassName() != null && node.getClassName().equals(NodeUtil.IMAGE_VIEW)
&& node.getContentDescription() != null && node.getContentDescription().toString().equals("評論");
}
});
if (commentNode != null && commentNode.isVisibleToUser()) {
NodeUtil.performClick(commentNode);
NodeUtil.findNodeByFilterAndClick(root, new NodeUtil.NodeFilter() {
@Override
public String text() {
return "贊";
}
@Override
public boolean filter(AccessibilityNodeInfo node) {
return node.getClassName() != null && node.getClassName().equals(NodeUtil.TEXT_VIEW)
&& node.getText() != null && node.getText().toString().equals("贊");
}
});
}
}
//可見的最后一個 item
if (i == count - 1) {
//滾動控件是否可以滾動
if (scrollerNode.isScrollable()) {
NodeUtil.findNodeByFilterAndClick(root, new NodeUtil.NodeFilter() {
@Override
public String text() {
return "評論";
}
@Override
public boolean filter(AccessibilityNodeInfo node) {
return node.getClassName() != null && node.getClassName().equals(NodeUtil.TEXT_VIEW)
&& node.getText() != null && node.getText().toString().equals("評論");
}
});
//循環(huán)滾動
performScroll(scrollerNode);
}
}
}
}
}
}
2毒姨、微信朋友圈自動評論
效果如圖
/**
* 朋友圈評論
* Created by czc on 2018/8/23.
*/
public class CommentStrategy extends BaseStrategy {
@Override
protected boolean handleEvent() {
/**
* 匹配每個界面,根據(jù)當前界面執(zhí)行相關操作
* 1钉寝、在微信首頁點擊【發(fā)現(xiàn)】按鈕
* 2弧呐、然后點擊【朋友圈】,進入朋友圈界面
* 3嵌纲、滾動朋友圈【動態(tài)列表】俘枫,對每個動態(tài)進行評論
*/
if (matchPage(Page.LauncherUI) || matchPage(Page.WxViewPager)) {
clickFindBtn(getRoot());
clickCommentTv(getRoot());
} else if (checkWxScroller(Page.SnsTimeLineUI)) {
scrollerList(getRoot());
} else if (matchPage(Page.BaseP)) {
} else {
return false;
}
return true;
}
private void clickFindBtn(AccessibilityNodeInfo root) {
NodeUtil.findNodeByTextAndClick(root, "發(fā)現(xiàn)");
}
private void clickCommentTv(AccessibilityNodeInfo root) {
NodeUtil.findNodeByTextAndClick(root, "朋友圈");
}
private void scrollerList(AccessibilityNodeInfo root) {
AccessibilityNodeInfo scrollerNode = findNodeByTaskNode(root, getNode("momentList"));
if (scrollerNode == null) {
Log.e(TAG, "scroller is null");
return;
}
if (scrollerNode != null) {
final int count = scrollerNode.getChildCount();
for (int i = 0; i < count; i++) {
AccessibilityNodeInfo child = scrollerNode.getChild(i);
if (child != null && child.isVisibleToUser()) {
AccessibilityNodeInfo commentNode = NodeUtil.findNodeByFilter(child, new NodeUtil.NodeFilter() {
@Override
public String text() {
return "評論";
}
@Override
public boolean filter(AccessibilityNodeInfo node) {
return node.getClassName() != null && node.getClassName().equals(NodeUtil.IMAGE_VIEW)
&& node.getContentDescription() != null && node.getContentDescription().toString().equals("評論");
}
});
if (commentNode != null && commentNode.isVisibleToUser()) {
NodeUtil.performClick(commentNode);
NodeUtil.findNodeByFilterAndClick(root, new NodeUtil.NodeFilter() {
@Override
public String text() {
return "評論";
}
@Override
public boolean filter(AccessibilityNodeInfo node) {
return node.getClassName() != null && node.getClassName().equals(NodeUtil.TEXT_VIEW)
&& node.getText() != null && node.getText().toString().equals("評論");
}
});
AccessibilityNodeInfo editNode = NodeUtil.findNodeByFilter(child, new NodeUtil.NodeFilter() {
@Override
public String text() {
return "評論";
}
@Override
public boolean filter(AccessibilityNodeInfo node) {
return node.getClassName() != null && node.getClassName().equals(NodeUtil.EDIT_TEXT)
&& node.getText() != null && node.getText().toString().equals("評論");
}
});
if (editNode == null) {
if (root != null) {
editNode = root.findFocus(AccessibilityNodeInfo.FOCUS_INPUT);
}
}
if (editNode != null) {
//輸入自定義評論內(nèi)容
NodeUtil.performPaste(editNode, "東西不錯哦~");
//點擊【發(fā)送】
NodeUtil.findNodeByTextAndClick(root, "發(fā)送");
}
}
}
if (i == count - 1) {
if (scrollerNode.isScrollable()) {
performScroll(scrollerNode);
}
}
}
}
}
}
3、相關工具類
工具類里面的代碼是自己封裝的逮走,不一定都適用鸠蚪,工具類可能存在不足與缺陷,為什么執(zhí)行操作需要 Sleep 1s 师溅,因為太快的話茅信,有些界面的節(jié)點信息還沒更新出來,會導致點擊失敗等情況:
/**
* Created by czc on 2017/7/3.
*/
public class NodeUtil {
private static final String TAG = "NodeUtil";
private static final int millis = 1000;
public static final String LIST_VIEW = "android.widget.ListView";
public static final String ABS_LIST_VIEW = "android.widget.AbsListView";
public static final String SCROLL_VIEW = "android.widget.ScrollView";
public static final String TEXT_VIEW = "android.widget.TextView";
public static final String BUTTON = "android.widget.Button";
public static final String VIEW = "android.view.View";
public static final String IMAGE_VIEW = "android.widget.ImageView";
public static final String IMAGE_BUTTON = "android.widget.ImageButton";
public static final String GRID_VIEW = "android.widget.GridView";
public static final String EDIT_TEXT = "android.widget.EditText";
public static final String RELATIVE_LAYOUT = "android.widget.RelativeLayout";
public static final String LINEAR_LAYOUT = "android.widget.LinearLayout";
public static final String LINEAR_LAYOUT_COMPAT = "android.support.v7.widget.LinearLayoutCompat";
public static void sleep(long times) {
try {
Thread.sleep(times);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static boolean checkNodeText(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return false;
}
return !StringUtil.isEmpty(nodeInfo.getText().toString());
}
public static boolean checkNodeDes(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return false;
}
return !StringUtil.isEmpty(nodeInfo.getContentDescription());
}
public static boolean performClick(AccessibilityNodeInfo node) {
AccessibilityNodeInfo clickNode = node;
if (clickNode == null) {
return false;
}
while (clickNode != null
&& !clickNode.isClickable()) {
clickNode = clickNode.getParent();
}
if (clickNode != null) {
boolean result = clickNode.performAction(AccessibilityNodeInfo.ACTION_CLICK);
sleep();
return result;
}
Log.e(TAG, "clickNode is null");
return false;
}
// root下墓臭,通過 adb 命令執(zhí)行點擊
public static boolean performShellClick(AccessibilityNodeInfo node) {
AccessibilityNodeInfo clickNode = node;
if (clickNode == null) {
return false;
}
Rect r = new Rect();
node.getBoundsInScreen(r);
if (r.centerX() > ScreenUtils.getScreenWidth(OttUtil.get().getApp())
|| r.centerY() > ScreenUtils.getScreenHeight(OttUtil.get().getApp())
|| r.centerX() <= 0
|| r.centerY() <= 0) {
return false;
}
String cmd = String.format("input tap %d %d", r.centerX(), r.centerY());
ShellUtils.CommandResult commandResult = ShellUtils.execCmd(cmd, true);
sleep();
return commandResult.result != -1;
}
// root下蘸鲸,通過 adb 命令執(zhí)行長按
public static boolean performShellLongClick(AccessibilityNodeInfo node, long time) {
AccessibilityNodeInfo clickNode = node;
if (clickNode == null) {
return false;
}
Rect r = new Rect();
clickNode.getBoundsInScreen(r);
int x = r.centerX();
int y = r.centerY();
String cmd = String.format("input swipe %d %d %d %d %d", x, y, x, y, time);
ShellUtils.CommandResult commandResult = ShellUtils.execCmd(cmd, true);
NodeUtil.sleep(time);
return commandResult.result != -1;
}
// root下,通過 adb 命令執(zhí)行滾動
public static boolean performShellScroll() {
int screenWidth = ScreenUtils.getScreenWidth(OttUtil.get().getApp());
int screenHeight = ScreenUtils.getScreenHeight(OttUtil.get().getApp());
String cmd = String.format("input swipe %d %d %d %d %d", screenWidth / 2, (int) (screenHeight * (3 * 1.0f / 5)),screenWidth / 2, screenHeight / 5, 1500L);
ShellUtils.CommandResult commandResult = ShellUtils.execCmd(cmd, true);
NodeUtil.sleep(500);
return commandResult.result != -1;
}
public static boolean performScroll(AccessibilityNodeInfo scrollerNode) {
while (scrollerNode != null && !scrollerNode.isScrollable()) {
scrollerNode = scrollerNode.getParent();
}
if (scrollerNode != null) {
boolean result = scrollerNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
sleep();
return result;
}
Log.e(TAG, "scrollerNode is null");
return false;
}
public static boolean performScrollBack(AccessibilityNodeInfo scrollerNode) {
while (scrollerNode != null && !scrollerNode.isScrollable()) {
scrollerNode = scrollerNode.getParent();
}
if (scrollerNode != null) {
boolean result = scrollerNode.performAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
sleep();
return result;
}
return false;
}
/**
* 執(zhí)行粘貼操作(注意:執(zhí)行之后窿锉,會 sleep 1s)
* @param ct
* @param node
* @param text
* @return
*/
public static boolean performPaste(Context ct, AccessibilityNodeInfo node, String text) {
if (node == null || StringUtil.isEmpty(text)) {
return false;
}
boolean result;
if (Build.VERSION.SDK_INT >= 21) {
Bundle arguments = new Bundle();
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text);
result = node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments);
sleep();
return result;
} else {
ClipboardManager cm = (ClipboardManager) ct.getSystemService(Context.CLIPBOARD_SERVICE);
ClipData mClipData = ClipData.newPlainText("text", text);
cm.setPrimaryClip(mClipData);
result = node.performAction(AccessibilityNodeInfo.ACTION_PASTE);
sleep();
return result;
}
}
public static boolean hasNode(AccessibilityNodeInfo root, String text) {
if (root == null || StringUtil.isEmpty(text)) {
return false;
}
List<AccessibilityNodeInfo> nodeList = root.findAccessibilityNodeInfosByText(text);
if (nodeList == null || nodeList.isEmpty()) {
return false;
}
return true;
}
public static AccessibilityNodeInfo findNodeByFilter(AccessibilityNodeInfo root, String text, NodeFilter filter) {
if (root == null || StringUtil.isEmpty(text)) {
return null;
}
List<AccessibilityNodeInfo> nodeList = root.findAccessibilityNodeInfosByText(text);
if (nodeList == null || nodeList.isEmpty()) {
return null;
}
AccessibilityNodeInfo clickNode = null;
for (AccessibilityNodeInfo nodeInfo : nodeList) {
if (filter.filter(nodeInfo)) {
clickNode = nodeInfo;
break;
}
}
return clickNode;
}
public static AccessibilityNodeInfo findNodeByFilter(AccessibilityNodeInfo root, NodeTextFilter filter) {
if (root == null || filter == null || StringUtil.isEmpty(filter.fiterText())) {
return null;
}
List<AccessibilityNodeInfo> nodeList = root.findAccessibilityNodeInfosByText(filter.fiterText());
if (nodeList == null || nodeList.isEmpty()) {
return null;
}
AccessibilityNodeInfo clickNode = null;
for (AccessibilityNodeInfo nodeInfo : nodeList) {
if (filter.filter(nodeInfo)) {
clickNode = nodeInfo;
break;
}
}
return clickNode;
}
public static AccessibilityNodeInfo findNodeByFilter(AccessibilityNodeInfo root, NodeIdFilter filter) {
if (root == null || filter == null || StringUtil.isEmpty(filter.fiterViewId())) {
return null;
}
List<AccessibilityNodeInfo> nodeList = root.findAccessibilityNodeInfosByViewId(filter.fiterViewId());
if (nodeList == null || nodeList.isEmpty()) {
return null;
}
AccessibilityNodeInfo clickNode = null;
for (AccessibilityNodeInfo nodeInfo : nodeList) {
if (filter.filter(nodeInfo)) {
clickNode = nodeInfo;
break;
}
}
return clickNode;
}
public static AccessibilityNodeInfo findNodeByText(AccessibilityNodeInfo root, String text) {
if (root == null || StringUtil.isEmpty(text)) {
return null;
}
List<AccessibilityNodeInfo> nodeList = root.findAccessibilityNodeInfosByText(text);
if (nodeList == null || nodeList.isEmpty()) {
return null;
}
AccessibilityNodeInfo clickNode = null;
for (AccessibilityNodeInfo nodeInfo : nodeList) {
boolean eqText = nodeInfo.getText() != null && nodeInfo.getText().toString().equals(text);
boolean eqDesc = nodeInfo.getContentDescription() != null && nodeInfo.getContentDescription().toString().equals(text);
if (eqText || eqDesc) {
clickNode = nodeInfo;
break;
}
}
return clickNode;
}
public static AccessibilityNodeInfo findNodeContainsText(AccessibilityNodeInfo root, String text) {
if (root == null || StringUtil.isEmpty(text)) {
return null;
}
List<AccessibilityNodeInfo> nodeList = root.findAccessibilityNodeInfosByText(text);
if (nodeList == null || nodeList.isEmpty()) {
return null;
}
AccessibilityNodeInfo clickNode = null;
for (AccessibilityNodeInfo nodeInfo : nodeList) {
boolean eqText = nodeInfo.getText() != null && nodeInfo.getText().toString().contains(text);
boolean eqDesc = nodeInfo.getContentDescription() != null && nodeInfo.getContentDescription().toString().contains(text);
if (eqText || eqDesc) {
clickNode = nodeInfo;
break;
}
}
return clickNode;
}
public static boolean findNodeByTextAndClick(AccessibilityNodeInfo root, String text) {
if (root == null || StringUtil.isEmpty(text)) {
return false;
}
List<AccessibilityNodeInfo> nodeList = root.findAccessibilityNodeInfosByText(text);
if (nodeList == null || nodeList.isEmpty()) {
return false;
}
AccessibilityNodeInfo clickNode = null;
for (AccessibilityNodeInfo nodeInfo : nodeList) {
boolean eqText = nodeInfo.getText() != null && nodeInfo.getText().toString().equals(text);
boolean eqDesc = nodeInfo.getContentDescription() != null && nodeInfo.getContentDescription().toString().equals(text);
if (eqText || eqDesc) {
clickNode = nodeInfo;
break;
}
}
return performClick(clickNode);
}
public static boolean findNodeByIdAndClick(AccessibilityNodeInfo root, String id) {
if (root == null || StringUtil.isEmpty(id)) {
return false;
}
List<AccessibilityNodeInfo> nodeList = root.findAccessibilityNodeInfosByViewId(id);
if (nodeList == null || nodeList.isEmpty()) {
return false;
}
return performClick(nodeList.get(0));
}
public static boolean findNodeContainsTextAndClick(AccessibilityNodeInfo root, String text) {
if (root == null || StringUtil.isEmpty(text)) {
return false;
}
List<AccessibilityNodeInfo> nodeList = root.findAccessibilityNodeInfosByText(text);
if (nodeList == null || nodeList.isEmpty()) {
return false;
}
AccessibilityNodeInfo clickNode = null;
for (AccessibilityNodeInfo nodeInfo : nodeList) {
boolean eqText = nodeInfo.getText() != null && nodeInfo.getText().toString().contains(text);
boolean eqDesc = nodeInfo.getContentDescription() != null && nodeInfo.getContentDescription().toString().contains(text);
if (eqText || eqDesc) {
clickNode = nodeInfo;
break;
}
}
Log.i(TAG, "點擊:"+text+"酌摇!");
return performClick(clickNode);
}
public static boolean findNodeByIdTextAndClick(AccessibilityNodeInfo root, String id, String text, boolean isNewPage) {
AccessibilityNodeInfo clickNode = findNodeByIdAndText(root, id, text);
if (clickNode == null) {
return false;
}
return performClick(clickNode);
}
public static boolean findNodeByIdClassAndClick(AccessibilityNodeInfo root, String id, String className, boolean isNewPage) {
AccessibilityNodeInfo clickNode = findNodeByIdAndClassName(root, id, className);
if (clickNode == null) {
return false;
}
return performClick(clickNode);
}
public static AccessibilityNodeInfo findNodeByIdAndClassName(AccessibilityNodeInfo root, String id, String className) {
if (root == null) {
return null;
}
List<AccessibilityNodeInfo> idNodeInfoList = root.findAccessibilityNodeInfosByViewId(id);
if (idNodeInfoList == null || idNodeInfoList.isEmpty()) {
return null;
}
for (int i = 0; i < idNodeInfoList.size(); i++) {
AccessibilityNodeInfo nodeInfo = idNodeInfoList.get(i);
if (nodeInfo == null) {
continue;
}
//根據(jù)className過濾
if (!StringUtil.isEmpty(className)) {
if (className.equals(nodeInfo.getClassName())) {
return nodeInfo;
}
}
}
return null;
}
public static AccessibilityNodeInfo findNodeByTextAndClass(AccessibilityNodeInfo root, String text, String clazz) {
if (root == null) {
return null;
}
List<AccessibilityNodeInfo> idNodeInfoList = root.findAccessibilityNodeInfosByText(text);
if (idNodeInfoList == null || idNodeInfoList.isEmpty()) {
return null;
}
AccessibilityNodeInfo clickNode = null;
for (int i = 0; i < idNodeInfoList.size(); i++) {
AccessibilityNodeInfo nodeInfo = idNodeInfoList.get(i);
if (nodeInfo == null) {
continue;
}
//根據(jù)class過濾
if (!StringUtil.isEmpty(clazz)) {
if (clazz.equals(nodeInfo.getClassName().toString())) {
clickNode = nodeInfo;
break;
}
}
}
if (clickNode == null) {
return null;
}
return clickNode;
}
public static AccessibilityNodeInfo findNodeByIdAndText(AccessibilityNodeInfo root, String id, String text) {
if (root == null) {
return null;
}
List<AccessibilityNodeInfo> idNodeInfoList = root.findAccessibilityNodeInfosByViewId(id);
if (idNodeInfoList == null || idNodeInfoList.isEmpty()) {
return null;
}
AccessibilityNodeInfo clickNode = null;
for (int i = 0; i < idNodeInfoList.size(); i++) {
AccessibilityNodeInfo nodeInfo = idNodeInfoList.get(i);
if (nodeInfo == null) {
continue;
}
//根據(jù)text過濾
if (!StringUtil.isEmpty(nodeInfo.getText())
&& !StringUtil.isEmpty(text)) {
if (text.equals(nodeInfo.getText())) {
clickNode = nodeInfo;
break;
}
}
}
if (clickNode == null) {
return null;
}
return clickNode;
}
/**
* @param root
* @param id
* @param className
* @return
*/
public static List<AccessibilityNodeInfo> findNodeByIdAndClassNameList(AccessibilityNodeInfo root, String id, String className) {
if (root == null) {
return null;
}
List<AccessibilityNodeInfo> resultList = new ArrayList<>();
List<AccessibilityNodeInfo> idNodeInfoList = root.findAccessibilityNodeInfosByViewId(id);
if (idNodeInfoList == null || idNodeInfoList.isEmpty()) {
return null;
}
for (int i = 0; i < idNodeInfoList.size(); i++) {
AccessibilityNodeInfo nodeInfo = idNodeInfoList.get(i);
if (nodeInfo == null) {
continue;
}
//根據(jù)className過濾
if (!StringUtil.isEmpty(className)) {
if (className.equals(nodeInfo.getClassName())) {
resultList.add(nodeInfo);
}
}
}
return resultList;
}
public static AccessibilityNodeInfo findNodeByClass(AccessibilityNodeInfo root, String className) {
if (TextUtils.isEmpty(className) || root == null) {
return null;
}
int childCount = root.getChildCount();
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo rootChild = root.getChild(i);
if (rootChild != null) {
if (className.equals(rootChild.getClassName().toString().trim())) {
return rootChild;
}
}
}
return null;
}
public static List<AccessibilityNodeInfo> findNodeByClassList(AccessibilityNodeInfo root, String className) {
List<AccessibilityNodeInfo> list = new ArrayList<>();
if (TextUtils.isEmpty(className) || root == null) {
return Collections.EMPTY_LIST;
}
int childCount = root.getChildCount();
for (int i = 0; i < childCount; i++) {
AccessibilityNodeInfo rootChild = root.getChild(i);
if (rootChild != null) {
if (className.equals(rootChild.getClassName().toString().trim())) {
list.add(rootChild);
}
}
}
return list;
}
public static List<AccessibilityNodeInfo> traverseNodefilterByDesc(AccessibilityNodeInfo root, final String desc) {
if (TextUtils.isEmpty(desc) || root == null) {
return null;
}
List<AccessibilityNodeInfo> list = new ArrayList<>();
traverseNodeClassToList(root, list, new NodeFilter() {
@Override
public boolean filter(AccessibilityNodeInfo node) {
return node.getContentDescription() != null && desc.equals(node.getContentDescription().toString());
}
});
return list;
}
public static List<AccessibilityNodeInfo> traverseNodeByClassName(AccessibilityNodeInfo root, final String className) {
if (TextUtils.isEmpty(className) || root == null) {
return Collections.EMPTY_LIST;
}
List<AccessibilityNodeInfo> list = new ArrayList<>();
traverseNodeClassToList(root, list, new NodeFilter() {
@Override
public boolean filter(AccessibilityNodeInfo node) {
return node.getClassName() != null && className.equals(node.getClassName().toString());
}
});
return list;
}
private static void traverseNodeClassToList(AccessibilityNodeInfo node, List<AccessibilityNodeInfo> list, NodeFilter filter) {
if (node == null || node.getChildCount() == 0) {
return;
}
for (int i = 0; i < node.getChildCount(); i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child != null) {
if (filter.filter(child)) {
list.add(child);
}
if (child.getChildCount() > 0) {
traverseNodeClassToList(child, list, filter);
}
}
}
}
public interface NodeFilter {
boolean filter(AccessibilityNodeInfo node);
}
public interface NodeTextFilter extends NodeFilter {
String fiterText();
}
public interface NodeIdFilter extends NodeFilter {
String fiterViewId();
}
}
在部分 Root 的手機,我們可以通過輔助權限找到該節(jié)點在屏幕上的位置嗡载,并獲取中心點:
Rect r = new Rect();
node.getBoundsInScreen(r);
if (r.centerX() > ScreenUtils.getScreenWidth(OttUtil.get().getApp())
|| r.centerY() > ScreenUtils.getScreenHeight(OttUtil.get().getApp())
|| r.centerX() <= 0
|| r.centerY() <= 0) {
return false;
}
String cmd = String.format("input tap %d %d", r.centerX(), r.centerY());
再去執(zhí)行相關操作:
ShellUtils.CommandResult commandResult = ShellUtils.execCmd(cmd, true);