第一節 緣由與准備
最近有時間空閑,閑來無事,想到使用釘釘打卡有時會遲到,所以周末的時候去看了相關網上資料,做了個demo。
材料:定時器,AccessibilityService
加工方案:使用定時器在簽到簽退期間內自啟,通過AccessibilityService模擬點擊:分為簽到與簽退兩種情況。
簽到正常流程:工作-》考勤打卡-》(判斷是否彈出窗口-是:我知道了否跳過)-》簽到。
簽到遲到流程:工作-》考勤打卡-》遲到打卡。
簽退正常流程:工作-》考勤打卡-》簽退。
工藝難點:簽到頁中嵌套的是基於WebView的頁面,一開始以為無法獲取節點,想到通過屏幕中的位置去點擊那塊區域,查看官方文檔發現有個方法getAccessibilityNodeProvider(),得到虛擬節點進行模擬點擊。
第二節:熱火朝天
技能點:判斷應用狀態,啟動指定應用,自定義AccessibilityService控制模擬點擊流程
判斷應用狀態:
public static boolean isBackground(Context context) {
ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
if (appProcess.processName.equals(context.getPackageName())) {
if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND) {
Log.i("后台", appProcess.processName);
return true;
}else{
Log.i("前台", appProcess.processName);
return false;
}
}
}
return false;
}
啟動指定應用:
public static void doStartApplicationWithPackageName(Context context, String packagename) {
// 通過包名獲取此APP詳細信息,包括Activities、services、versioncode、name等等
PackageInfo packageinfo = null;
try {
packageinfo = context.getPackageManager().getPackageInfo(packagename, 0);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (packageinfo == null) {
return;
}
// 創建一個類別為CATEGORY_LAUNCHER的該包名的Intent
Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null);
resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER);
resolveIntent.setPackage(packageinfo.packageName);
// 通過getPackageManager()的queryIntentActivities方法遍歷
List<ResolveInfo> resolveinfoList = context.getPackageManager()
.queryIntentActivities(resolveIntent, 0);
ResolveInfo resolveinfo = resolveinfoList.iterator().next();
if (resolveinfo != null) {
// packagename = 參數packname
String packageName = resolveinfo.activityInfo.packageName;
// 這個就是我們要找的該APP的LAUNCHER的Activity[組織形式:packagename.mainActivityname]
String className = resolveinfo.activityInfo.name;
// LAUNCHER Intent
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
// 設置ComponentName參數1:packagename參數2:MainActivity路徑
ComponentName cn = new ComponentName(packageName, className);
intent.setComponent(cn);
context.startActivity(intent);
}
}
自定義AccessibilityService控制模擬點擊流程:
1.獲取點擊控件對象(可以通過文本不推薦通過資源id方式點擊)
工作布局的資源ID:

考勤打卡布局的資源ID(這個id是動態生成的8個都是):

考勤打卡布局的資源ID:

2窗口發生變化處理:
@Override
public void onAccessibilityEvent(AccessibilityEvent accessibilityEvent) {
if(isFinish){
return;
}
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if(nodeInfo == null) {
Log.d(TAG, "rootWindow為空");
return ;
}
switch (accessibilityEvent.getEventType()){
//當窗口的狀態發生改變時
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
if (index==1) {
//主頁點擊工作
clickHome(nodeInfo,accessibilityEvent);
Log.d(TAG,"工作");
} else if (index==2) {
//工作tab點擊考勤打卡
clickKaoQing(nodeInfo,accessibilityEvent);
Log.d(TAG,"點擊考勤打卡");
} else if (index==3) {
//開始打卡
clickKaoQingBtn(nodeInfo,accessibilityEvent);
Log.d(TAG,"打卡");
}
break;
}
}
3后續處理:
發現考勤打卡頁面是基於webview的h5頁面,因此暫時沒有好的方法,獲取webview對象,以及獲取虛擬節點。
不過如果可以獲取到窗口下的webview對象,那么是可以獲取頁面的虛擬節點,進行模擬點擊。打卡是沒問題的,由於現在極速打卡的功能,打開應用自動簽到。
參考:
>Android WebView官方文檔
>Android AccessibilityNodeProvider官方文檔
>基於AccessibilityService制作的釘釘自動簽到程序