4種獲取前臺應(yīng)用的方法(肯定有你不知道的)

轉(zhuǎn)載注明出處:簡書-十個雨點

我目前已知走贪,并且嘗試過的獲取當(dāng)前前臺應(yīng)用的方法有如下幾種:

  1. Android5.0以前蓄拣,使用ActivityManager的getRunningTasks()方法,可以得到應(yīng)用包名和Activity整葡;
  2. Android5.0以后村象,通過使用量統(tǒng)計功能來實現(xiàn)温艇,只能得到應(yīng)用包名逻恐;
  3. 通過輔助服務(wù)來實現(xiàn)像吻,可以得到包名和Activity峻黍;
  4. Android5.0以后,可以通過設(shè)備輔助應(yīng)用程序來實現(xiàn)拨匆,能得到包名和Activity姆涩,不過這種方式必須用戶主動觸發(fā)(長按Home鍵)

一、ActivityManager的getRunningTasks方法

這是大家可能都知道的方法惭每。在Android5.0以前阵面,只要以下代碼就可以獲得前臺應(yīng)用:

ActivityManager activityManager = (ActivityManager)context.getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
ComponentName runningTopActivity = activityManager.getRunningTasks(1).get(0).topActivity;

還需要聲明權(quán)限:

<uses-permission android:name="android.permission.GET_TASKS" />

這種方法不止能獲取包名,還能獲取Activity名洪鸭。但是在Android 5.0以后,系統(tǒng)就不再對第三方應(yīng)用提供這種方式來獲取前臺應(yīng)用了仑扑,雖然調(diào)用這個方法還是能夠返回結(jié)果览爵,但是結(jié)果只包含你自己的Activity和Launcher了。

二镇饮、通過使用量統(tǒng)計功能獲取前臺應(yīng)用

stackoverflow will find a way蜓竹,getRunningTasks方法失效以后,基本上搜索到的方案都是通過使用量統(tǒng)計功能來獲取储藐,也就是下面這種方式:

UsageStatsManager mUsageStatsManager = (UsageStatsManager)context.getApplicationContext().getSystemService(Context.USAGE_STATS_SERVICE);
long time = System.currentTimeMillis();
List<UsageStats> stats ;
if (isFirst){
    stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - TWENTYSECOND, time);
}else {
    stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - THIRTYSECOND, time);
}
// Sort the stats by the last time used
if(stats != null) {
    TreeMap<Long,UsageStats> mySortedMap = new TreeMap<Long,UsageStats>();
    start=System.currentTimeMillis();
    for (UsageStats usageStats : stats) {
        mySortedMap.put(usageStats.getLastTimeUsed(),usageStats);
    }
    LogUtil.e(TAG,"isFirst="+isFirst+",mySortedMap cost:"+ (System.currentTimeMillis()-start));
    if(mySortedMap != null && !mySortedMap.isEmpty()) {                    
        
        topPackageName =  mySortedMap.get(mySortedMap.lastKey()).getPackageName();      
        
        runningTopActivity=new ComponentName(topPackageName,"");
        if (LogUtil.isDebug())LogUtil.d(TAG,topPackageName);
    }
}

代碼的功能是通過UsageStatsManager 來獲取用戶使用的程序的列表俱济,然后按照最近使用時間排序,就得到了當(dāng)前的前臺應(yīng)用钙勃,這種方式只能拿到包名蛛碌,無法精確了Activity了。
使用這種方發(fā)之前辖源,首先要引導(dǎo)用戶開啟使用量功能:

Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
startActivity(intent);

還要申明權(quán)限:

<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />

要注意的是蔚携,只是這樣并不夠!因為在一些手機上克饶,應(yīng)用發(fā)起通知欄消息的時候酝蜒,或者是下拉通知欄,也會被記錄到使用量中矾湃,就會導(dǎo)致按最近時間排序出現(xiàn)混亂亡脑。而且收起通知欄以后,這種混亂并不會被修正邀跃,而是必須重新開啟一個應(yīng)用才行霉咨。
比如下圖:

打開通知欄導(dǎo)致前臺應(yīng)用判斷錯誤.gif

圖中應(yīng)用的功能是每隔1秒判斷當(dāng)前的前臺應(yīng)用,如果是Chrome的話坞嘀,則把懸浮窗(眼睛圖案)隱藏躯护,否則保持顯示。圖中可見丽涩,剛打開Chrome的時候棺滞,懸浮窗隱藏了裁蚁,但是下拉通知欄,懸浮窗就又出現(xiàn)了继准。

那怎么辦呢枉证?萬能的StackOverflow也不能了,我只好自己研究移必,通過仔細對比室谚,我發(fā)現(xiàn)UsageStats有一個hide的字段似乎有些蹊蹺——mLastEvent,如下圖崔泵。

新建位圖圖像_看圖王.jpg

