Android搶紅包插件實現(xiàn)原理淺析
搶紅包,先看效果圖~
實現(xiàn)自動搶紅包,解決問題有兩點:
一:如何實時監(jiān)聽發(fā)紅包的事件
二:如何在紅包到來的時候自動進入頁面并自動點擊紅包
一、如何獲取紅包到來的事件
為了獲取紅包到來狀態(tài)欄的變化,我們要用到一個類:Accessibility
許多Android使用者因為各種情況導(dǎo)致他們要以不同的方式與手機交互。
這包括了有些用戶由于視力上,身體上,年齡上的問題致使他們不能看完整的屏幕或者使用觸屏,也包括了無法很好接收到語音信息和提示的聽力能力比較弱的用戶。
Android提供了Accessibility功能和服務(wù)幫助這些用戶更加簡單地操作設(shè)備,包括文字轉(zhuǎn)語音(這個不支持中文),觸覺反饋,手勢操作,軌跡球和手柄操作。
OK,了解到這一點,那么接下來就順利點了,首先來看看Accessibility以及AccessibilityService的使用
1.新建一個類繼承AccessibilityService,并在AndroidManifest文件里注冊它:
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" /> <application> <service android:name="com.zkhb.weixinqinghongbao.service.QiangHongBaoService" 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/qianghongbao_service_config" /> </service> </application>
在子類QiangHongBaoService里實現(xiàn)幾個重要的重載方法:
onServiceConnected() - 可選。系統(tǒng)會在成功連接上你的服務(wù)的時候調(diào)用這個方法,在這個方法里你可以做一下初始化工作,例如設(shè)備的聲音震動管理,也可以調(diào)用setServiceInfo()進行配置工作。
onAccessibilityEvent() - 必須。通過這個函數(shù)可以接收系統(tǒng)發(fā)送來的AccessibilityEvent,接收來的AccessibilityEvent是經(jīng)過過濾的,過濾是在配置工作時設(shè)置的。
onInterrupt() - 必須。這個在系統(tǒng)想要中斷AccessibilityService返給的響應(yīng)時會調(diào)用。在整個生命周期里會被調(diào)用多次。
onUnbind() - 可選。在系統(tǒng)將要關(guān)閉這個AccessibilityService會被調(diào)用。在這個方法中進行一些釋放資源的工作。
然后在/res/xml/accessibility_service_config.xml:
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged|typeWindowContentChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="" android:canRetrieveWindowContent="true" android:description="@string/accessibility_description" android:notificationTimeout="100" android:packageNames="com.tencent.mm" />
二、主要關(guān)注點以及如何在紅包到來的時候自動進入頁面并自動點擊紅包
在onAccessibilityEvent方法中監(jiān)聽狀態(tài)欄的變化,主要有:
AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED、
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED、
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
在響應(yīng)窗體以及窗體內(nèi)容變化時處理相關(guān)邏輯,獲取帶微信消息時以下代碼打開消息:
//將微信的通知欄消息打開 Notification notification = (Notification) event.getParcelableData(); PendingIntent pendingIntent = notification.contentIntent; try { pendingIntent.send(); } catch (PendingIntent.CanceledException e) { e.printStackTrace(); }
具體代碼網(wǎng)上有很多,一般都是通過:findAccessibilityNodeInfosByText、findAccessibilityNodeInfosByViewId查找文本或者資源節(jié)點進行點擊操作,但新版微信開紅包頁面進行了處理,沒有文本信息,而如果采用:
圖中resouces-id這種形式就可能出現(xiàn)這種情況:
在了解整個核心后,獲取事件不外乎就是通過文本與id判斷,那么就可以將文本改為圖標(biāo)方式,將id改為動態(tài)id(每次顯示都是隨機生成),這樣一來就可以提高外掛的門檻。
如何進行規(guī)避呢,目前我想的是既然開紅包的頁面文本和ID都有可能會變,那我們能不能找個不變的進行判斷呢,考慮過后,我覺得最可能不變的地方就是開紅包這個按鈕的位置,也就是圖中的bounds,于是在思考過后有了下面的處理:
for (int i = 0; i < nodeInfo.getChildCount(); i++) { //Log.e("TAG", "getViewIdResourceName :"+nodeInfo.getChild(i).getViewIdResourceName()); Rect outBounds = new Rect(); nodeInfo.getChild(i).getBoundsInScreen(outBounds); int left_dp = px2dip(this, 400); int top_dp = px2dip(this, 1035); int right_dp = px2dip(this, 682); int bottom_dp = px2dip(this, 1320); int left_px = dip2px(this, left_dp); int top_px = dip2px(this, top_dp); int right_px = dip2px(this, right_dp); int bottom_px = dip2px(this, bottom_dp); Rect mStandar = new Rect(left_px,top_px,right_px,bottom_px); if(mStandar.contains(outBounds)){ Log.e("TAG", "outBounds.left :"+outBounds.left+";outBounds.top :"+outBounds.top+";outBounds.right :"+outBounds.right+";outBounds.bottom :"+outBounds.bottom); nodeInfo.getChild(i).performAction(AccessibilityNodeInfo.ACTION_CLICK); break; } }
這里取的矩形區(qū)域要比按鈕區(qū)域稍大點,然后判斷到開紅包頁面按鈕是否在我們預(yù)先設(shè)置的區(qū)域內(nèi),如果在,我們直接進行點擊開紅包操作:AccessibilityNodeInfo.ACTION_CLICK。
其他比如如何防止重復(fù)搶等細(xì)節(jié)問題,也是要處理的問題。
好了,直接放下關(guān)鍵代碼,僅供參考:
package com.zkhb.weixinqinghongbao.service; import java.util.Date; import java.util.List; import android.accessibilityservice.AccessibilityService; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.KeyguardManager; import android.app.KeyguardManager.KeyguardLock; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.graphics.Rect; import android.os.Build; import android.os.Handler; import android.os.PowerManager; import android.os.PowerManager.WakeLock; import android.util.Log; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Toast; import com.zkhb.weixinqinghongbao.MainActivity; import com.zkhb.weixinqinghongbao.R; import com.zkhb.weixinqinghongbao.entity.HongBaoInfo; import com.zkhb.weixinqinghongbao.util.DateFormatUtils; import com.zkhb.weixinqinghongbao.util.LogUtil; /** * * 搶紅包服務(wù) */ @SuppressLint("NewApi") public class QiangHongBaoService extends AccessibilityService { // static final String TAG = "QiangHongBao"; /** 微信的包名*/ static final String WECHAT_PACKAGENAME = "com.tencent.mm"; /** 紅包消息的關(guān)鍵字*/ static final String HONGBAO_TEXT_KEY = "[微信紅包]"; /** 紅包消息的關(guān)鍵字*/ static final String HONGBAO_TEXT_KEY1 = "微信紅包"; private static final int ENVELOPE_RETURN = 0; private static final String LOCK_TAG = "屏幕"; Handler handler = new Handler(); /** 是否在搶紅包界面里*/ // public boolean isInMM=false; /** 是否可以點擊*/ // public boolean ISCLICKED=false; /** 是否進入過拆紅包界面*/ public static boolean ISCOMINQIANGCHB=false; //真正的 public static boolean ISCOMINQIANGCHB2=false; //真正的判斷 public static boolean ISCOMINQIANGCHB3=false; /** 是否來自通知欄*/ private static boolean ISCOMNOTIFY=false; private PowerManager pm; //點亮屏幕 private WakeLock mWakeLock; //解鎖鎖定屏幕 private KeyguardLock keyguardLock; /**判斷之前用戶是否鎖屏 */ private static boolean islock=false; /**通知服務(wù) */ private NotificationManager n_manager; public void unlock(){ if(pm==null){ pm = (PowerManager) getApplication().getSystemService(Context.POWER_SERVICE); } boolean isScreenOn = pm.isScreenOn();//如果為true,則表示屏幕“亮”了,否則屏幕“暗”了。 if(!isScreenOn){ islock=true; KeyguardManager keyguardManager = (KeyguardManager)getSystemService(KEYGUARD_SERVICE); keyguardLock = keyguardManager.newKeyguardLock(LOCK_TAG); keyguardLock.disableKeyguard(); mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, LOCK_TAG); mWakeLock.acquire(); } } public void lock(){ if(islock){ //釋放屏幕常亮鎖 if(null != mWakeLock) { mWakeLock.release(); } //屏幕鎖定 if(keyguardLock!=null){ keyguardLock.reenableKeyguard(); } } } @Override public void onAccessibilityEvent(AccessibilityEvent event) { final int eventType = event.getEventType(); LogUtil.info("事件---->" + event); //通知欄事件 if(eventType == AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED) { unlock(); List<CharSequence> texts = event.getText(); if(!texts.isEmpty()) { for(CharSequence t : texts) { String text = String.valueOf(t); if(text.contains(HONGBAO_TEXT_KEY)) { ISCOMNOTIFY=true; ISCOMINQIANGCHB=false; openNotify(event); break; } } } } else if(eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { unlock(); openHongBao(event); // AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); // List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText = nodeInfo.findAccessibilityNodeInfosByText("領(lǐng)取紅包"); // if(findAccessibilityNodeInfosByText.isEmpty()){ // isInMM=false; // }else{ // isInMM=true; // } // List<CharSequence> text = event.getText(); // if(text.size()>=0){ // CharSequence charSequence = text.get(0); //// if(charSequence.equals("微信")){ //// isInMM=true; //// }else{ //// isInMM=false; //// } // } }else if(eventType==AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && ISCOMNOTIFY){ unlock(); openHongBao(event); List<AccessibilityNodeInfo> InfoText = getRootInActiveWindow().findAccessibilityNodeInfosByText("領(lǐng)取紅包"); if(!InfoText.isEmpty()){ checkKey2(); ISCOMNOTIFY=false; } } } /*@Override protected boolean onKeyEvent(KeyEvent event) { //return super.onKeyEvent(event); return true; }*/ @Override public boolean onUnbind(Intent intent) { Toast.makeText(this, "斷開搶紅包服務(wù)", Toast.LENGTH_SHORT).show(); ISCOMINQIANGCHB=false; islock=false; ISCOMINQIANGCHB2=false; ISCOMINQIANGCHB3=false; ISCOMNOTIFY=false; setNotification("已關(guān)閉搶紅包小助手服務(wù)~~",Notification.FLAG_AUTO_CANCEL,"已關(guān)閉搶紅包小助手服務(wù)~~~"); return super.onUnbind(intent); } @Override public void onInterrupt() { Toast.makeText(this, "中斷搶紅包服務(wù)", Toast.LENGTH_SHORT).show(); } @Override protected void onServiceConnected() { super.onServiceConnected(); ISCOMINQIANGCHB=false; ISCOMINQIANGCHB2=false; ISCOMINQIANGCHB3=false; islock=false; ISCOMNOTIFY=false; setNotification("已開啟搶紅包小助手服務(wù)~~",Notification.FLAG_NO_CLEAR,"已開啟搶紅包小助手服務(wù)~~~"); Toast.makeText(this, "連接搶紅包服務(wù)", Toast.LENGTH_SHORT).show(); } private void setNotification(String content,int flags,String title) { if(n_manager==null){ n_manager = (NotificationManager) getApplicationContext().getSystemService(Context.NOTIFICATION_SERVICE); } n_manager.cancelAll(); Notification notification=new Notification(R.drawable.ic_launcher, content, System.currentTimeMillis()); // notification.defaults |= Notification.DEFAULT_VIBRATE; // long[] vibrate = {0,100,200,300}; //0毫秒后開始振動,振動100毫秒后停止,再過200毫秒后再次振動300毫秒 // notification.vibrate=vibrate; notification.flags |= flags; //表明在點擊了通知欄中的"清除通知"后,此通知不清除, Intent notificationIntent = new Intent(this,MainActivity.class); //點擊該通知后要跳轉(zhuǎn)的Activity PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(),0,notificationIntent,0); notification.setLatestEventInfo(getApplicationContext(), title, "進入微信搶紅包~~", contentIntent); n_manager.notify(0, notification); } private void sendNotifyEvent(){ AccessibilityManager manager= (AccessibilityManager)getSystemService(ACCESSIBILITY_SERVICE); if (!manager.isEnabled()) { return; } AccessibilityEvent event=AccessibilityEvent.obtain(AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED); event.setPackageName(WECHAT_PACKAGENAME); event.setClassName(Notification.class.getName()); CharSequence tickerText = HONGBAO_TEXT_KEY; event.getText().add(tickerText); manager.sendAccessibilityEvent(event); } /** 打開通知欄消息*/ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void openNotify(AccessibilityEvent event) { if(event.getParcelableData() == null || !(event.getParcelableData() instanceof Notification)) { return; } //將微信的通知欄消息打開 Notification notification = (Notification) event.getParcelableData(); PendingIntent pendingIntent = notification.contentIntent; 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())) { //點中了紅包,下一步就是去拆紅包 ISCOMINQIANGCHB=true; ISCOMINQIANGCHB2=true; checkKey1(); } else if("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI".equals(event.getClassName())) { //拆完紅包后看詳細(xì)的紀(jì)錄界面 LogUtil.info("事件---->com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI"); //nonething // if(ISCOMINQIANGCHB){ // ISCOMINQIANGCHB=false; // } if(ISCOMINQIANGCHB2){ ISCOMINQIANGCHB3=true; }else{ ISCOMINQIANGCHB3=false; } checkKey3(); ISCOMINQIANGCHB=true; ISCOMINQIANGCHB2=false; performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); if(getSharedPreferences("config", Context.MODE_PRIVATE).getBoolean("auto", false)){ performGlobalAction(AccessibilityService.GLOBAL_ACTION_HOME); } lock(); } else if("com.tencent.mm.ui.LauncherUI".equals(event.getClassName())) { // isInMM=true; //在聊天界面,去點中紅包 LogUtil.info("事件---->com.tencent.mm.ui.LauncherUI"); checkKey2(); } } @SuppressLint("NewApi") @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void checkKey3() { AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); if(nodeInfo == null) { LogUtil.info("rootWindow為空333"); return; } //TODO List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText = nodeInfo.findAccessibilityNodeInfosByText("元"); if(findAccessibilityNodeInfosByText.size()>=0){ AccessibilityNodeInfo accessibilityNodeInfo2 = findAccessibilityNodeInfosByText.get(0).getParent(); CharSequence money = accessibilityNodeInfo2.getChild(2).getText(); CharSequence name = accessibilityNodeInfo2.getChild(0).getText(); if(ISCOMINQIANGCHB3){ HongBaoInfo info=new HongBaoInfo(); info.setStrDateTime(DateFormatUtils.format("yyyy-MM-dd HH:mm:ss", new Date())); info.setStrMoney(money+""); info.setStrName(name+""); info.save(); } Toast.makeText(getApplicationContext(), money+":::"+name, 0).show(); } // List<AccessibilityNodeInfo> findAccessibilityNodeInfosByViewId = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/aw8"); // AccessibilityNodeInfo accessibilityNodeInfo = findAccessibilityNodeInfosByViewId.get(0); // CharSequence text = accessibilityNodeInfo.getText(); } private void checkKey1() { AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); if(nodeInfo == null) { LogUtil.info("rootWindow為空11111"); return; } for (int i = 0; i < nodeInfo.getChildCount(); i++) { Log.e("TAG", "getViewIdResourceName :"+nodeInfo.getChild(i).getViewIdResourceName()); Rect outBounds = new Rect(); nodeInfo.getChild(i).getBoundsInScreen(outBounds); int left_dp = px2dip(this, 400); int top_dp = px2dip(this, 1035); int right_dp = px2dip(this, 682); int bottom_dp = px2dip(this, 1320); int left_px = dip2px(this, left_dp); int top_px = dip2px(this, top_dp); int right_px = dip2px(this, right_dp); int bottom_px = dip2px(this, bottom_dp); Rect mStandar = new Rect(left_px,top_px,right_px,bottom_px); if(mStandar.contains(outBounds)){ Log.e("TAG", "outBounds.left :"+outBounds.left+";outBounds.top :"+outBounds.top+";outBounds.right :"+outBounds.right+";outBounds.bottom :"+outBounds.bottom); nodeInfo.getChild(i).performAction(AccessibilityNodeInfo.ACTION_CLICK); break; } // nodeInfo.performAction(action)//[405,1042][675,1312] } //List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("拆紅包"); // List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByViewId("com.tencent.mm:id/b5d"); // for(AccessibilityNodeInfo n : list) { // n.performAction(AccessibilityNodeInfo.ACTION_CLICK); // } } private void checkKey2() { AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); if(nodeInfo == null) { LogUtil.info("rootWindow為空222222"); return; } List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText("領(lǐng)取紅包"); if(list.isEmpty()) { list = nodeInfo.findAccessibilityNodeInfosByText(HONGBAO_TEXT_KEY); for(AccessibilityNodeInfo n : list) { LogUtil.info("-->微信紅包:" + n); n.performAction(AccessibilityNodeInfo.ACTION_CLICK); break; } } else { //最新的紅包領(lǐng)起 AccessibilityNodeInfo parent = list.get(list.size() - 1).getParent(); // Log.w(TAG, "ISCLICKED::"+ISCLICKED)!ISCLICKED; if(parent != null && !ISCOMINQIANGCHB) { parent.performAction(AccessibilityNodeInfo.ACTION_CLICK); } // for(int i = list.size() - 1; i >= 0; i --) { // AccessibilityNodeInfo parent = list.get(i).getParent(); // Log.i(TAG, "-->領(lǐng)取紅包:" + parent); // if(parent != null && parent.isClickable()) { // parent.performAction(AccessibilityNodeInfo.ACTION_CLICK); // break; // } // } // performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); } } /** * @param info 當(dāng)前節(jié)點 * @param matchFlag 需要匹配的文字 * @param type 操作的類型 */ @SuppressLint("NewApi") public void recycle(AccessibilityNodeInfo info, String matchFlag, int type) { if (info != null) { if (info.getChildCount() == 0) { CharSequence desrc = info.getContentDescription(); switch (type) { case ENVELOPE_RETURN://返回 if (desrc != null && matchFlag.equals(info.getContentDescription().toString().trim())) { if (info.isCheckable()) { info.performAction(AccessibilityNodeInfo.ACTION_CLICK); } else { performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); } } break; } } else { int size = info.getChildCount(); for (int i = 0; i < size; i++) { AccessibilityNodeInfo childInfo = info.getChild(i); if (childInfo != null) { LogUtil.info("index: " + i + " info" + childInfo.getClassName() + " : " + childInfo.getContentDescription()+" : "+info.getText()); recycle(childInfo, matchFlag, type); } } } } } @Override public void onDestroy() { super.onDestroy(); lock(); } /** * 根據(jù)手機的分辨率從 dip 的單位 轉(zhuǎn)成為 px(像素) */ public static int dip2px(Context context, float dpValue) { final float scale = context.getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } /** * 根據(jù)手機的分辨率從 px(像素) 的單位 轉(zhuǎn)成為 dp */ public static int px2dip(Context context, float pxValue) { // final float scale = context.getResources().getDisplayMetrics().density; return (int) (pxValue / 3 + 0.5f); //3是本人手機的設(shè)備密度 } }
AccessibilityService還可以用在智能安裝、虛擬按鍵上都有很多應(yīng)用,這樣的實踐只是給我們一種解決問題另外的一種思路,問題嘛,總還是要解決的。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android實現(xiàn)藍(lán)牙客戶端與服務(wù)器端通信示例
這篇文章主要介紹了Android實現(xiàn)藍(lán)牙客戶端與服務(wù)器端通信示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-01-01Android中使用ZXing生成二維碼(支持添加Logo圖案)
ZXing是谷歌的一個開源庫,可以用來生成二維碼、掃描二維碼。接下來通過本文給大家介紹Android中使用ZXing生成二維碼(支持添加Logo圖案),需要的朋友參考下2017-01-01Android開發(fā)實現(xiàn)圖片平移、縮放、倒影及旋轉(zhuǎn)功能的方法
這篇文章主要介紹了Android開發(fā)實現(xiàn)圖片平移、縮放、倒影及旋轉(zhuǎn)功能的方法,涉及Android針對圖片的讀取、寫入、屬性設(shè)置及矩陣運算等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10Android入門之RelativeLayout、FrameLayout用法分析
這篇文章主要介紹了Android入門之RelativeLayout、FrameLayout用法分析,需要的朋友可以參考下2014-08-08