自動(dòng)搶紅包AccessibilityService

微信紅包自打出世以來(lái)就極其受歡迎,搶紅包插件可謂紅極一時(shí).今天,我們重新談?wù)創(chuàng)尲t包插件的哪些事兒.本質(zhì)上,搶紅包插件的原理不難理解,其過(guò)程就是在收到紅包時(shí),自動(dòng)模擬點(diǎn)擊.做過(guò)自動(dòng)化UI測(cè)試的童鞋應(yīng)該非常熟悉了.那么問(wèn)題來(lái)了,我們?cè)趺粗烙袥](méi)有紅包,又怎么模擬點(diǎn)擊操作呢?在PC端我們有按鍵精靈,那么在Android設(shè)備上呢?話說(shuō)也偶然,Google為了讓Android系統(tǒng)更實(shí)用,為用戶提供了無(wú)障礙輔助服務(wù)(AccessibilityService).

AccessibilityService運(yùn)行在后臺(tái),并且能夠收到由系統(tǒng)發(fā)出的一些事件(AccessibilityEvent,這些事件表示用戶界面一系列的狀態(tài)變化),比如焦點(diǎn)改變,輸入內(nèi)容變化,按鈕被點(diǎn)擊了等等,該種服務(wù)能夠請(qǐng)求獲取當(dāng)前活動(dòng)窗口并查找其中的內(nèi)容.換言之,界面中產(chǎn)生的任何變化都會(huì)產(chǎn)生一個(gè)時(shí)間,并由系統(tǒng)通知給AccessibilityService.這就像監(jiān)視器監(jiān)視著界面的一舉一動(dòng),一旦界面發(fā)生變化,立刻發(fā)出警報(bào).

是不是感覺(jué)很棒?接下來(lái),讓我們來(lái)看看如何AccessibilityService的基本使用,在不同的階段,對(duì)其中的一些點(diǎn)做深入的說(shuō)明,之后我們從實(shí)際應(yīng)用出發(fā),探討其中的一些使用場(chǎng)景.

深入AccessibilityService使用

1. 創(chuàng)建服務(wù)類

編寫(xiě)自己的服務(wù)類,需要繼承AccessibilityService類.其中要實(shí)現(xiàn)onAccessibilityEvent(AccessibilityEvent event)及onInterruput()兩個(gè)重要的方法:

publicclassRobServiceextendsAccessibilityService{@OverridepublicvoidonAccessibilityEvent(AccessibilityEvent event){? }@OverridepublicvoidonInterrupt(){? }

這里我們對(duì)該類常用的方法做下說(shuō)明,更詳細(xì)的內(nèi)容參見(jiàn)官方文檔

方法作用

disableSelf()禁用當(dāng)前服務(wù),也就是在服務(wù)可以通過(guò)該方法停止運(yùn)行

findFoucs(int falg)查找擁有特定焦點(diǎn)類型的控件

getRootInActiveWindow()如果配置能夠獲取窗口內(nèi)容,則會(huì)返回當(dāng)前活動(dòng)窗口的根結(jié)點(diǎn)

getSeviceInfo()獲取當(dāng)前服務(wù)的配置信息

onAccessibilityEvent(AccessibilityEvent event)有關(guān)AccessibilityEvent事件的回調(diào)函數(shù).系統(tǒng)通過(guò)sendAccessibiliyEvent()不斷的發(fā)送AccessibilityEvent到此處

performGlobalAction(int action)執(zhí)行全局操作,比如返回,回到主頁(yè),打開(kāi)最近等操作

setServiceInfo(AccessibilityServiceInfo info)設(shè)置當(dāng)前服務(wù)的配置信息

getSystemService(String name)獲取系統(tǒng)服務(wù)

onKeyEvent(KeyEvent event)如果允許服務(wù)監(jiān)聽(tīng)按鍵操作,該方法是按鍵事件的回調(diào),需要注意,這個(gè)過(guò)程發(fā)生了系統(tǒng)處理按鍵事件之前

onServiceConnected()系統(tǒng)成功綁定該服務(wù)時(shí)被觸發(fā),也就是當(dāng)你在設(shè)置中開(kāi)啟相應(yīng)的服務(wù),系統(tǒng)成功的綁定了該服務(wù)時(shí)會(huì)觸發(fā),通常我們可以在這里做一些初始化操作

2. 聲明服務(wù)

像其他Service服務(wù)一樣,需要在AndroidManifest.xml中聲明該服務(wù).除此之外,該服務(wù)還必須配置以下兩項(xiàng):

配置,其name為固定的:

android.accessibilityservice.AccessibilityService

聲明BIND_ACCESSIBILITY_SERVICE權(quán)限,以便系統(tǒng)能夠綁定該服務(wù)(4.0版本后要求)

注意:任何一點(diǎn)配置錯(cuò)誤,系統(tǒng)都無(wú)反應(yīng),因此其固定配置如下:

? ? ? ? ? ? ? ?

3. 服務(wù)參數(shù)設(shè)置

在AndroidManifest.xml聲明了該服務(wù)之后,接下來(lái)就是需要對(duì)該服務(wù)進(jìn)行一些參數(shù)設(shè)置了.該服務(wù)能夠被配置用來(lái)接受指定類型的事件,監(jiān)聽(tīng)指定package,檢索窗口內(nèi)容,獲取事件類型的時(shí)間等等.目前有兩種配置方法:

方法一:4.0之后提供了可以通過(guò)標(biāo)簽進(jìn)行配置

方法二:通過(guò)setServiceInfo()進(jìn)行配置

3.1. 通過(guò)進(jìn)行配置

在AndroidManifest.xml生命的的service中提供一個(gè)meta-data標(biāo)簽,然后通過(guò)android:resource指定相應(yīng)的配置文件(在res目錄下創(chuàng)建xml文件,并在其中創(chuàng)建配置文件accessibility.xml):

接下來(lái)我們來(lái)看accessibility.xml的相關(guān)配置:

后面,我們?cè)谥恍枰抡赵撆渲梦募鶕?jù)自己的需求進(jìn)行修改即可.下面我們會(huì)對(duì)這些屬性進(jìn)行介紹.

