2.3版本下载
本软件仅供个人学习研究使用,严禁倒卖
最初,我有一个朋友问我能不能有办法监听到另一个App界面的内容,一旦有特定的消息出现就提醒用户,就这样,我接触到了AccessibityService。这个项目很好写,所以很快就实现了。主要是那个app结点都能直接获取到,也都能点击。有一天,我收蚂蚁森林能量,好友比较多,就在想,我能不能写一个基于AccessibilityService的自动收集能量的应用,造福一下懒人
APP已放在末尾,欢迎大家测试
AccessibilityService + 多线程 + 手势
实现逻辑
说明
所有判断页面是否加载完成,用户名是否匹配的操作都是在开辟的子线程(图中橙色部分)中进行,主线程的MainHandler 接收来自子线程发送的消息,进行处理。
一、怎么获得webView中的结点信息
二、怎么判断webView加载完成了
三、怎么点击按钮
第一个问题,webView就像浏览器一样,里面的内容是网页的内容。
在webView中,所有的通过搜索获取结点List的方法失效了,但是可以通过不停地得到子节点、判断、得到子节点、判断的方法粗略的得到结点信息。
可以借助安卓的 uiautomatorviewer 工具查看结点树,但是这里的结点树也可能和实际运行的有点点出入,主要是webview 加载成功和不成功 之间有些变化,其他的结点结构是固定的,所以可取。
我是用dfs的方式得到webView的结点
private AccessibilityNodeInfo returnWebView(AccessibilityNodeInfo nowNode){
if(nowNode==null) return null;
if(nowNode.getClassName().toString().equals("android.webkit.WebView")){
return nowNode;
}
if(nowNode.getChildCount()==0) return null;
int size = nowNode.getChildCount();
AccessibilityNodeInfo webViewNode = null;
for(int i=0;i<size;i++){
webViewNode = returnWebView(nowNode.getChild(i));
if(webViewNode!=null) return webViewNode;
}
return null;
}
UIautomatorviewer具体怎么使用网上有很多博客,就不说了
第二个问题,怎么判断webView是否加载完成。
老实说,没有特别好的办法。你看一般的浏览器加载网页,有些时候加载的快,有些时候加载得慢,网页上很多东西的加载都是异步的。我很难准确的找到一个判定条件说网页绝对加载完成了。我用的办法是子线程判断关键信息是否加载完成了,只要我要点击的按钮加载完成了,就判定webView加载完成了。
private void LoadingForest(){
//判定已载入的规则:只包含能量的view节点子孩子数大于等于4
new Thread(new Runnable() {
@Override
public void run() {
debug("加载蚂蚁森林...");
Message message = Message.obtain();
int MaxCount = MAX_REQUEST_TIME;
WebViewNode = null;
while(MaxCount > 0 ){
MaxCount--;
sleep(500);
rootNode = getRootInActiveWindow();//Frame
if(rootNode==null || rootNode.getChildCount()==0) continue;
WebViewNode = returnWebView(rootNode);
if(WebViewNode==null || WebViewNode.getChildCount()==0) continue;
nowNode = WebViewNode.getChild(0);
if(nowNode==null || nowNode.getChildCount()==0) continue;
nowNode = nowNode.getChild(0);
if (nowNode==null || nowNode.getChildCount()<3) continue;
nowNode = nowNode.getChild(2);
// dfs(nowNode);
if(nowNode.getChildCount()>=4) break;
}
if(MaxCount==0){
message.what = -1;
}else{
message.what = 6;
}
MainHandler.sendMessage(message);
}
}).start();
}
第三个问题 ,怎么点击
通过手势,可以实现滑动 和点击
滑动
private void scroll(){
new Thread(new Runnable() {
@Override
public void run() {
int sx = (nowUserEntrance.left+nowUserEntrance.right)>>1;
int sy = nowUserEntrance.bottom;
int ey = nowUserEntrance.top-21;
dispatchGestureScroll(2,sx,sy,sx,ey);
sleep(100);
Message message = Message.obtain();
message.what = 10;
MainHandler.sendMessage(message);
}
}).start();
}
private void dispatchGestureScroll(final int flag, int sx, int sy,int ex,int ey) {
// debug("sx:"+sx+"sy:"+sy+"ex:"+ex+"ey:"+ey);
GestureDescription.Builder builder = new GestureDescription.Builder();
Path p = new Path();
p.moveTo(sx, sy);
p.lineTo(ex, ey);
builder.addStroke(new GestureDescription.StrokeDescription(p, 0L, 100L));
GestureDescription gesture = builder.build();
dispatchGesture(gesture, new AccessibilityService.GestureResultCallback() {
@Override
public void onCompleted(GestureDescription gestureDescription) {
super.onCompleted(gestureDescription);
Log.d(TAG, flag+"onCompleted: 完成..........");
}
@Override
public void onCancelled(GestureDescription gestureDescription) {
super.onCancelled(gestureDescription);
Log.d(TAG, flag+"onCompleted: 取消..........");
}
}, null);
}
点击
/**
* 模拟点击事件
* @param flag 点击的控件类型(1 能量) (0 用户入口)
* @param x 横坐标
* @param y 纵坐标
*/
private boolean dispatchGestureView(final int flag, int x, int y) {
boolean res = false;
GestureDescription.Builder builder = new GestureDescription.Builder();
Path p = new Path();
p.moveTo(x, y);
p.lineTo(x, y);
builder.addStroke(new GestureDescription.StrokeDescription(p, 0L, 100L));
GestureDescription gesture = builder.build();
Log.d("","点击了位置"+"("+x+","+y+")");
sleep(200);
res = dispatchGesture(gesture, new GestureResultCallback(){}, null);
return res;
}
为什么不直接用
来点击呢。呵,最初我就是这么做的,而且UIautomatorviewer也显示该按钮的clickable是true 但是就是点不了呀,后来研究了一下,发现安卓的按钮还有
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
//点击事件
return false;
}
});
来实现点击事件,注意这里的return false ,返回false的时候,点击按钮onClick和onTouch 都会执行,而当返回true的时候,只有onTouch会执行。所以我打算用OnTouch来实现点击事件,蚂蚁森林能给我的只有按钮的坐标,通过给指定坐标的View分发TouchEvent
/**
* 模拟点击事件
* @param x 横坐标
* @param y 纵坐标
*/
private void onTouchClick(int x,int y){
final long downTime = SystemClock.uptimeMillis();
View b1 = findViewById(R.id.button);
Log.d("","onTouchClick");
MotionEvent downEvent = MotionEvent.obtain(downTime,downTime, MotionEvent.ACTION_DOWN,x,y,0);
MotionEvent upEvent = MotionEvent.obtain(downTime,SystemClock.uptimeMillis(), MotionEvent.ACTION_UP,x,y,0);
b1.dispatchTouchEvent(downEvent);
b1.dispatchTouchEvent(upEvent);
downEvent.recycle();
upEvent.recycle();
}
理论上可以通过坐标来获取view,网上有方法。但是我试了很多办法也获取不到第三方的AppDecorView
,因为连第三方的Activity
实例都获取不到。我当时卡了好久,差点想放弃这个项目,因为连点击都不能实现还怎么收能量。。
幸好,后来发现了手势也可以实现点击,才使得前面的努力没有白费。
想要了解更多,您也可以看看续篇
蚂蚁森林自动收集能量(续)