這篇文章主要介紹Android輔助功如何實現自動搶紅包,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
一、描述
最近看到同事有用搶紅包的軟件,就想看看搶紅包的具體實現是如何的,所以了解了一下,有用輔助功能實現的,所以在下面的示例中會展示一個搶紅包的小Demo,附帶源碼搶紅包源碼。
二、效果圖
在桌面收到紅包進行搶
在聊天頁面收到口令紅包
三、AccessibilityService使用
創建輔助服務類,繼承AccessibilityService,實現兩個接口,接收系統的事件
public class MyService extends AccessibilityService { @Override public void onAccessibilityEvent(AccessibilityEvent event) { } @Override public void onInterrupt() { } }
輔助服務的配置文件,配置事件,在 res/xml下創建accessibility_service_info.xml
//具體屬性的說明在第5點有說明 <?xml version="1.0" encoding="utf-8"?> <accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_description" android:accessibilityEventTypes="typeAllMask" android:accessibilityFeedbackType="feedbackGeneric" android:notificationTimeout="100" android:accessibilityFlags="flagDefault" android:canRetrieveWindowContent="true" android:packageNames="top.cokernut.sample" android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity" />
注冊Service輔助服務,并且為Service附加上第二步創建的xml,看清除下面的一些屬性,必須要加,如果有的沒加的話是沒效果的
<service android:name=".MyService" android:label="輔助功能" 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/accessibility_service_info" /> </service>
4 清單文件中添加權限
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
輔助服務配置文件xml屬性說明:
//是否可以檢索整個層級下的內容 android:canRetrieveWindowContent="true"級下的信息 //事件通知觸發點,比如窗口打開,滑動,焦點變化,長按等。 android:accessibilityEventTypes="typeAllMask" #TYPES_ALL_MASK:所有類型 #TYPE_VIEW_CLICKED :單擊 #TYPE_VIEW_LONG_CLICKED :長按 #TYPE_VIEW_SELECTED :選中 #TYPE_VIEW_FOCUSED :獲取焦點 #TYPE_VIEW_TEXT_CHANGED :文字改變 #TYPE_WINDOW_STATE_CHANGED :窗口狀態改變 //表示反饋方式,比如是語音播放,還是震動 android:accessibilityFeedbackType="feedbackGeneric" //接受事件的時間間隔,通常將其設置為100即可. android:notificationTimeout="100" //表示該服務是用來單獨監聽哪個應用的產生的事件,其他的都會過濾,如果不填就是對所有的應用進行監聽,填入包名即可。 android:packageNames="top.cokernut.sample" //在代碼中我們就可以通過node節點來getViewIdResourceName()獲取對應的節點的id android:accessibilityFlags="flagDefault"
提供一個AccessibilityService的基類,集成了一些常用方法:
public class BaseAccessibilityService extends AccessibilityService { private AccessibilityManager mAccessibilityManager; private Context mContext; private static BaseAccessibilityService mInstance; public void init(Context context) { mContext = context.getApplicationContext(); mAccessibilityManager = (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); } public static BaseAccessibilityService getInstance() { if (mInstance == null) { mInstance = new BaseAccessibilityService(); } return mInstance; } /** * Check當前輔助服務是否啟用 * * @param serviceName serviceName * @return 是否啟用 */ private boolean checkAccessibilityEnabled(String serviceName) { List<AccessibilityServiceInfo> accessibilityServices = mAccessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC); for (AccessibilityServiceInfo info : accessibilityServices) { if (info.getId().equals(serviceName)) { return true; } } return false; } /** * 前往開啟輔助服務界面 */ public void goAccess() { Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); } /** * 模擬點擊事件 * * @param nodeInfo nodeInfo */ public void performViewClick(AccessibilityNodeInfo nodeInfo) { if (nodeInfo == null) { return; } while (nodeInfo != null) { if (nodeInfo.isClickable()) { nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK); break; } nodeInfo = nodeInfo.getParent(); } } /** * 模擬返回操作 */ public void performBackClick() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } performGlobalAction(GLOBAL_ACTION_BACK); } /** * 模擬下滑操作 */ public void performScrollBackward() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD); } /** * 模擬上滑操作 */ public void performScrollForward() { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } performGlobalAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD); } /** * 查找對應文本的View * * @param text text * @return View */ public AccessibilityNodeInfo findViewByText(String text) { return findViewByText(text, false); } /** * 查找對應文本的View * * @param text text * @param clickable 該View是否可以點擊 * @return View */ public AccessibilityNodeInfo findViewByText(String text, boolean clickable) { AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow(); if (accessibilityNodeInfo == null) { return null; } List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text); if (nodeInfoList != null && !nodeInfoList.isEmpty()) { for (AccessibilityNodeInfo nodeInfo : nodeInfoList) { if (nodeInfo != null && (nodeInfo.isClickable() == clickable)) { return nodeInfo; } } } return null; } /** * 查找對應ID的View * * @param id id * @return View */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public AccessibilityNodeInfo findViewByID(String id) { AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow(); if (accessibilityNodeInfo == null) { return null; } List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id); if (nodeInfoList != null && !nodeInfoList.isEmpty()) { for (AccessibilityNodeInfo nodeInfo : nodeInfoList) { if (nodeInfo != null) { return nodeInfo; } } } return null; } public void clickTextViewByText(String text) { AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow(); if (accessibilityNodeInfo == null) { return; } List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text); if (nodeInfoList != null && !nodeInfoList.isEmpty()) { for (AccessibilityNodeInfo nodeInfo : nodeInfoList) { if (nodeInfo != null) { performViewClick(nodeInfo); break; } } } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) public void clickTextViewByID(String id) { AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow(); if (accessibilityNodeInfo == null) { return; } List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByViewId(id); if (nodeInfoList != null && !nodeInfoList.isEmpty()) { for (AccessibilityNodeInfo nodeInfo : nodeInfoList) { if (nodeInfo != null) { performViewClick(nodeInfo); break; } } } } /** * 模擬輸入 * * @param nodeInfo nodeInfo * @param text text */ public void inputText(AccessibilityNodeInfo nodeInfo, String text) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Bundle arguments = new Bundle(); arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, text); nodeInfo.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("label", text); clipboard.setPrimaryClip(clip); nodeInfo.performAction(AccessibilityNodeInfo.ACTION_FOCUS); nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE); } } @Override public void onAccessibilityEvent(AccessibilityEvent event) { } @Override public void onInterrupt() { } }
四、QQ搶紅包
(一)搶紅包流程:
通知欄收到QQ的消息,發現是QQ紅包,模擬點擊消息進入聊天頁面
檢索頁面上的所有元素,發現有包含“點擊拆開”的字眼,就模擬點擊打開紅包窗口
一兩秒后執行Back操作,關閉紅包窗口。
繼續等待消息來到。
(二)實現功能:
鎖屏搶紅包(不可以有密碼或者圖案之類的鎖屏)
口令紅包,自動輸入口令并且發送
搶完紅包后,自動回復感謝語,可在紅包設置里自行設置內容
其他的功能就沒繼續往下做了,知道方法,其他都可能慢慢研究出來。
(三)搶紅包輔助功能類,注釋都寫好了,很好理解,類中有用到QQConstant類,在第四點貼出了代碼
/** * 描述:QQ搶紅包服務 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/6 上午9:25 */ public class EnvelopeService extends BaseAccessibilityService { //鎖屏、解鎖相關 private KeyguardManager.KeyguardLock kl; //喚醒屏幕相關 private PowerManager.WakeLock wl = null; private long delayTime = 0;//延遲搶的時間 /** * 描述:所有事件響應的時候會回調 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/6 上午9:26 */ @Override public void onAccessibilityEvent(AccessibilityEvent event) { //驗證搶紅包的開關 if (!invalidEnable()) { return; } //事件類型 int eventType = event.getEventType(); //獲取包名 CharSequence packageName = event.getPackageName(); if (TextUtils.isEmpty(packageName)) { return; } switch (eventType) { //狀態欄變化 case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: if (QQConstant.QQ_PACKAGE_NAME.equals(packageName)) { //處理狀態欄上QQ的消息,如果是紅包就跳轉過去 progressQQStatusBar(event); } break; //窗口切換的時候回調 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED: if (QQConstant.QQ_PACKAGE_NAME.equals(packageName)) { //處理正在QQ聊天窗口頁面,有其他群或者人有新的紅包提醒,跳轉過去。 progressNewMessage(event); //處理聊天頁面的紅包 progressQQChat(event); } break; } } /** * 描述:處理新消息 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/3 下午11:21 */ private void progressNewMessage(AccessibilityEvent event) { if (event == null) { return; } AccessibilityNodeInfo source = event.getSource(); if (source == null) { return; } //根據event的source里的text,來判斷這個消息是否包含[QQ紅包]的字眼,有的話就跳轉過去 CharSequence text = source.getText(); if (!TextUtils.isEmpty(text) && text.toString().contains(QQConstant.QQ_ENVELOPE_KEYWORD)) { performViewClick(source); } } /** * 描述:驗證搶紅包是否開啟 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/3 下午4:57 */ private boolean invalidEnable() { return SettingConfig.getInstance().getReEnable(); } /** * 描述:處理QQ狀態欄 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/1 下午1:49 */ public void progressQQStatusBar(AccessibilityEvent event) { List<CharSequence> text = event.getText(); //開始檢索界面上是否有QQ紅包的文本,并且他是通知欄的信息 if (text != null && text.size() > 0) { for (CharSequence charSequence : text) { if (charSequence.toString().contains(QQConstant.QQ_ENVELOPE_KEYWORD)) { //說明存在紅包彈窗,馬上進去 if (event.getParcelableData() != null && event.getParcelableData() instanceof Notification) { Notification notification = (Notification) event.getParcelableData(); if (notification == null) { return; } PendingIntent pendingIntent = notification.contentIntent; if (pendingIntent == null) { return; } try { //要跳轉之前,先進行解鎖屏幕,然后再跳轉,有可能你現在屏幕是鎖屏狀態,先進行解鎖,然后打開頁面,有密碼的可能就不行了 wakeUpAndUnlock(MyApp.context); //跳轉 pendingIntent.send(); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); } } } } } } /** * 描述:處理QQ聊天紅包 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/1 下午1:56 */ public void progressQQChat(AccessibilityEvent event) { if (TextUtils.isEmpty(event.getClassName())) { return; //如果當前頁面是聊天頁面或者當前的描述信息是"返回消息界面",就肯定是對話頁面 } //驗證當前事件是否符合查詢頁面上的紅包 if (!invalidEnvelopeUi(event)) { return; } //延遲點擊紅包,防止被檢測到開了搶紅包,不過感覺還是感覺會被檢測到,應該有的效果吧... try { Thread.sleep(delayTime); } catch (InterruptedException e) { e.printStackTrace(); } //普通紅包,檢索點擊拆開的字眼。 List<AccessibilityNodeInfo> envelope = findViewListByText(QQConstant.QQ_CLICK_TAKE_APART, false); //處理普通紅包 progressNormal(envelope); //口令紅包,檢索口令紅包的字眼。 List<AccessibilityNodeInfo> passwordList = findViewListByText(QQConstant.QQ_CLICK_PASSWORD_DIALOG, false); //處理口令紅包 progressPassword(passwordList); } /** * 描述:驗證是否現在是在聊天頁面,可以進行搶紅包處理 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/3 上午11:52 * * @param event */ public boolean invalidEnvelopeUi(AccessibilityEvent event) { //判斷類名是否是聊天頁面 if (!QQConstant.QQ_IM_CHAT_ACTIVITY.equals(event.getClassName().toString())) { return true; } //判斷頁面中的元素是否有點擊拆開的文本,有就返回可以進行查詢了 int recordCount = event.getRecordCount(); if (recordCount > 0) { for (int i = 0; i < recordCount; i++) { AccessibilityRecord record = event.getRecord(i); if (record == null) { break; } List<CharSequence> text = record.getText(); if (text != null && text.size() > 0 && text.contains(QQConstant.QQ_CLICK_TAKE_APART)) { //如果文本中有點擊拆開的字眼,就返回可以進行查詢了 return true; } } } return false; } /** * 回到系統桌面 */ private void back2Home(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } Intent home = new Intent(Intent.ACTION_MAIN); home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); home.addCategory(Intent.CATEGORY_HOME); startActivity(home); } /** * 描述:處理普通紅包 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/1 下午5:02 */ public void progressNormal(List<AccessibilityNodeInfo> passwordList) { if (passwordList != null && passwordList.size() > 0) { for (AccessibilityNodeInfo accessibilityNodeInfo : passwordList) { if (accessibilityNodeInfo != null && !TextUtils.isEmpty(accessibilityNodeInfo.getText()) && QQConstant.QQ_CLICK_TAKE_APART.equals(accessibilityNodeInfo.getText().toString())) { //點擊拆開紅包 performViewClick(accessibilityNodeInfo); //回復感謝信息,根據配置文件中配置的回復信息回復 String reReplyMessage = SettingConfig.getInstance().getReReplyMessage(); if (!TextUtils.isEmpty(reReplyMessage)) { replyMessage(reReplyMessage); } } } //最后延遲事件觸發返回事件,關閉紅包頁面 performBackClick(1200); } } /** * 描述:處理口令紅包 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/1 下午4:58 * * @param passwordList */ public void progressPassword(List<AccessibilityNodeInfo> passwordList) { if (passwordList != null && passwordList.size() > 0) { for (AccessibilityNodeInfo accessibilityNodeInfo : passwordList) { if (accessibilityNodeInfo != null && !TextUtils.isEmpty(accessibilityNodeInfo.getText()) && QQConstant.QQ_CLICK_PASSWORD_DIALOG.equals(accessibilityNodeInfo.getText().toString())) { //如果口令紅包存在,就在輸入框中進行輸入,然后發送 AccessibilityNodeInfo parent = accessibilityNodeInfo.getParent(); if (parent != null) { CharSequence contentDescription = parent.getContentDescription(); if (!TextUtils.isEmpty(contentDescription)) { //1. 獲取口令 String key = (String) contentDescription; if (key.contains(",") && key.contains("口令:")) { key = key.substring(key.indexOf("口令:") + 3, key.lastIndexOf(",")); } Log.e("口令", key); //2. 填寫口令到編輯框上然后進行發送 replyMessage(key); //返回,關閉紅包頁面 performBackClick(1200); } } } } } } /** * 喚醒屏幕并解鎖權限 * <uses-permission android:name="android.permission.WAKE_LOCK" /> */ @SuppressLint("Wakelock") @SuppressWarnings("deprecation") public void wakeUpAndUnlock(Context context) { // 點亮屏幕 wl.acquire(); // 釋放 wl.release(); // 解鎖 kl.disableKeyguard(); } /** * 描述:回復消息,無延遲 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/3 下午5:10 */ public void replyMessage(String key) { replyMessage(key, 0); } /** * 描述:回復消息 * 作者:卜俊文 * 郵箱:344176791@qq.com * 日期:2017/11/3 下午5:10 */ public void replyMessage(String key, int time) { //延遲 if (time > 0) { try { Thread.sleep(time); } catch (InterruptedException e) { e.printStackTrace(); } } //獲取QQ聊天頁面輸入框 AccessibilityNodeInfo chat_edit = findViewByID(QQConstant.QQ_CHAT_MESSAGE_INPUT); if (chat_edit != null) { //把口令粘貼到輸入框中 pastaText(chat_edit, MyApp.context, key); //獲取QQ聊天頁面發送消息按鈕 AccessibilityNodeInfo sendMessage = findViewByID(QQConstant.QQ_CHAT_MESSAGE_SEND); //然后就按下發送按鈕 if (sendMessage != null && Button.class.getName().equals(sendMessage.getClassName())) { performViewClick(sendMessage); } } } @Override public void onInterrupt() { } @Override protected void onServiceConnected() { super.onServiceConnected(); // 獲取電源管理器對象 PowerManager pm = (PowerManager) MyApp.context .getSystemService(Context.POWER_SERVICE); // 獲取PowerManager.WakeLock對象,后面的參數|表示同時傳入兩個值,最后的是調試用的Tag wl = pm.newWakeLock( PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright"); KeyguardManager km = (KeyguardManager) MyApp.context.getSystemService(Context.KEYGUARD_SERVICE); kl = km.newKeyguardLock("unLock"); //初始化屏幕的監聽 ScreenListener screenListener = new ScreenListener(MyApp.context); screenListener.begin(new ScreenListener.ScreenStateListener() { @Override public void onScreenOn() { Log.e("ScreenListener", "屏幕打開了"); } @Override public void onScreenOff() { //在屏幕關閉的時候,進行鎖屏,不執行的話,鎖屏就失效了,因為要實現鎖屏狀態下也可以進行搶紅包。 Log.e("ScreenListener", "屏幕關閉了"); if (kl != null) { kl.disableKeyguard(); kl.reenableKeyguard(); } } @Override public void onUserPresent() { Log.e("ScreenListener", "解鎖了"); } }); } @Override public void onDestroy() { super.onDestroy(); } }
(四)QQ輔助服務里有用到的常量
public class QQConstant { //QQ的應用包名 public static final String QQ_PACKAGE_NAME = "com.tencent.mobileqq"; //狀態欄紅包關鍵字 public static final String QQ_ENVELOPE_KEYWORD = "[QQ紅包]"; //QQ聊天頁面 public static final String QQ_IM_CHAT_ACTIVITY = "com.tencent.mobileqq.activity.SplashActivity"; //點擊拆開 public static final String QQ_CLICK_TAKE_APART = "點擊拆開"; //口令紅包 public static final String QQ_CLICK_PASSWORD_DIALOG = "口令紅包"; //聊天頁面,輸入框ID public static final String QQ_CHAT_MESSAGE_INPUT = "com.tencent.mobileqq:id/input"; //聊天頁面,發送按鈕 public static final String QQ_CHAT_MESSAGE_SEND = "com.tencent.mobileqq:id/fun_btn"; }
五、紅包問題
用的時候偶爾會被QQ檢測到用了紅包插件,可能是因為搶的速度太快,導致數據不符合正常的點擊時間,我有加入一個延遲時間,不知道有沒有效果,如果有知道的也可以留言,謝謝。
在QQ的主頁面上,收到消息的時候通知欄是不會通知的,所以這里不能進行解析通知欄跳轉聊天頁面,沒有找到什么元素可以告訴我怎么進入紅包的聊天頁面,如果有知道的可以留言,謝謝。
這種輔助服務的方式搶紅包,進入聊天頁面后,他檢索字段只會檢索當前頁面可視的元素,某些紅包要是在聊天記錄上面看不見的,需要滑動上去才可以觸發解析紅包,不過一般不會一次性10個紅包都發出來吧,嘿嘿。
以上是“Android輔助功如何實現自動搶紅包”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。