摘要
在Android程序的開(kāi)發(fā)維護(hù)過(guò)程中,我們可能經(jīng)常需要知道自己所看到的界面處于哪一個(gè)Activity中,相信大部分程序員的做法是“在基類(lèi)里打Log”,很傳統(tǒng)沒(méi)毛病o(╯□╰)o....
前幾天,在應(yīng)用寶的應(yīng)用商店內(nèi)發(fā)現(xiàn)了一個(gè)很有意思的APP - 當(dāng)前Activity,可以顯示出當(dāng)前界面上顯示的 應(yīng)用包名 和 Activity類(lèi)名。然后,一直很好奇是如何實(shí)現(xiàn)的,最近抽時(shí)間研究了一下。
下面是研究成果:
開(kāi)源項(xiàng)目CurrentActivity地址:
https://github.com/sinawangnan7/CurrentActivity
歡迎Star...(??????)??
正文
本文主要講述兩部分內(nèi)容:
1、CurrentActivity的使用場(chǎng)景
2、CurrentActivity的實(shí)現(xiàn)思路
CurrentActivity的使用場(chǎng)景
1、定位自己APP的頁(yè)面,獲取類(lèi)名稱(chēng)
這應(yīng)該是我們經(jīng)常碰到的問(wèn)題,測(cè)試人員經(jīng)常拿著出Bug的頁(yè)面詢(xún)問(wèn)“開(kāi)發(fā)人員”,如果這個(gè)頁(yè)面還不是“此開(kāi)發(fā)人員”寫(xiě)的,估計(jì)查起來(lái)就費(fèi)勁了。此工具能幫助開(kāi)發(fā)人員提高查找效率。
2、分析其他APP命名及功能
可以查看其他APP的頁(yè)面路徑和命名,給自己開(kāi)發(fā)做個(gè)參考(下面以微信和支付寶為例)。
(1) 微信
觀察上圖,可以看到 微信 的Activity在命名時(shí)是以 "UI" 為后綴進(jìn)行標(biāo)識(shí)的,類(lèi)在分包時(shí)也放在了ui包目錄下。另外,還有一個(gè)比較有意思的發(fā)現(xiàn),微信的“主界面”和“聊天界面”用的是同一個(gè)Activity。
(2) 支付寶
筆者公司開(kāi)發(fā)的APP和支付寶APP類(lèi)似,也是做 互聯(lián)網(wǎng)金融 的。所以,產(chǎn)品經(jīng)理在設(shè)計(jì)功能和流程時(shí)經(jīng)常會(huì)“參考”支付寶是如何實(shí)現(xiàn)的,比如需要確定 哪些頁(yè)面用原生做,哪些需要用H5來(lái)做。但是,支付寶的原生和H5在使用體驗(yàn)上很難感知出來(lái)(這里支付寶做得很贊),這時(shí)這個(gè)工具就可以派上用場(chǎng)了,支付寶展示H5的頁(yè)面用的是H5Activity。
最近,筆者需要做一個(gè)類(lèi)似支付寶的支付鍵盤(pán),一開(kāi)始以為是純Dialog實(shí)現(xiàn)的,最后發(fā)現(xiàn)實(shí)際上用Activity包了一層。這其實(shí)也并不意外,第三方APP在吊起“支付寶支付”時(shí),用戶(hù)如果安裝了支付寶,吊起的也是這個(gè)FlyBirdWindowActivity。
CurrentActivity的實(shí)現(xiàn)思路
這個(gè)監(jiān)視活動(dòng)的窗口是如何實(shí)現(xiàn)的?
本文只講述 思路和核心代碼,完整代碼請(qǐng)參看GitHub開(kāi)源項(xiàng)目:
CurrentActivity
實(shí)現(xiàn)這個(gè)監(jiān)視窗口需要解決兩個(gè)問(wèn)題:
1、如何向界面頂部添加一個(gè)顯示窗口?
2、如何監(jiān)聽(tīng)?wèi)?yīng)用切換、Activity切換?
第一個(gè)問(wèn)題很簡(jiǎn)單,通過(guò)WindowManager添加一個(gè)TextView即可, 但是需要注意下懸浮窗權(quán)限問(wèn)題,這個(gè)問(wèn)題很多博客都給出了解決方案,筆者簡(jiǎn)單說(shuō)下,就不在贅述了。
筆者做了一個(gè)簡(jiǎn)單的窗口視圖管理器:
創(chuàng)建窗口的核心代碼如下:
/**
* 添加窗口視圖
*/
private void addView() {
// 創(chuàng)建布局參數(shù)
mParams = new WindowManager.LayoutParams();
// 獲取窗口管理器
mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
// 設(shè)置類(lèi)型
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
// Android O 以上,使用TYPE_APPLICATION_OVERLAY彈窗類(lèi)型
mParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
} else {
// Android O 以下,使用TYPE_SYSTEM_ALERT彈窗類(lèi)型
mParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
}
// 設(shè)置標(biāo)簽(FLAG_NOT_FOCUSABLE表示窗口不會(huì)獲取焦點(diǎn);FLAG_NOT_TOUCHABLE表示窗口不會(huì)接收Touch事件,即將Touch事件向下層分發(fā))
mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
// 設(shè)置位圖模式 (PixelFormat.RGBA_8888可以使背景透明。不設(shè)置默認(rèn)PixelFormat.OPAQUE,即不透明)
mParams.format = PixelFormat.RGBA_8888;
// 設(shè)置分布位置(距左對(duì)齊 + 距頂對(duì)齊)
mParams.gravity = Gravity.LEFT | Gravity.TOP;
// 設(shè)置布局寬/高為自適應(yīng)
mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
// 添加TextView
mWindowManager.addView(mTextView, mParams);
// 記錄視圖已被添加、顯示
isAdded = true;
isShow = true;
}
提醒:
1.注意懸浮窗權(quán)限問(wèn)題,可參考 AndroidManifest.xml 中的權(quán)限聲明。
2.適配Android 8.0,請(qǐng)使用TYPE_APPLICATION_OVERLAY彈窗類(lèi)型。
第二個(gè)問(wèn)題:如何監(jiān)聽(tīng)?wèi)?yīng)用切換、Activity切換?筆者查了很長(zhǎng)時(shí)間,最后發(fā)現(xiàn)了一個(gè)叫 AccessibilityService(輔助服務(wù))的東西,了解完 AccessibilityService 感覺(jué)好像發(fā)現(xiàn)了新大陸,其實(shí)這個(gè)“輔助服務(wù)”使用最為出名的是“微信自動(dòng)搶紅包插件”。但是,今天筆者只是簡(jiǎn)單介紹下AccessibilityService(輔助服務(wù))的入門(mén)使用 - 監(jiān)聽(tīng)窗口改變。
輔助服務(wù)創(chuàng)建步驟及使用流程
1.創(chuàng)建自定義AccessibilityService,核心代碼如下:
/**
* @ClassName: MAccessibilityService
* @Description: 輔助服務(wù)
* @Author wangnan7
* @Date: 2018/4/1
*/
public class MAccessibilityService extends AccessibilityService {
......
/**
* 服務(wù)連接完成
*/
@Override
protected void onServiceConnected() {
// 添加窗口
mWindowViewContainer = WindowViewContainer.getInstance(this);
mWindowViewContainer.addWindowView();
......
}
......
/**
* 接收輔助服務(wù)事件
*/
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event == null) {
return;
}
switch (event.getEventType()) {
case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED: // 窗口狀態(tài)改變
if (event.getPackageName() != null && event.getClassName() != null) {
// 更新窗口視圖
mWindowViewContainer.updateWindowView(event.getPackageName() + "\n" + event.getClassName());
}
break;
default:
break;
}
}
/**
* 服務(wù)中斷
*/
@Override
public void onInterrupt() {
}
/**
* 服務(wù)退出
*/
@Override
public void onDestroy() {
// 移除窗口,銷(xiāo)毀視圖容器
mWindowViewContainer.destory();
......
}
}
整個(gè)功能的核心都在onAccessibilityEvent(AccessibilityEvent event)
這個(gè)方法中,如果開(kāi)啟輔助服務(wù),只要服務(wù)不死,進(jìn)程不掛,當(dāng)前窗口的每次改變我們都能監(jiān)聽(tīng)到。(如果你查看了完整代碼,會(huì)看到筆者添加了一個(gè)Notification,把輔助服務(wù)提升為了前臺(tái)服務(wù),這么做只是簡(jiǎn)單的做下服務(wù)保活)
2.在AndroidManifest.xml文件中聲明該服務(wù), 核心代碼如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.wangnan.currentactivity">
......
<application
......
<!-- 輔助服務(wù) -->
<service
android:name=".service.MAccessibilityService"
android:label="當(dāng)前Activity(輔助工具)"
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>
......
</application>
</manifest>
完整代碼:AndroidManifest.xml
詳細(xì)說(shuō)下輔助服務(wù)的配置聲明:
1.android:name 用于指定服務(wù)名稱(chēng),前面加“.”表示書(shū)寫(xiě)時(shí)省略掉了包名。
2.android:lable 用于指定外部顯示的服務(wù)名稱(chēng)。
3.android:permission 表明服務(wù)向系統(tǒng)請(qǐng)求那些權(quán)限
4.intent-filter 的 action 表明傳遞給該服務(wù)的哪些Intent需要過(guò)濾出來(lái)進(jìn)行處理
可能會(huì)有人疑問(wèn)為什么要配置3和4,其實(shí)這是源碼文檔要求的,是必須配置的,如下圖所示:
source_code.png
5.meta-data 元數(shù)據(jù),用于配置輔助服務(wù)的詳情信息
其實(shí)這一項(xiàng)在manifest文件中是可配可不配的,只是文檔支持在manifest文件里進(jìn)行配置,如果不在這配置就需要到輔助服務(wù)的Java代碼里去配置了,源碼解釋如下:
source_code2.png
接下來(lái),看下筆者的元數(shù)據(jù)配置文件(android:resource="@xml/accessibility")
完整代碼:accessibility.xml
元數(shù)據(jù)配置文件說(shuō)明,如下圖所示:
3.開(kāi)啟輔助服務(wù)
開(kāi)啟輔助服務(wù)不能用startService()
這種方式打開(kāi),因?yàn)槭褂幂o助服務(wù)是有一定風(fēng)險(xiǎn)的,需要用戶(hù)主動(dòng)授權(quán)(同意開(kāi)啟)。
輔助服務(wù)打開(kāi)流程:
安裝APP(含有輔助服務(wù)) -> 輔助功能 -> 服務(wù)(點(diǎn)擊需要開(kāi)啟的服務(wù)) -> 開(kāi)啟
以筆者的手機(jī)(360N5)和 CurrentActivity 為例,打開(kāi)流程如下圖所示:
4.關(guān)閉輔助服務(wù)
輔助功能 -> 服務(wù)(點(diǎn)擊需要關(guān)閉的服務(wù)) -> 關(guān)閉
以筆者的手機(jī)(360N5)和 CurrentActivity 為例,關(guān)閉流程如下圖所示:
CurrentActivity的詳細(xì)使用方法和源碼請(qǐng)參看Github:
其他
1.AccessibilityService位于在系統(tǒng)設(shè)置里,有些手機(jī)翻譯成“輔助服務(wù)”,有些手機(jī)翻譯成“無(wú)障礙”。
2.AccessibilityService被用戶(hù)授權(quán)開(kāi)啟后并不是一直保活的,也有可能被系統(tǒng)銷(xiāo)毀。
題外話
輔助服務(wù)其實(shí)還有很多使用場(chǎng)景。比如,從上圖筆者手機(jī)應(yīng)用列表里看到的云服務(wù)、360手機(jī)助手(下載自動(dòng)安裝)、京東金融(手環(huán)社交軟件提醒)。另外,還有我們經(jīng)常聽(tīng)說(shuō)的微信自動(dòng)搶紅包、微信自動(dòng)回復(fù)...各位如果有興趣可以研究下。Good Luck~
最后的福利(2018年4月16號(hào)添加):
寫(xiě)完這篇博客后,筆者又研究了下微信搶紅包原理,寫(xiě)了一個(gè)【微信自動(dòng)搶紅包】插件,原理也是使用輔助服務(wù),開(kāi)源項(xiàng)目地址:
https://github.com/sinawangnan7/WXGiftMoney
歡迎Star...(??????)??