編輯:關於Android編程
最近有一段時間沒寫博客了,一方面是工作比較忙,一方面也著實本人水平有限,沒有太多能與大家分享的東西,也就是在最近公司要做一個搶紅包的功能,老板發話了咋們就開干呗,本人就開始在網上收集資料,經過整理和實踐,總算完美實現了功能,這裡拿出本人一點微薄的成就與大家分享。
首先界面是這樣的
開啟自動搶紅包只需點擊相應的選項即可,下面我們進入正題,實現自動搶紅包的原理,其實是借助android下的一個輔助服務AccessibilityService,這個服務是google公司為許多Android使用者因為各種情況導致他們要以不同的方式與手機交互。這包括了有些用戶由於視力上,身體上,年齡上的問題致使他們不能看完整的屏幕或者使用觸屏,也包括了無法很好接收到語音信息和提示的聽力能力比較弱的用戶。Android提供了Accessibility功能和服務幫助這些用戶更加簡單地操作設備,包括文字轉語音(這個不支持中文),觸覺反饋,手勢操作,軌跡球和手柄操作。
而開發者可以利用這些服務使得程序更好用,我們來看代碼:
android:accessibilityEventTypes 這個是配置要監聽的輔助事件,我們只需要用到typeNotificationStateChanged(通知變化事件)、typeWindowStateChanged(界面變化事件)
android:packageNames 這個是要監聽應用的包名,如果要監聽多個應用,則用","去分隔
android:accessibilityFeedbackType 這個是設置反饋方式,方式有如下幾種
android:accessibilityFeedbackType="feedbackGeneric"通用的反饋類型
android:notificationTimeout="100"為事件回調的延遲時間
android:accessibilityFlags="flagDefault"默認標記
android:canRetrieveWindowContent="true"如果不設為true,AccessibilityEvent.getSource()獲取的對象即為空
AccessibilityService類主要有以下四個方法:
@Override public void onAccessibilityEvent(AccessibilityEvent event) { //接收事件,如觸發了通知欄變化、界面變化等 } @Override protected boolean onKeyEvent(KeyEvent event) { //接收按鍵事件 return super.onKeyEvent(event); } @Override public void onInterrupt() { //服務中斷,如授權關閉或者將服務殺死 } @Override protected void onServiceConnected() { super.onServiceConnected(); //連接服務後,一般是在授權成功後會接收到 }
@Override public void onReceiveJob(AccessibilityEvent event) { final int eventType = event.getEventType(); if (eventType == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) {// 通知欄事件 Parcelable data = event.getParcelableData(); if (data == null || !(data instanceof Notification)) { return; } // 開啟快速模式,不處理 if (QiangHongBaoService.isNotificationServiceRunning() && getConfig().isEnableNotificationService()) { return; } Listtexts = event.getText(); if (!texts.isEmpty()) { String text = String.valueOf(texts.get(0)); notificationEvent(text, (Notification) data); } } else if (eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {// 界面改變事件 openHongBao(event); } else if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {// 界面內容改變事件 if (mCurrentWindow != WINDOW_LAUNCHER) { // 不在聊天界面或聊天列表,不處理 return; } if (isReceivingHongbao) { handleChatListHongBao(); } } }
通知欄事件中,我們進行了判空,並攔截快速模式,重點是event.getText()獲取通知中的文本,進行判斷:
/** 所有通知欄事件,都在該方法處理 */ private void notificationEvent(String ticker, Notification nf) { String text = ticker; int index = text.indexOf(":"); if (index != -1) { text = text.substring(index + 1); } text = text.trim(); if (text.contains("[微信紅包]")) { // 紅包消息 newHongBaoNotification(nf); } }
如果有[微信紅包]字眼,便進入下一步處理
/** 打開通知欄消息 */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void newHongBaoNotification(Notification notification) { isReceivingHongbao = true; // 以下是精華,將微信的通知欄消息打開 PendingIntent pendingIntent = notification.contentIntent; boolean lock = NotifyHelper.isLockScreen(getContext()); if (!lock) {// 未鎖,自動點開通知 NotifyHelper.send(pendingIntent); } else {// 鎖屏,顯示通知,微信自帶,不作任何處理 NotifyHelper.showNotify(getContext(), String.valueOf(notification.tickerText), pendingIntent); } // 鎖屏 || 模式非自動搶 if (lock || getConfig().getWechatMode() != Config.WX_MODE_0) { // 開啟聲音,震動提示 NotifyHelper.playEffect(getContext(), getConfig()); } }
/** 執行PendingIntent事件 */ public static void send(PendingIntent pendingIntent) { try { pendingIntent.send(); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); } }
@TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void openHongBao(AccessibilityEvent event) { if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI".equals(event.getClassName())) { mCurrentWindow = WINDOW_LUCKYMONEY_RECEIVEUI; // 點中了紅包,下一步就是去拆紅包 handleLuckyMoneyReceive(); } else if ("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI".equals(event.getClassName())) { mCurrentWindow = WINDOW_LUCKYMONEY_DETAIL; // 拆完紅包後看詳細的紀錄界面 if (getConfig().getWechatAfterGetHongBaoEvent() == Config.WX_AFTER_GET_GOHOME) { // 返回主界面,以便收到下一次的紅包通知 AccessibilityHelper.performHome(getService()); } } else if ("com.tencent.mm.ui.LauncherUI".equals(event.getClassName())) { mCurrentWindow = WINDOW_LAUNCHER; // 在聊天界面,去點中紅包 handleChatListHongBao(); } else { mCurrentWindow = WINDOW_OTHER; } }
/** * 收到聊天裡的紅包 * */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) private void handleChatListHongBao() { int mode = getConfig().getWechatMode(); if (mode == Config.WX_MODE_3) { // 只通知模式 return; } AccessibilityNodeInfo nodeInfo = getService().getRootInActiveWindow(); if (nodeInfo == null) { return; } if (mode != Config.WX_MODE_0) {// 非自動搶 boolean isMember = isMemberChatUi(nodeInfo); if (mode == Config.WX_MODE_1 && isMember) {// 過濾群聊 return; } else if (mode == Config.WX_MODE_2 && !isMember) { // 過濾單聊 return; } } // 下面就是,激動人心的搶紅包代碼 List list = nodeInfo.findAccessibilityNodeInfosByText("領取紅包"); if (list != null && list.isEmpty()) {// "領取紅包"關鍵字節點獲取不到 // 從消息列表查找紅包 AccessibilityNodeInfo node = AccessibilityHelper.findNodeInfosByText(nodeInfo, HONGBAO_TEXT_KEY); if (node != null) { isReceivingHongbao = true; AccessibilityHelper.performClick(node); } } else if (list != null) { if (isReceivingHongbao) { // 最新的紅包領起 AccessibilityNodeInfo node = list.get(list.size() - 1); AccessibilityHelper.performClick(node); isReceivingHongbao = false; } } }
/** * 點擊聊天裡的紅包後,顯示的界面 * */ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void handleLuckyMoneyReceive() { AccessibilityNodeInfo nodeInfo = getService().getRootInActiveWindow(); AccessibilityNodeInfo targetNode = null; int event = getConfig().getWechatAfterOpenHongBaoEvent(); int wechatVersion = getWechatVersion(); if (event == Config.WX_AFTER_OPEN_HONGBAO) { // 拆紅包 if (wechatVersion < USE_ID_MIN_VERSION) { targetNode = AccessibilityHelper.findNodeInfosByText(nodeInfo, "拆紅包"); } else { String buttonId = "com.tencent.mm:id/b43"; if (wechatVersion == 700) { buttonId = "com.tencent.mm:id/b2c"; } if (buttonId != null) { targetNode = AccessibilityHelper.findNodeInfosById(nodeInfo, buttonId); } if (targetNode == null) { // 分別對應固定金額的紅包 拼手氣紅包 AccessibilityNodeInfo textNode = AccessibilityHelper.findNodeInfosByTexts(nodeInfo, "發了一個紅包", "給你發了一個紅包", "發了一個紅包,金額隨機"); if (textNode != null) { for (int i = 0; i < textNode.getChildCount(); i++) { AccessibilityNodeInfo node = textNode.getChild(i); if ("android.widget.Button".equals(node.getClassName())) { targetNode = node; break; } } } } if (targetNode == null) { // 通過組件查找 targetNode = AccessibilityHelper.findNodeInfosByClassName(nodeInfo, "android.widget.Button"); } } } else if (event == Config.WX_AFTER_OPEN_SEE) { // 看一看 if (getWechatVersion() < USE_ID_MIN_VERSION) { // 低版本才有 看大家手氣的功能 targetNode = AccessibilityHelper.findNodeInfosByText(nodeInfo, "看看大家的手氣"); } } else if (event == Config.WX_AFTER_OPEN_NONE) {// 靜靜地看著 return; } if (targetNode != null) { final AccessibilityNodeInfo n = targetNode; long sDelayTime = getConfig().getWechatOpenDelayTime(); if (sDelayTime != 0) { getHandler().postDelayed(new Runnable() { @Override public void run() { AccessibilityHelper.performClick(n); } }, sDelayTime); } else { AccessibilityHelper.performClick(n); } } }
QQ的實現也基本相同,我們直接上代碼
@Override public void onReceiveJob(AccessibilityEvent event) { openRed(event); }
private void openRed(AccessibilityEvent event) { this.rootNodeInfo = event.getSource(); if (rootNodeInfo == null) { return; } mReceiveNode = null; checkNodeInfo(); /* 如果已經接收到紅包並且還沒有戳開 */ if (mLuckyMoneyReceived && (mReceiveNode != null)) { int size = mReceiveNode.size(); if (size > 0) { String id = getHongbaoText(mReceiveNode.get(size - 1)); long now = System.currentTimeMillis(); if (this.shouldReturn(id, now - lastFetchedTime)) return; lastFetchedHongbaoId = id; lastFetchedTime = now; AccessibilityNodeInfo cellNode = mReceiveNode.get(size - 1); if (cellNode.getText().toString().equals("口令紅包已拆開")) { return; } cellNode.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); if (cellNode.getText().toString().equals(QQ_HONG_BAO_PASSWORD)) { AccessibilityNodeInfo rowNode = getService().getRootInActiveWindow(); if (rowNode == null) { return; } else { recycle(rowNode); } } mLuckyMoneyReceived = false; } } }
@TargetApi(Build.VERSION_CODES.KITKAT) public void recycle(AccessibilityNodeInfo info) { if (info.getChildCount() == 0) { /* 這個if代碼的作用是:匹配“點擊輸入口令的節點,並點擊這個節點” */ if (info.getText() != null && info.getText().toString().equals(QQ_CLICK_TO_PASTE_PASSWORD)) { info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); } /* 這個if代碼的作用是:匹配文本編輯框後面的發送按鈕,並點擊發送口令 */ if (info.getClassName().toString().equals("android.widget.Button") && info.getText().toString().equals("發送")) { info.performAction(AccessibilityNodeInfo.ACTION_CLICK); } } else { for (int i = 0; i < info.getChildCount(); i++) { if (info.getChild(i) != null) { recycle(info.getChild(i)); } } } }
為使應用程序之間能夠彼此通信,Android提供了IPC (Inter Process Communication,進程間通信)的一種獨特實現: AIDL (Androi
官網:https://developer.android.com/training/implementing-navigation/nav-drawer.ht
最近比較忙,而且又要維護自己的博客,視頻和公眾號,也就沒仔細的梳理源碼的入門邏輯,今天也就來講一個源碼的玩法,各位看官,一起學習學習! 參考資料 官方教程:http:/
內容概要示例演示和基本介紹啟用Action Bar在Action Bar上添加按鈕自定義Action Bar樣式自動隱藏Action BarAction Provider