3.2 通過(guò)setServiceInfo(AccessibilityServiceInfo info)

也可以通過(guò)setServiceInfo(AccessibilityServiceInfo)為其配置信息,除此之外,通過(guò)該方法可以在運(yùn)行期間動(dòng)態(tài)修改服務(wù)配置.需要注意,該方法只能用來(lái)配置動(dòng)態(tài)屬性:eventTypes,feedbackType,flags,notificaionTimeout及packageNames.

通常是在onServiceConnected()進(jìn)行配置,如下代碼:

@OverrideprotectedvoidonServiceConnected(){? ? ? ? AccessibilityServiceInfo serviceInfo =newAccessibilityServiceInfo();? ? ? ? serviceInfo.eventTypes = AccessibilityEvent.TYPES_ALL_MASK;? ? ? ? serviceInfo.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;? ? ? ? serviceInfo.packageNames =newString[]{"com.tencent.mm"};? ? ? ? serviceInfo.notificationTimeout=100;? ? ? ? setServiceInfo(serviceInfo);? ? }

在這里涉及到了AccessibilityServiceInfo類,做個(gè)說(shuō)明:

AccessibilityServiceInfo該類被用于配置AccessibilityService信息,該類中包含了大量用于配置的常量字段及用來(lái)xml 屬性,比如常見(jiàn)的:

android:accessibilityEventTypes,android:canRequestFilterKeyEvents,android:packageNames等等,更多信息參見(jiàn)官方文檔

現(xiàn)在我們對(duì)配置中的重要屬性進(jìn)行說(shuō)明:

accessibilityEventTypes:表示該服務(wù)對(duì)界面中的哪些變化感興趣,即哪些事件通知,比如窗口打開(kāi),滑動(dòng),焦點(diǎn)變化,長(zhǎng)按等.具體的值可以在AccessibilityEvent類中查到,如typeAllMask表示接受所有的事件通知.

accessibilityFeedbackType:表示反饋方式,比如是語(yǔ)音播放,還是震動(dòng)

canRetrieveWindowContent:表示該服務(wù)能否訪問(wèn)活動(dòng)窗口中的內(nèi)容.也就是如果你希望在服務(wù)中獲取窗體內(nèi)容的化,則需要設(shè)置其值為true.

notificationTimeout:接受事件的時(shí)間間隔,通常將其設(shè)置為100即可.

packageNames:表示對(duì)該服務(wù)是用來(lái)監(jiān)聽(tīng)哪個(gè)包的產(chǎn)生的事件

4. 啟動(dòng)服務(wù)

