搶紅包,先看效果圖~
實現自動搶紅包,解決問題有兩點:
一:如何實時監聽發紅包的事件
二:如何在紅包到來的時候自動進入頁面并自動點擊紅包
【zhangke3016 http://blog.csdn.net/zhangke3016】
一、如何獲取紅包到來的事件
為了獲取紅包到來狀態欄的變化,我們要用到一個類:Accessibility
許多Android使用者因為各種情況導致他們要以不同的方式與手機交互。
這包括了有些用戶由于視力上,身體上,年齡上的問題致使他們不能看完整的屏幕或者使用觸屏,也包括了無法很好接收到語音信息和提示的聽力能力比較弱的用戶。
Android提供了Accessibility功能和服務幫助這些用戶更加簡單地操作設備,包括文字轉語音(這個不支持中文),觸覺反饋,手勢操作,軌跡球和手柄操作。
OK,了解到這一點,那么接下來就順利點了,首先來看看Accessibility以及AccessibilityService的使用
新建一個類繼承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里實現幾個重要的重載方法:
onServiceConnected() - 可選。系統會在成功連接上你的服務的時候調用這個方法,在這個方法里你可以做一下初始化工作,例如設備的聲音震動管理,也可以調用setServiceInfo()進行配置工作。
onAccessibilityEvent() - 必須。通過這個函數可以接收系統發送來的AccessibilityEvent,接收來的AccessibilityEvent是經過過濾的,過濾是在配置工作時設置的。
onInterrupt() - 必須。這個在系統想要中斷AccessibilityService返給的響應時會調用。在整個生命周期里會被調用多次。
onUnbind() - 可選。在系統將要關閉這個AccessibilityService會被調用。在這個方法中進行一些釋放資源的工作。
然后在/res/xml/qianghongbao_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" />
二、主要關注點以及如何在紅包到來的時候自動進入頁面并自動點擊紅包
在onAccessibilityEvent方法中監聽狀態欄的變化,主要有:AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED、AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED、AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
在響應窗體以及窗體內容變化時處理相關邏輯,獲取帶微信消息時以下代碼打開消息:
//將微信的通知欄消息打開Notification notification = (Notification) event
.getParcelableData()PendingIntent pendingIntent = notification
.contentIntenttry {pendingIntent
.send()} catch (PendingIntent
.CanceledException e) {e
.printStackTrace()}
具體代碼網上有很多,一般都是通過:findAccessibilityNodeInfosByText、findAccessibilityNodeInfosByViewId查找文本或者資源節點進行點擊操作,但新版微信開紅包頁面進行了處理,沒有文本信息,而如果采用:
(上圖截取是用的DDMS中的 UI Automator Viewer,如圖:Eclipse中:
或者在sdk的tools目錄下:
)
圖中resouces-id這種形式就可能出現這種情況:
在了解整個核心后,獲取事件不外乎就是通過文本與id判斷,那么就可以將文本改為圖標方式,將id改為動態id(每次顯示都是隨機生成),這樣一來就可以提高外掛的門檻。
如何進行規避呢,目前我想的是既然開紅包的頁面文本和ID都有可能會變,那我們能不能找個不變的進行判斷呢,考慮過后,我覺得最可能不變的地方就是開紅包這個按鈕的位置,也就是圖中的bounds,于是在思考過后有了下面的處理:
for (
int i =
0; i < nodeInfo.getChildCount(); i++) {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;}}
這里取的矩形區域要比按鈕區域稍大點,然后判斷到開紅包頁面按鈕是否在我們預先設置的區域內,如果在,我們直接進行點擊開紅包操作:AccessibilityNodeInfo.ACTION_CLICK。
其他比如如何防止重復搶等細節問題,也是要處理的問題。
好了,直接放下關鍵代碼,僅供參考:
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;
/**** 搶紅包服務*/
@SuppressLint(
"NewApi")
public class QiangHongBaoService extends AccessibilityService {/** 微信的包名*/static final String WECHAT_PACKAGENAME =
"com.tencent.mm";
/** 紅包消息的關鍵字*/static final String HONGBAO_TEXT_KEY =
"[微信紅包]";
/** 紅包消息的關鍵字*/static final String HONGBAO_TEXT_KEY1 =
"微信紅包";
private static final int ENVELOPE_RETURN =
0;
private static final String LOCK_TAG =
"屏幕";Handler handler =
new Handler();
/** 是否在搶紅包界面里*/
/** 是否可以點擊*/
/** 是否進入過拆紅包界面*/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;
/**通知服務 */private NotificationManager n_manager;
public void unlock(){
if(pm==
null){pm = (PowerManager) getApplication().getSystemService(Context.POWER_SERVICE);}
boolean isScreenOn = pm.isScreenOn();
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();}}}
@Overridepublic 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);
}
else if(eventType==AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED && ISCOMNOTIFY){unlock();openHongBao(event);List<AccessibilityNodeInfo> InfoText = getRootInActiveWindow().findAccessibilityNodeInfosByText(
"領取紅包");
if(!InfoText.isEmpty()){checkKey2();ISCOMNOTIFY=
false;}}}
@Overridepublic boolean onUnbind(Intent intent) {Toast.makeText(
this,
"斷開搶紅包服務", Toast.LENGTH_SHORT).show();ISCOMINQIANGCHB=
false;islock=
false;ISCOMINQIANGCHB2=
false;ISCOMINQIANGCHB3=
false;ISCOMNOTIFY=
false;setNotification(
"已關閉搶紅包小助手服務~~",Notification.FLAG_AUTO_CANCEL,
"已關閉搶紅包小助手服務~~~");
return super.onUnbind(intent);}
@Overridepublic void onInterrupt() {Toast.makeText(
this,
"中斷搶紅包服務", Toast.LENGTH_SHORT).show();}
@Overrideprotected void onServiceConnected() {
super.onServiceConnected();ISCOMINQIANGCHB=
false;ISCOMINQIANGCHB2=
false;ISCOMINQIANGCHB3=
false;islock=
false;ISCOMNOTIFY=
false;setNotification(
"已開啟搶紅包小助手服務~~",Notification.FLAG_NO_CLEAR,
"已開啟搶紅包小助手服務~~~");Toast.makeText(
this,
"連接搶紅包服務", 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.flags |= flags; Intent notificationIntent =
new Intent(
this,MainActivity.class); 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())) {LogUtil.info(
"事件---->com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI");
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())) {
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;}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();}
}
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;}
}
}
private void checkKey2() {AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if(nodeInfo ==
null) {LogUtil.info(
"rootWindow為空222222");
return;}List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(
"領取紅包");
if(list.isEmpty()) {list = nodeInfo.findAccessibilityNodeInfosByText(HONGBAO_TEXT_KEY);
for(AccessibilityNodeInfo n : list) {LogUtil.info(
"-->微信紅包:" + n);n.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;}}
else {AccessibilityNodeInfo parent = list.get(list.size() -
1).getParent();
if(parent !=
null && !ISCOMINQIANGCHB) {parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);}
}}
/*** @param info 當前節點* @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);}}}}}
@Overridepublic void onDestroy() {
super.onDestroy();lock();}
/** * 根據手機的分辨率從 dip 的單位 轉成為 px(像素) */ public static int dip2px(Context context,
float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (
int) (dpValue * scale +
0.5f); }
/** * 根據手機的分辨率從 px(像素) 的單位 轉成為 dp */ public static int px2dip(Context context,
float pxValue) {
return (
int) (pxValue /
3 +
0.5f); }
}
AccessibilityService還可以用在智能安裝、虛擬按鍵上都有很多應用,這樣的實踐只是給我們一種解決問題另外的一種思路,問題嘛,總還是要解決的。
總結
以上是生活随笔為你收集整理的抢红包插件实现原理浅析的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。