我發(fā)現(xiàn)對于打開的在前臺的應(yīng)用mLastEvent=1秒赤,而對于通知欄收到消息的應(yīng)用,則mLastEvent憎瘸!=1入篮,有時為2,有時為0幌甘〕笔郏看看UsageStats的源碼,沒發(fā)現(xiàn)有用信息锅风,但是UsageStatsManager還有個方法queryEvents酥诽,返回值是UsageEvents類型,同樣是Event會不會有什么相同的地方呢皱埠?果然肮帐,UsageEvents的內(nèi)部類有一個Event,它包含兩個常量定義:

/**
 * An event type denoting that a component moved to the foreground.
 */
public static final int MOVE_TO_FOREGROUND = 1;

/**
 * An event type denoting that a component moved to the background.
 */
public static final int MOVE_TO_BACKGROUND = 2;

此時我們不妨樂觀的假設(shè)边器,這兩個值分別就是UsageStats.mLastEvent中的1和2泪姨,從名字上就能看得出含義,正是我們需要的值饰抒。
帶著假設(shè)去源碼中尋找答案肮砾,會發(fā)現(xiàn)源碼中充斥著類似下面的代碼:

//下面代碼來自com.android.server.usage.IntervalStats
private boolean isStatefulEvent(int eventType) {
   switch (eventType) {
       case UsageEvents.Event.MOVE_TO_FOREGROUND:
       case UsageEvents.Event.MOVE_TO_BACKGROUND:
       case UsageEvents.Event.END_OF_DAY:
       case UsageEvents.Event.CONTINUE_PREVIOUS_DAY:
          return true;
   }
   return false;
}

void update(String packageName, long timeStamp, int eventType) {
  UsageStats usageStats = getOrCreateUsageStats(packageName);

  // TODO(adamlesinski): Ensure that we recover from incorrect event sequences
   // like double MOVE_TO_BACKGROUND, etc.
   if (eventType == UsageEvents.Event.MOVE_TO_BACKGROUND ||
           eventType == UsageEvents.Event.END_OF_DAY) {
       if (usageStats.mLastEvent == UsageEvents.Event.MOVE_TO_FOREGROUND ||
               usageStats.mLastEvent == UsageEvents.Event.CONTINUE_PREVIOUS_DAY) {
           usageStats.mTotalTimeInForeground += timeStamp - usageStats.mLastTimeUsed;
       }
   }

   if (isStatefulEvent(eventType)) {
       usageStats.mLastEvent = eventType;
   }

   if (eventType != UsageEvents.Event.SYSTEM_INTERACTION) {
       usageStats.mLastTimeUsed = timeStamp;
   }
   usageStats.mLastTimeSystemUsed = timeStamp;
   usageStats.mEndTimeStamp = timeStamp;

   if (eventType == UsageEvents.Event.MOVE_TO_FOREGROUND) {
       usageStats.mLaunchCount += 1;
   }

   endTime = timeStamp;
}

可見 usageStats.mLastEvent就對應(yīng)著UsageEvents.Event中的常量。那么我們要做的就很簡單了袋坑,只要將queryUsageStats()方法得到的結(jié)果按最后使用時間降序排列仗处,然后取第一個mLastEvent ==1的元素即可。代碼和效果圖如下:

//改進版本的通過使用量統(tǒng)計功能獲取前臺應(yīng)用
UsageStatsManager mUsageStatsManager = (UsageStatsManager)context.getApplicationContext().getSystemService(Context.USAGE_STATS_SERVICE);
long time = System.currentTimeMillis();
List<UsageStats> stats ;
if (isFirst){
    stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - TWENTYSECOND, time);
}else {
    stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - THIRTYSECOND, time);
}
// Sort the stats by the last time used
if(stats != null) {
    TreeMap<Long,UsageStats> mySortedMap = new TreeMap<Long,UsageStats>();
    start=System.currentTimeMillis();
    for (UsageStats usageStats : stats) {
        mySortedMap.put(usageStats.getLastTimeUsed(),usageStats);
    }
    LogUtil.e(TAG,"isFirst="+isFirst+",mySortedMap cost:"+ (System.currentTimeMillis()-start));
    if(mySortedMap != null && !mySortedMap.isEmpty()) {                   

        NavigableSet<Long> keySet=mySortedMap.navigableKeySet();
        Iterator iterator=keySet.descendingIterator();
        while(iterator.hasNext()){
            UsageStats usageStats = mySortedMap.get(iterator.next());
            if (mLastEventField==null) {
                try {
                    mLastEventField = UsageStats.class.getField("mLastEvent");
                } catch (NoSuchFieldException e) {
                    break;
                }
            }
            if (mLastEventField!=null) {
                int lastEvent = 0;
                try {
                    lastEvent = mLastEventField.getInt(usageStats);
                } catch (IllegalAccessException e) {
                    break;
                }
                if (lastEvent==1){
                    topPackageName=usageStats.getPackageName();
                    break;
                }
            }else {
                break;
            }
        }    
        if (topPackageName==null){
            topPackageName =  mySortedMap.get(mySortedMap.lastKey()).getPackageName();
        }
        runningTopActivity=new ComponentName(topPackageName,"");
        if (LogUtil.isDebug())LogUtil.d(TAG,topPackageName);
    }
}
改進后不會受通知欄影響了

