前兩天公司開始宣布要使用阿里釘釘來簽到啦?。。~這就意味著,我必須老老實實每天按時簽到上班下班了,這真是一個悲傷的消息,可是?。。。∧敲礄C(jī)智(lan)的我,怎么可能就這么屈服!??!阿里釘釘簽到,說到底不就是手機(jī)軟件簽到嗎?我就是干移動開發(fā)的,做一個小應(yīng)用每天自動簽到不就行了:)
說干就干,首先分析一下,阿里釘釘?shù)暮灥搅鞒蹋?br /> 打開阿里釘釘->廣告頁停留2S左右->進(jìn)入主頁->點擊“工作”tab->點擊“簽到”模塊->進(jìn)入簽到頁面(可能會再次出現(xiàn)廣告和對話框)->點擊簽到
我們操作手機(jī)的過程就是這樣,要實現(xiàn)這些點擊,很自然想起了前段時間做的微信搶紅包小應(yīng)用,利用AccessibilityService服務(wù)幫助我們實現(xiàn)這些自動化操作。
以上是分析過程,接下來是我對這個小功能實現(xiàn)的具體方案思路:
將測試手機(jī)放公司并且安裝這個應(yīng)用,通過我遠(yuǎn)程的電話撥打或者短信發(fā)送到測試手機(jī)(只要能產(chǎn)生廣播或者信息的就行),測試手機(jī)接受到廣播信息,喚醒釘釘,進(jìn)入釘釘頁面,AccessibilityService開始工作,進(jìn)行一系列點擊簽到操作,結(jié)束操作后退出釘釘,簽到完成。
通過以上過程的分析我們大概要用到的知識有以下幾塊:
1. 喚醒非自己的其他第三方應(yīng)用
2. 廣播
3. AccessibilityService服務(wù)
以下是對這三部分代碼實現(xiàn):
喚醒第三方應(yīng)用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
package net.fenzz.dingplug; import java.util.List; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; public class Utils { public static void openCLD(String packageName,Context context) { PackageManager packageManager = context.getPackageManager(); PackageInfo pi = null ; try { pi = packageManager.getPackageInfo( "com.alibaba.android.rimet" , 0 ); } catch (NameNotFoundException e) { } Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null ); resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER); resolveIntent.setPackage(pi.packageName); List<ResolveInfo> apps = packageManager.queryIntentActivities(resolveIntent, 0 ); ResolveInfo ri = apps.iterator().next(); if (ri != null ) { String className = ri.activityInfo.name; Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); ComponentName cn = new ComponentName(packageName, className); intent.setComponent(cn); context.startActivity(intent); } } } |
接受電話廣播并且喚醒釘釘:
mainifest先注冊監(jiān)聽器
1
2
3
4
5
6
|
<!-- 注冊監(jiān)聽手機(jī)狀態(tài) --> < receiver android:name = ".PhoneReceiver" > < intent-filter android:priority = "1000" > < action android:name = "android.intent.action.PHONE_STATE" /> </ intent-filter > </ receiver > |
相關(guān)權(quán)限
1
2
3
|
<!-- 讀取手機(jī)狀態(tài)的權(quán)限 --> < uses-permission android:name = "android.permission.READ_PHONE_STATE" /> < uses-permission android:name = "android.permission.PROCESS_OUTGOING_CALLS" /> |
代碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
package net.fenzz.dingplug; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.telephony.TelephonyManager; import android.app.Service; import android.util.Log; public class PhoneReceiver extends BroadcastReceiver { private static final String TAG = "message" ; private static boolean mIncomingFlag = false ; private static String mIncomingNumber = null ; @Override public void onReceive(Context context, Intent intent) { // 如果是撥打電話 if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) { mIncomingFlag = false ; String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER); Log.i(TAG, "call OUT:" + phoneNumber); } else { // 如果是來電 TelephonyManager tManager = (TelephonyManager) context .getSystemService(Service.TELEPHONY_SERVICE); switch (tManager.getCallState()) { case TelephonyManager.CALL_STATE_RINGING: mIncomingNumber = intent.getStringExtra( "incoming_number" ); Log.i(TAG, "RINGING :" + mIncomingNumber); if (mIncomingNumber!= null &&mIncomingNumber.equals(你的手機(jī)號)){ Utils.openCLD( "com.alibaba.android.rimet" , context); DingService.instance.setServiceEnable(); } break ; case TelephonyManager.CALL_STATE_OFFHOOK: if (mIncomingFlag) { Log.i(TAG, "incoming ACCEPT :" + mIncomingNumber); } break ; case TelephonyManager.CALL_STATE_IDLE: if (mIncomingFlag) { Log.i(TAG, "incoming IDLE" ); } break ; } } } } |
AccessibilityService服務(wù)實現(xiàn):
相關(guān)權(quán)限及注冊:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
< uses-permission android:name = "android.permission.BIND_ACCESSIBILITY_SERVICE" /> < service android:name = ".DingService" android:enabled = "true" android:exported = "true" 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/red_service_config" /> </ service > |
需要在res文件夾下新建一個xml文件夾里面放入一個這樣的xml配置文件:
1
2
3
4
5
6
7
8
9
10
|
<? xml version = "1.0" encoding = "utf-8" ?> < accessibility-service xmlns:android = "http://schemas.android.com/apk/res/android" android:accessibilityEventTypes = "typeAllMask" android:accessibilityFeedbackType = "feedbackGeneric" android:accessibilityFlags = "flagDefault" android:canRetrieveWindowContent = "true" android:description = "@string/accessibility_description" android:notificationTimeout = "100" android:packageNames = "com.alibaba.android.rimet" /> |
代碼:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
|
package net.fenzz.dingplug; import java.util.ArrayList; import java.util.List; import android.accessibilityservice.AccessibilityService; import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Toast; public class DingService extends AccessibilityService { private String TAG = getClass().getSimpleName(); private boolean isFinish = false ; public static DingService instance; private int index = 1 ; /** * 獲取到短信通知 * 0.喚醒屏幕 * 1.打開釘釘 * 2.確保當(dāng)前頁是主頁界面 * 3.找到“工作”tab并且點擊 * 4.確保到達(dá)簽到頁面 * 5.找到簽到按鈕,并且點擊 * 6.判斷簽到是否成功 * 1.成功,退出程序 * 2.失敗,返回到主頁,重新從1開始簽到 */ @Override public void onAccessibilityEvent(AccessibilityEvent event) { // TODO Auto-generated method stub // final int eventType = event.getEventType(); ArrayList<String> texts = new ArrayList<String>(); Log.i(TAG, "事件---->" + event.getEventType()); if (isFinish){ return ; } AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); if (nodeInfo == null ) { Log.w(TAG, "rootWindow為空" ); return ; } // nodeInfo. // System.out.println("nodeInfo"+nodeInfo); System.out.println( "index:" +index); switch (index) { case 1 : //進(jìn)入主頁 OpenHome(event.getEventType(),nodeInfo); break ; case 2 : //進(jìn)入簽到頁 OpenQianDao(event.getEventType(),nodeInfo); break ; case 3 : doQianDao(event.getEventType(),nodeInfo); break ; default : break ; } } private ArrayList<String> getTextList(AccessibilityNodeInfo node,ArrayList<String> textList){ if (node == null ) { Log.w(TAG, "rootWindow為空" ); return null ; } if (textList== null ){ textList = new ArrayList<String>(); } String text = node.getText().toString(); if (text!= null &&text.equals( "" )){ textList.add(text); } // node.get return null ; } private void OpenHome( int type,AccessibilityNodeInfo nodeInfo) { if (type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){ //判斷當(dāng)前是否是釘釘主頁 List<AccessibilityNodeInfo> homeList = nodeInfo.findAccessibilityNodeInfosByText( "工作" ); if (!homeList.isEmpty()){ //點擊 boolean isHome = click( "工作" ); System.out.println( "---->" +isHome); index = 2 ; System.out.println( "點擊進(jìn)入主頁簽到" ); } } } private void OpenQianDao( int type,AccessibilityNodeInfo nodeInfo) { if (type == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED){ //判斷當(dāng)前是否是主頁的簽到頁 List<AccessibilityNodeInfo> qianList = nodeInfo.findAccessibilityNodeInfosByText( "工作" ); if (!qianList.isEmpty()){ boolean ret = click( "簽到" ); index = 3 ; System.out.println( "點擊進(jìn)入簽到頁面詳情" ); } // index = ret?3:1; } } private void doQianDao( int type,AccessibilityNodeInfo nodeInfo) { if (type == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED){ //判斷當(dāng)前頁是否是簽到頁 List<AccessibilityNodeInfo> case1 = nodeInfo.findAccessibilityNodeInfosByText( "開啟我的簽到之旅" ); if (!case1.isEmpty()){ click( "開啟我的簽到之旅" ); System.out.println( "點擊簽到之旅" ); } List<AccessibilityNodeInfo> case2 = nodeInfo.findAccessibilityNodeInfosByText( "我知道了" ); if (!case2.isEmpty()){ click( "我知道了" ); System.out.println( "點擊我知道對話框" ); } List<AccessibilityNodeInfo> case3 = nodeInfo.findAccessibilityNodeInfosByText( "簽到" ); if (!case3.isEmpty()){ Toast.makeText(getApplicationContext(), "發(fā)現(xiàn)目標(biāo)啦??!~~" , 1 ).show(); System.out.println( "發(fā)現(xiàn)目標(biāo)啦!" ); click( "簽到" ); isFinish = true ; } } // if(type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){ // List<AccessibilityNodeInfo> case3 = nodeInfo.findAccessibilityNodeInfosByText("簽到"); // if(!case3.isEmpty()){ // Toast.makeText(getApplicationContext(), "發(fā)現(xiàn)目標(biāo)啦!!~~", 1).show(); // } // } } //通過文字點擊 private boolean click(String viewText){ AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); if (nodeInfo == null ) { Log.w(TAG, "點擊失敗,rootWindow為空" ); return false ; } List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(viewText); if (list.isEmpty()){ //沒有該文字的控件 Log.w(TAG, "點擊失敗," +viewText+ "控件列表為空" ); return false ; } else { //有該控件 //找到可點擊的父控件 AccessibilityNodeInfo view = list.get( 0 ); return onclick(view); //遍歷點擊 } } private boolean onclick(AccessibilityNodeInfo view){ if (view.isClickable()){ view.performAction(AccessibilityNodeInfo.ACTION_CLICK); Log.w(TAG, "點擊成功" ); return true ; } else { AccessibilityNodeInfo parent = view.getParent(); if (parent== null ){ return false ; } onclick(parent); } return false ; } //點擊返回按鈕事件 private void back(){ performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); } @Override public void onInterrupt() { // TODO Auto-generated method stub } @Override protected void onServiceConnected() { // TODO Auto-generated method stub super .onServiceConnected(); Log.i(TAG, "service connected!" ); Toast.makeText(getApplicationContext(), "連接成功!" , 1 ).show(); instance = this ; } public void setServiceEnable(){ isFinish = false ; Toast.makeText(getApplicationContext(), "服務(wù)可用開啟!" , 1 ).show(); index = 1 ; } } |
以上基本是所有代碼,這個小程序中可以不用Activity組件,也可以加一個小的Activity,用來作為系統(tǒng)的總開關(guān),當(dāng)然也可以自動檢測時間,來判斷是否開啟服務(wù),這樣就不用Activity了,在這個小例子中,我使用了一個小activity,就放了一個button。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持服務(wù)器之家。
原文鏈接:https://blog.csdn.net/fenzzz/article/details/50156697