Android基于AccessibilityService制作的釘釘自動簽到程序代碼
前兩天公司開始宣布要使用阿里釘釘來簽到啦?。?!~~這就意味著,我必須老老實實每天按時簽到上班下班了,這真是一個悲傷的消息,可是!?。?!那么機智(lan)的我,怎么可能就這么屈服?。?!阿里釘釘簽到,說到底不就是手機軟件簽到嗎?我就是干移動開發(fā)的,做一個小應(yīng)用每天自動簽到不就行了:)
說干就干,首先分析一下,阿里釘釘?shù)暮灥搅鞒蹋?
打開阿里釘釘->廣告頁停留2S左右->進入主頁->點擊“工作”tab->點擊“簽到”模塊->進入簽到頁面(可能會再次出現(xiàn)廣告和對話框)->點擊簽到
我們操作手機的過程就是這樣,要實現(xiàn)這些點擊,很自然想起了前段時間做的微信搶紅包小應(yīng)用,利用AccessibilityService服務(wù)幫助我們實現(xiàn)這些自動化操作。
以上是分析過程,接下來是我對這個小功能實現(xiàn)的具體方案思路:
將測試手機放公司并且安裝這個應(yīng)用,通過我遠程的電話撥打或者短信發(fā)送到測試手機(只要能產(chǎn)生廣播或者信息的就行),測試手機接受到廣播信息,喚醒釘釘,進入釘釘頁面,AccessibilityService開始工作,進行一系列點擊簽到操作,結(jié)束操作后退出釘釘,簽到完成。
通過以上過程的分析我們大概要用到的知識有以下幾塊:
1. 喚醒非自己的其他第三方應(yīng)用
2. 廣播
3. AccessibilityService服務(wù)
以下是對這三部分代碼實現(xiàn):
喚醒第三方應(yīng)用
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)聽器
<!-- 注冊監(jiān)聽手機狀態(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)限
<!-- 讀取手機狀態(tài)的權(quán)限 --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
代碼
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(你的手機號)){ 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)限及注冊:
<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配置文件:
<?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" />
代碼:
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.確保到達簽到頁面 * 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: //進入主頁 OpenHome(event.getEventType(),nodeInfo); break; case 2: //進入簽到頁 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("點擊進入主頁簽到"); } } } 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("點擊進入簽到頁面詳情"); } // 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)目標啦!!~~", 1).show(); System.out.println("發(fā)現(xiàn)目標啦!"); click("簽到"); isFinish = true; } } // if(type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){ // List<AccessibilityNodeInfo> case3 = nodeInfo.findAccessibilityNodeInfosByText("簽到"); // if(!case3.isEmpty()){ // Toast.makeText(getApplicationContext(), "發(fā)現(xiàn)目標啦!!~~", 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í)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android組件TabHost實現(xiàn)頁面中多個選項卡切換效果
這篇文章主要為大家詳細介紹了Android組件TabHost實現(xiàn)頁面中多個選項卡切換效果的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-05-05Android開發(fā)手冊Chip監(jiān)聽及ChipGroup監(jiān)聽
這篇文章主要為大家介紹了Android開發(fā)手冊Chip監(jiān)聽及ChipGroup監(jiān)聽,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-06-06Android開發(fā)中的文件操作工具類FileUtil完整實例
這篇文章主要介紹了Android開發(fā)中的文件操作工具類FileUtil,結(jié)合完整實例形式分析了Android文件操作的常用技巧,包括文件的獲取、遍歷、搜索、復(fù)制、刪除、判斷等功能,需要的朋友可以參考下2017-11-11Android學(xué)習(xí)之Flux架構(gòu)入門
Flux是Facebook在14年提出的一種Web前端架構(gòu),主要用來處理復(fù)雜的UI邏輯的一致性問題(當(dāng)時是為了解決Web頁面的消息通知問題)。接下來從其特點和使用上來介紹Flux架構(gòu)。本文主要目的是讓你對Flux的一個架構(gòu)大體面貌有個了解。2016-08-08Flutter持久化存儲之?dāng)?shù)據(jù)庫存儲(sqflite)詳解
這篇文章主要給大家介紹了關(guān)于Flutter持久化存儲之?dāng)?shù)據(jù)庫存儲的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03