三枣宫、通過輔助服務(wù)獲取前臺應(yīng)用

輔助服務(wù)(AccessibilityService)有很多神奇的妙用婆誓,比如輔助點擊,比如頁面抓取也颤,還有就是獲取前臺應(yīng)用洋幻。
這里簡單介紹一下如何使用輔助服務(wù),首先要在AndroidManifest.xml中聲明:

<service
    android:name=".service.AccessibilityMonitorService"
    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>

然后在res/xml/文件夾下新建文件accessibility.xml翅娶,內(nèi)容如下:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeViewClicked|typeViewLongClicked|typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagRetrieveInteractiveWindows"
    android:canRetrieveWindowContent="true"
    android:canRequestFilterKeyEvents ="true"
    android:notificationTimeout="10"
    android:packageNames="@null"
    android:description="@string/accessibility_des"
    android:settingsActivity="com.pl.recent.MainActivity"
/>

至于其中每一項的內(nèi)容文留,還是去看API文檔吧好唯,我這里一一解釋的話,文章就太長了燥翅。關(guān)鍵是typeWindowStateChanged骑篙。
新建AccessibilityMonitorService,主要內(nèi)容如下:

public class AccessibilityMonitorService extends AccessibilityService {
    private CharSequence mWindowClassName;
    private String mCurrentPackage;
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int type=event.getEventType();
        switch (type){
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
                mWindowClassName = event.getClassName();
                mCurrentPackage = event.getPackageName()==null?"":event.getPackageName().toString();                
                break;
            case TYPE_VIEW_CLICKED:
            case TYPE_VIEW_LONG_CLICKED:
                break;
        }
    }
}

就這么簡單森书,就可以獲取當(dāng)前前臺應(yīng)用的包名和Activity名了靶端。
但是需要注意的是,輔助服務(wù)在一些手機(小米凛膏、魅族杨名、華為等國產(chǎn)手機)上,一旦程序被清理后臺猖毫,就會被關(guān)閉镣煮。。鄙麦。

想要了解輔助服務(wù)如何監(jiān)控點擊和抓取頁面的,可以參考Bigbang項目的BigBangMonitorService镊折。

四胯府、通過設(shè)備輔助應(yīng)用程序獲取前臺應(yīng)用(比較雞肋)

所謂設(shè)備輔助應(yīng)用程序,是在一些接近原生的系統(tǒng)上恨胚,長按Home鍵就會觸發(fā)的應(yīng)用骂因,默認是會觸發(fā)Google搜索。設(shè)備輔助應(yīng)用程序有點像是需要主動觸發(fā)的輔助服務(wù)赃泡,因為應(yīng)用中是無法主動去觸發(fā)其功能的寒波,所以說比較雞肋,鑒于篇幅升熊,這里就不詳細介紹了俄烁。
感興趣的朋友可以看Demo源碼中的簡單應(yīng)用,也可以看看Bigbang項目的BBVoiceInteractionService级野、BBVoiceInteractionSession和BBVoiceInteractionSessionService

Demo源碼

Github

免費授權(quán)轉(zhuǎn)載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末页屠,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子蓖柔,更是在濱河造成了極大的恐慌辰企,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件况鸣,死亡現(xiàn)場離奇詭異牢贸,居然都是意外死亡,警方通過查閱死者的電腦和手機镐捧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進店門潜索,熙熙樓的掌柜王于貴愁眉苦臉地迎上來臭增,“玉大人,你說我怎么就攤上這事帮辟∷僦罚” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵由驹,是天一觀的道長芍锚。 經(jīng)常有香客問我,道長蔓榄,這世上最難降的妖魔是什么并炮? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮甥郑,結(jié)果婚禮上逃魄,老公的妹妹穿的比我還像新娘。我一直安慰自己澜搅,他們只是感情好伍俘,可當(dāng)我...
    茶點故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著勉躺,像睡著了一般癌瘾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上饵溅,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天妨退,我揣著相機與錄音,去河邊找鬼蜕企。 笑死咬荷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的轻掩。 我是一名探鬼主播幸乒,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼唇牧!你這毒婦竟也來了逝变?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤奋构,失蹤者是張志新(化名)和其女友劉穎壳影,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弥臼,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡宴咧,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了径缅。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掺栅。...
    茶點故事閱讀 40,488評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡烙肺,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出氧卧,到底是詐尸還是另有隱情桃笙,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布沙绝,位于F島的核電站搏明,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闪檬。R本人自食惡果不足惜星著,卻給世界環(huán)境...
    茶點故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望粗悯。 院中可真熱鬧虚循,春花似錦、人聲如沸样傍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衫哥。三九已至茎刚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間炕檩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工捌斧, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留笛质,地道東北人。 一個月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓捞蚂,卻偏偏與公主長得像妇押,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子姓迅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,500評論 2 359

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