當(dāng)我們做完以上操作,便可將app安裝到手機(jī).安裝成功后,在設(shè)置->輔助功能中便可以找到我們的服務(wù).該服務(wù)默認(rèn)處在關(guān)閉狀態(tài),需要手動(dòng)開(kāi)啟.

5. 獲取事件信息

上面我們說(shuō)道,onAccessibilityEvent(AccessibilityEvent event)是該服務(wù)的核心方法,其中參數(shù)event封裝來(lái)自界面相關(guān)事件的信息,比如我們可以獲得該事件的事件類型,進(jìn)而根據(jù)起類型選擇不同的處理方式:

publicvoidonAccessibilityEvent(AccessibilityEventevent){inteventType =event.getEventType();switch(eventType) {caseAccessibilityEvent.TYPE_VIEW_CLICKED://界面點(diǎn)擊break;caseAccessibilityEvent.TYPE_VIEW_TEXT_CHANGED://界面文字改動(dòng)break;? ? ? ? }? ? }

這里我們對(duì)AccessibilityEvent進(jìn)行簡(jiǎn)單的說(shuō)明:

當(dāng)用戶發(fā)生發(fā)生變化時(shí),系統(tǒng)會(huì)發(fā)送一系列的AccessibilityEvent事件,比如按鈕被點(diǎn)擊時(shí)會(huì)發(fā)送TYPE_VIEW_CLICKED類型的事件.

方法說(shuō)明

getEventType()事件類型

getSource()獲取事件源對(duì)應(yīng)的結(jié)點(diǎn)信息

getClassName()獲取事件源對(duì)應(yīng)類的類型,比如點(diǎn)擊事件是有某個(gè)Button產(chǎn)生的,那么此時(shí)獲取的就是Button的完整類名

getText()獲取事件源的文本信息,比如事件是有TextView發(fā)出的,此時(shí)獲取的就是TextView的text屬性.如果該事件源是樹(shù)結(jié)構(gòu),那么此時(shí)獲取的是這個(gè)樹(shù)上所有具有text屬性的值的集合

isEnabled()事件源(對(duì)應(yīng)的界面控件)是否處在可用狀態(tài)

getItemCount()如果事件源是樹(shù)結(jié)構(gòu),將返回該樹(shù)根節(jié)點(diǎn)下子節(jié)點(diǎn)的數(shù)量

系統(tǒng)不斷的產(chǎn)生各種事件,有些是界面控件產(chǎn)生的,有些是系統(tǒng)產(chǎn)生的.對(duì)于由界面控件的產(chǎn)生的事件,通常我們將該控件稱之為事件源.并不是所有的事件都能通過(guò)getSource()方法獲取到事件源,比如像通知消息類型的事件(TYPE_NOTIFICATION_STATE_CHANGED).

6. 獲取窗口內(nèi)容

僅僅知道事件的信息是不夠的,我們還希望通過(guò)事件來(lái)獲取發(fā)出該事件(事件源)的信息,比如Button按鈕被點(diǎn)擊時(shí)它的text.一個(gè)服務(wù)可以配置為允許服務(wù)檢索窗口內(nèi)容,即獲取窗口內(nèi)容.整個(gè)窗口內(nèi)容本質(zhì)上是關(guān)于AccessibilityWindowInfo和AccessibilityNodeInfo的樹(shù)結(jié)構(gòu),我稱之為內(nèi)容樹(shù).(類似View Tree,但由不完全相同)

需要注意,該服務(wù)可能配置了只檢測(cè)了部分事件,而不是全部事件,這就意味著,當(dāng)內(nèi)容樹(shù)發(fā)生變化后,該服務(wù)可能并不知道,即該服務(wù)無(wú)法及時(shí)的了解當(dāng)前的內(nèi)容樹(shù)是否發(fā)生了變化.比如說(shuō),你的服務(wù)只檢測(cè)了點(diǎn)擊事件,但是此時(shí)界面的輸入焦點(diǎn)已經(jīng)變化,這樣整個(gè)結(jié)點(diǎn)樹(shù)也發(fā)生了變化,但是你的服務(wù)卻不知道,此時(shí)你在結(jié)點(diǎn)中拿到的窗口內(nèi)容可能已經(jīng)不是最新的了.因此,如果你想及時(shí)的獲知當(dāng)前窗口的內(nèi)容,那么就在配置的時(shí)候,設(shè)置監(jiān)聽(tīng)全部事件.

正如上面所提到的,要想獲取窗口內(nèi)容,在配置AccessibilityService時(shí)需設(shè)置canRetrieveWindowContent為true.之后,便可以通過(guò)一下方法獲取窗口內(nèi)容:AccessibilityEvent.getSource(),findFocus(int),getWindow()或者getRootInActiveWindow()

7. 服務(wù)的生命周期

要理解該中服務(wù)的生命周期只需要記住一下三點(diǎn)即可:

該種服務(wù)完全由系統(tǒng)管理,并遵循已有的服務(wù)周期.

開(kāi)啟一個(gè)服務(wù)只能由用戶在設(shè)置中打開(kāi),而關(guān)閉則只能由用戶在設(shè)置中關(guān)閉或者服務(wù)本身通過(guò)diableSelf()方法關(guān)閉(當(dāng)然,現(xiàn)在有些第三放軟件也可以強(qiáng)制關(guān)閉該類型服務(wù))

系統(tǒng)綁定該服務(wù)之后,會(huì)調(diào)用onServiceConnected()方法,這個(gè)方法可以被重寫(xiě),在其中,你可以做一些初始化的操作.

8. 檢測(cè)服務(wù)是否開(kāi)啟

介紹了一些AccessibilityService的基礎(chǔ)知識(shí)之后,再補(bǔ)充一點(diǎn)關(guān)于檢測(cè)某個(gè)服務(wù)是否開(kāi)啟的知識(shí).通常來(lái)說(shuō)大體有一下兩種方法來(lái)檢測(cè)服務(wù)是否啟用:

方法一:借助服務(wù)管理器AccessibilityManager來(lái)判斷,但是該方法不能檢測(cè)app本身開(kāi)啟的服務(wù).

privatebooleanenabled(Stringname) {? ? ? ? AccessibilityManager am = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);List serviceInfos = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);List installedAccessibilityServiceList = am.getInstalledAccessibilityServiceList();? ? ? ? for (AccessibilityServiceInfo info : installedAccessibilityServiceList) {Log.d("MainActivity","all -->"+ info.getId());if(name.equals(info.getId())) {returntrue;? ? ? ? ? ? }? ? ? ? }returnfalse;? ? }

既然談到了AccessibilityManager,那么在這里我們就做個(gè)簡(jiǎn)單的介紹:

AccessibilityManager是系統(tǒng)級(jí)別的服務(wù),用來(lái)管理AccessibilityService服務(wù),比如分發(fā)事件,查詢系統(tǒng)中服務(wù)的狀態(tài)等等,更多信息參考官方文檔,常見(jiàn)方法如下:

方法說(shuō)明

getAccessibilityServiceList()獲取服務(wù)列表(api 14之后廢棄,用下面的方法代替)

getInstalledAccessibilityServiceList()獲取已安裝到系統(tǒng)的服務(wù)列表

getEnabledAccessibilityServiceList(int feedbackTypeFlags)獲取已啟用的服務(wù)列表

isEnabled()判斷服務(wù)是否啟用

sendAccessibilityEvent(AccessibilityEvent event)發(fā)送事件

方法二:我們知道大部分的系統(tǒng)屬性都在settings中進(jìn)行設(shè)置,比如wifi,藍(lán)牙狀態(tài)等,而這些信息主要是存儲(chǔ)在settings對(duì)應(yīng)的的數(shù)據(jù)庫(kù)中(system表和serure表),這就意味我們可以通過(guò)直接讀取setting設(shè)置來(lái)判斷相關(guān)服務(wù)是否開(kāi)啟:

privatebooleancheckStealFeature1(Stringservice) {intok =0;try{? ? ? ? ? ? ok = Settings.Secure.getInt(getApplicationContext().getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);? ? ? ? }catch(Settings.SettingNotFoundException e) {? ? ? ? }? ? ? ? TextUtils.SimpleStringSplitter ms =newTextUtils.SimpleStringSplitter(':');if(ok ==1) {StringsettingValue = Settings.Secure.getString(getApplicationContext().getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);if(settingValue !=null) {? ? ? ? ? ? ? ? ms.setString(settingValue);while(ms.hasNext()) {StringaccessibilityService = ms.next();if(accessibilityService.equalsIgnoreCase(service)) {returntrue;? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }? ? ? ? ? ? }

實(shí)際應(yīng)用

到現(xiàn)在有關(guān)AccessibilityService的一些知識(shí),我們已經(jīng)講完,下面我們就看它的具體使用,其中典型的應(yīng)用就是搶紅包插件.

1. 搶紅包插件

先回顧一下?lián)尲t包的的流程:

狀態(tài)欄出現(xiàn)"[微信紅包]"的消息提示,點(diǎn)擊進(jìn)入聊天界面

點(diǎn)擊相應(yīng)的紅包信息,彈出搶紅包界面

在搶紅包界面點(diǎn)擊"開(kāi)",打開(kāi)紅包

在紅包詳情頁(yè)面,查看詳情,點(diǎn)擊返回按鈕返回微信聊天界面.

以上是不在微信聊天界面時(shí)的流程.如果你所在的微信聊天窗口出現(xiàn)紅包,則不會(huì)執(zhí)行步驟1,而是直接執(zhí)行2,3,4.如果是在微信好友列表時(shí),收到紅包,則會(huì)在列表項(xiàng)中顯示[微信紅包],需要點(diǎn)即該列表項(xiàng),進(jìn)入聊天界面,隨后執(zhí)行2,3,4.為了方便演示,這里我們暫時(shí)不考慮好友列表時(shí)出現(xiàn)紅包的情況.

明白了搶紅包流程,之后我們通過(guò)AccessibilityService獲取通知欄信息及微信聊天窗口界面,繼而通過(guò)模擬點(diǎn)擊實(shí)現(xiàn)打開(kāi)紅包,搶紅包等操作.

AccessibilityService配置如下:


typeWindowContentChanged"android:accessibilityFeedbackType="feedbackGeneric"android:accessibilityFlags="flagDefault"android:canRetrieveWindowContent="true"android:notificationTimeout="100"android:packageNames="com.tencent.mm"/>

具體實(shí)現(xiàn)代碼如下:

publicclassRobServiceextendsAccessibilityService{? ? @OverridepublicvoidonAccessibilityEvent(AccessibilityEventevent){inteventType =event.getEventType();switch(eventType) {caseAccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:? ? ? ? ? ? ? ? handleNotification(event);break;caseAccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:caseAccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:? ? ? ? ? ? ? ? String className =event.getClassName().toString();if(className.equals("com.tencent.mm.ui.LauncherUI")) {? ? ? ? ? ? ? ? ? ? getPacket();? ? ? ? ? ? ? ? }elseif(className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyReceiveUI")){? ? ? ? ? ? ? ? ? ? openPacket();? ? ? ? ? ? ? ? }elseif(className.equals("com.tencent.mm.plugin.luckymoney.ui.LuckyMoneyDetailUI")){? ? ? ? ? ? ? ? ? ? close();? ? ? ? ? ? ? ? }break;? ? ? ? }? ? }/**

* 處理通知欄信息

*

* 如果是微信紅包的提示信息,則模擬點(diǎn)擊

*

* @param event

*/privatevoidhandleNotification(AccessibilityEventevent){? ? ? ? List texts =event.getText();if(!texts.isEmpty()) {for(CharSequence text : texts) {? ? ? ? ? ? ? ? String content = text.toString();//如果微信紅包的提示信息,則模擬點(diǎn)擊進(jìn)入相應(yīng)的聊天窗口if(content.contains("[微信紅包]")) {if(event.getParcelableData() !=null&&event.getParcelableData() instanceof Notification) {? ? ? ? ? ? ? ? ? ? ? ? Notification notification = (Notification)event.getParcelableData();? ? ? ? ? ? ? ? ? ? ? ? PendingIntent pendingIntent = notification.contentIntent;try{? ? ? ? ? ? ? ? ? ? ? ? ? ? pendingIntent.send();? ? ? ? ? ? ? ? ? ? ? ? }catch(PendingIntent.CanceledException e) {? ? ? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();? ? ? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }? ? }/**

* 關(guān)閉紅包詳情界面,實(shí)現(xiàn)自動(dòng)返回聊天窗口

*/@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)privatevoidclose(){? ? ? ? AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();if(nodeInfo !=null) {//為了演示,直接查看了關(guān)閉按鈕的idList infos = nodeInfo.findAccessibilityNodeInfosByViewId("@id/ez");? ? ? ? ? ? nodeInfo.recycle();for(AccessibilityNodeInfo item : infos) {? ? ? ? ? ? ? ? item.performAction(AccessibilityNodeInfo.ACTION_CLICK);? ? ? ? ? ? }? ? ? ? }? ? }/**

* 模擬點(diǎn)擊,拆開(kāi)紅包

*/@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)privatevoidopenPacket(){? ? ? ? AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();if(nodeInfo !=null) {//為了演示,直接查看了紅包控件的idList list = nodeInfo.findAccessibilityNodeInfosByViewId("@id/b9m");? ? ? ? ? ? nodeInfo.recycle();for(AccessibilityNodeInfo item : list) {? ? ? ? ? ? ? ? item.performAction(AccessibilityNodeInfo.ACTION_CLICK);? ? ? ? ? ? }? ? ? ? }? ? }/**

* 模擬點(diǎn)擊,打開(kāi)搶紅包界面

*/@TargetApi(Build.VERSION_CODES.JELLY_BEAN)privatevoidgetPacket(){? ? ? ? AccessibilityNodeInfo rootNode = getRootInActiveWindow();? ? ? ? AccessibilityNodeInfo node = recycle(rootNode);? ? ? ? node.performAction(AccessibilityNodeInfo.ACTION_CLICK);? ? ? ? AccessibilityNodeInfo parent = node.getParent();while(parent !=null) {if(parent.isClickable()) {? ? ? ? ? ? ? ? parent.performAction(AccessibilityNodeInfo.ACTION_CLICK);break;? ? ? ? ? ? }? ? ? ? ? ? parent = parent.getParent();? ? ? ? }? ? }/**

* 遞歸查找當(dāng)前聊天窗口中的紅包信息

*

* 聊天窗口中的紅包都存在"領(lǐng)取紅包"一詞,因此可根據(jù)該詞查找紅包

*

* @param node

*/publicAccessibilityNodeInforecycle(AccessibilityNodeInfo node){if(node.getChildCount() ==0) {if(node.getText() !=null) {if("領(lǐng)取紅包".equals(node.getText().toString())) {returnnode;? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }else{for(inti =0; i < node.getChildCount(); i++) {if(node.getChild(i) !=null) {? ? ? ? ? ? ? ? ? ? recycle(node.getChild(i));? ? ? ? ? ? ? ? }? ? ? ? ? ? }? ? ? ? }returnnode;? ? }? ? @OverridepublicvoidonInterrupt(){? ? }? ? @OverrideprotectedvoidonServiceConnected(){? ? ? ? super.onServiceConnected();? ? }}

上面的代碼簡(jiǎn)單演示了搶紅包的原理,為了方便起見(jiàn),我直接通過(guò)findAccessibilityNodeInfosByViewId()獲取制定id控件.在實(shí)際中,這種方法不太可靠,到目前為止,微信已經(jīng)改過(guò)幾次相關(guān)控件的id了.

有童鞋問(wèn),怎么樣知道該控件的id呢.其實(shí)很簡(jiǎn)單,android中已經(jīng)為我們提供了相關(guān)的工具:在Android Studio中開(kāi)啟Android Device Monitor,選擇設(shè)備后點(diǎn)擊Dump View Hierarchy for UI Automator,如下:

這里寫(xiě)圖片描述

稍等片刻之后,便會(huì)出現(xiàn)當(dāng)前設(shè)備的窗口,在該窗口中點(diǎn)擊相關(guān)控件,便會(huì)顯示該控件的屬性.借助該工具,可以幫我們快速的分析界面結(jié)構(gòu),幫助我們從其他app布局策略中學(xué)習(xí)

這里寫(xiě)圖片描述

我們用Dump View Hierarchy for UI Automator分析聊天界面微信紅包信息:

這里寫(xiě)圖片描述

搶紅包界面:

這里寫(xiě)圖片描述

2. App自動(dòng)安裝

講完了微信紅包插件的實(shí)現(xiàn)原理,不難發(fā)現(xiàn)其本質(zhì)是根據(jù)相關(guān)的界面狀態(tài),模擬后續(xù)的操作(比如點(diǎn)擊等).

既然這樣,那么我們完全可以利用該服務(wù)實(shí)現(xiàn)更多的功能,比如apk自動(dòng)安裝,傳統(tǒng)的安裝過(guò)程大概是如下流程:

點(diǎn)擊apk文件,彈出安裝信息界面,在該界面點(diǎn)擊"下一步",然后在點(diǎn)擊"安裝",最后在安裝完成界面點(diǎn)擊"完成".

不難發(fā)現(xiàn),該流程完全可以通過(guò)模擬點(diǎn)擊操作完成.現(xiàn)在我們簡(jiǎn)單的講一下AccessibilityService在這方面的具體應(yīng)用:

我們知道系統(tǒng)的安裝程序由PackageInstaller負(fù)責(zé),其包名是com.android.packageinstaller,那么我們只需要監(jiān)聽(tīng)該package下的安裝信息界面和安裝完成界面,并模擬點(diǎn)擊"下一步","安裝",完成""操作即可實(shí)現(xiàn)自動(dòng)安裝.

AccessibilityService配置如下:

具體實(shí)現(xiàn)代碼如下:

publicclassInstallServiceextendsAccessibilityService{? ? @OverridepublicvoidonAccessibilityEvent(AccessibilityEventevent){? ? ? ? Log.d("InstallService",event.toString());? ? ? ? checkInstall(event);? ? }privatevoidcheckInstall(AccessibilityEventevent){? ? ? ? AccessibilityNodeInfo source =event.getSource();if(source !=null) {? ? ? ? ? ? boolean installPage =event.getPackageName().equals("com.android.packageinstaller");if(installPage) {? ? ? ? ? ? ? ? installAPK(event);? ? ? ? ? ? }? ? ? ? }? ? }? ? @TargetApi(Build.VERSION_CODES.JELLY_BEAN)privatevoidinstallAPK(AccessibilityEventevent){? ? ? ? AccessibilityNodeInfo source = getRootInActiveWindow();? ? ? ? List nextInfos = source.findAccessibilityNodeInfosByText("下一步");? ? ? ? nextClick(nextInfos);? ? ? ? List installInfos = source.findAccessibilityNodeInfosByText("安裝");? ? ? ? nextClick(installInfos);? ? ? ? List openInfos = source.findAccessibilityNodeInfosByText("打開(kāi)");? ? ? ? nextClick(openInfos);? ? ? ? runInBack(event);? ? }privatevoidrunInBack(AccessibilityEventevent){event.getSource().performAction(AccessibilityService.GLOBAL_ACTION_BACK);? ? }privatevoidnextClick(List infos){if(infos !=null)for(AccessibilityNodeInfo info : infos) {if(info.isEnabled() && info.isClickable())? ? ? ? ? ? ? ? ? ? info.performAction(AccessibilityNodeInfo.ACTION_CLICK);? ? ? ? ? ? }? ? }? ? @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)privatebooleancheckTilte(AccessibilityNodeInfo source){? ? ? ? List infos = getRootInActiveWindow().findAccessibilityNodeInfosByViewId("@id/app_name");for(AccessibilityNodeInfo nodeInfo : infos) {if(nodeInfo.getClassName().equals("android.widget.TextView")) {returntrue;? ? ? ? ? ? }? ? ? ? }returnfalse;? ? }? ? @OverridepublicvoidonInterrupt(){? ? }? ? @OverrideprotectedvoidonServiceConnected(){? ? ? ? Log.d("InstallService","auto install apk");? ? }}

3. 檢測(cè)前臺(tái)服務(wù):

在很多情況下,我們需要檢測(cè)自己的app是不是處在前臺(tái),借助該服務(wù)同樣也能夠完成該檢測(cè)操作.下面,我們就演示一下如何實(shí)現(xiàn):

AccessibilityService配置如下:

具體實(shí)現(xiàn)代碼如下:

publicclassDetectionServiceextendsAccessibilityService{privatestaticvolatileString foregroundPackageName ="error";/**? ? * 檢測(cè)是否是前臺(tái)服務(wù)? ? *? ? *@parampackagenName? ? *@return*/publicstaticbooleanisForeground(String packagenName){returnforegroundPackageName.equals(packagenName);? ? }@OverridepublicvoidonAccessibilityEvent(AccessibilityEvent event){if(event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {? ? ? ? ? ? foregroundPackageName = event.getPackageName().toString();? ? ? ? }? ? }@OverridepublicvoidonInterrupt(){? ? }}

在使用時(shí),需要引導(dǎo)用戶開(kāi)啟該服務(wù),之后通過(guò)調(diào)用DetectionService.isForeground()即可.

4. 竊取信息

上面的所有示例演示的都是AccessibilityService在具體應(yīng)用中發(fā)揮的良好作用.但是該服務(wù)也存在一定的風(fēng)險(xiǎn),很多人利用該服務(wù)做一些"壞事",比如竊取短信驗(yàn)證碼,竊取短信內(nèi)容,想要看看自己女朋友最近在和誰(shuí)聊QQ,偷偷安裝流氓軟件等.

上面我們了解微信搶紅包插件的原理,那么利用該AccessibilityService編寫(xiě)相應(yīng)的反搶紅包插件:通過(guò)模擬微信紅包的通知信息,發(fā)送虛假的事件通知.不出意外,我們編寫(xiě)的反搶紅包插件會(huì)讓失眠絕大多數(shù)的搶紅包插件.這里我就不做深入的解釋,有興趣的同學(xué)可以自行研究.

你現(xiàn)在是不是想能否借助該服務(wù)直接獲取一些app的密碼呢?凡是EditText中設(shè)置inputType為password類型的,都無(wú)法獲取其輸入值.除此之外,大多數(shù)軟件都針對(duì)該中風(fēng)險(xiǎn)做了提前的防范.因此,你想要借助該服務(wù)來(lái)實(shí)現(xiàn)竊取密碼還是比較有難度的,所以,少年覺(jué)悟吧.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末股耽,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子原在,更是在濱河造成了極大的恐慌,老刑警劉巖嫩舟,帶你破解...
    沈念sama閱讀 221,820評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件骤素,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡灵临,警方通過(guò)查閱死者的電腦和手機(jī)桅狠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門讼载,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人中跌,你說(shuō)我怎么就攤上這事咨堤。” “怎么了漩符?”我有些...
    開(kāi)封第一講書(shū)人閱讀 168,324評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵一喘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我嗜暴,道長(zhǎng)凸克,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,714評(píng)論 1 297
  • 正文 為了忘掉前任闷沥,我火速辦了婚禮触徐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘狐赡。我一直安慰自己,他們只是感情好疟丙,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,724評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布颖侄。 她就那樣靜靜地躺著,像睡著了一般享郊。 火紅的嫁衣襯著肌膚如雪览祖。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 52,328評(píng)論 1 310
  • 那天炊琉,我揣著相機(jī)與錄音展蒂,去河邊找鬼又活。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锰悼,可吹牛的內(nèi)容都是我干的柳骄。 我是一名探鬼主播,決...
    沈念sama閱讀 40,897評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼箕般,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼耐薯!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起丝里,我...
    開(kāi)封第一講書(shū)人閱讀 39,804評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤曲初,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后杯聚,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體臼婆,經(jīng)...
    沈念sama閱讀 46,345評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,431評(píng)論 3 340
  • 正文 我和宋清朗相戀三年幌绍,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了颁褂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,561評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡纷捞,死狀恐怖痢虹,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情主儡,我是刑警寧澤奖唯,帶...
    沈念sama閱讀 36,238評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站糜值,受9級(jí)特大地震影響丰捷,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜寂汇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,928評(píng)論 3 334
  • 文/蒙蒙 一病往、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧骄瓣,春花似錦停巷、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,417評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至扒磁,卻和暖如春庆揪,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背妨托。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,528評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工缸榛, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吝羞,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,983評(píng)論 3 376
  • 正文 我出身青樓内颗,卻偏偏與公主長(zhǎng)得像钧排,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子起暮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,573評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理卖氨,服務(wù)發(fā)現(xiàn),斷路器负懦,智...
    卡卡羅2017閱讀 134,702評(píng)論 18 139
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,297評(píng)論 25 707
  • ¥開(kāi)啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開(kāi)一個(gè)線程筒捺,因...
    小菜c閱讀 6,444評(píng)論 0 17
  • 晚風(fēng)如醉 思念你的心 就像那漲潮的海水 奔騰著 永不退卻 每個(gè)夜晚 都是思念你的夜晚 每次,你都會(huì)調(diào)皮的 悄悄的 ...
    水果沙拉黑閱讀 176評(píng)論 0 1
  • 時(shí)間管理〔習(xí)慣〕: 今早聽(tīng)得印象最深的一句話是:管理時(shí)間的基礎(chǔ)是精力管理(能量――身體的體能,情緒颗品,精神肯尺,情...
    曦哈小子閱讀 176評(píng)論 0 0