1. 程式人生 > >[Android] Android獲取當前頂部Activity名方法歷史版本彙總

[Android] Android獲取當前頂部Activity名方法歷史版本彙總

在做一個顯示當前頂部activity名和包名的ToolApp時遇到的問題。

在Android5.0之前,獲取top Activity方法非常簡單。直接使用getRunningTasks方法即可。

        //getRunningTasks() is deprecated since API Level 21 (Android 5.0)  
        List localList = manager.getRunningTasks(1);  
        ActivityManager.RunningTaskInfo localRunningTaskInfo = (ActivityManager.RunningTaskInfo)localList.get(0);  
        info.packageName = localRunningTaskInfo.topActivity.getPackageName();  
        info.topActivityName = localRunningTaskInfo.topActivity.getClassName();  

但是這個方法到了5.0就被Android因安全原因ban掉了。這之後使用該方法只能獲取到Laucher的資訊了。

後來有人找出了ActivityManger的一個方法:

private String getLollipopRecentTask() { 
    final int PROCESS_STATE_TOP = 2; 
    try { 
        Field processStateField = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState"); 
        List<ActivityManager.RunningAppProcessInfo> processes = ((ActivityManager) getSystemService(Context.ACTIVITY_SERVICE)).getRunningAppProcesses(); 
        for (ActivityManager.RunningAppProcessInfo process : processes) { 
            if (process.importance <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && process.importanceReasonCode == 0) { 
                int state = processStateField.getInt(process); 
                if (state == PROCESS_STATE_TOP) { 
                String[] packname = process.pkgList; 
                return packname[0]; 
                } 
            } 
        } 
    } catch (Exception e) { 
        throw new RuntimeException(e); 
    } 
    return ""; 
}
然而僅僅過了一個Android OS版本,這個也無法生效了。

所以有人找出了這樣的方法:

    public static void getInfo() {
        if (Build.VERSION.SDK_INT >= 21) {
            if (mUsageStatsManager != null) {
                long now = System.currentTimeMillis();
                // get app data during 60s
                List<UsageStats> stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, now - 60 * 1000, now);
                // get present app
                if ((stats != null) && (!stats.isEmpty())) {
                    int j = 0;
                    for (int i = 0; i < stats.size(); i++) {
                        if (stats.get(i).getLastTimeUsed() > stats.get(j).getLastTimeUsed()) {
                            j = i;
                        }
                    }
                    topPackageName = stats.get(j).getPackageName();
                    topActivityName = "";
                }
            }
        }

通過UsageStatsManager獲取最近一段時間的使用資料,找到最後一次被使用的資訊。但是這個方法只能獲取到Package名,得不到Activity的名字。

後來在StackOverFlow上看到了這樣的做法:(https://stackoverflow.com/questions/31980976/get-top-activity-name-in-android-l)

while (true) {
    final long INTERVAL = 1000;
    final long end = System.currentTimeMillis();
    final long begin = end - INTERVAL;
    final UsageEvents usageEvents = manager.queryEvents(begin, end);
    while (usageEvents.hasNextEvent()) {
        UsageEvents.Event event = new UsageEvents.Event();
        usageEvents.getNextEvent(event);
        if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
            Log.d("event", "timestamp : " + event.getTimeStamp());
            Log.d("event", "package name : " + event.getPackageName());
            Log.d("event", "class name : " + event.getClassName());
        }
    }
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

似乎可以生效沒有試過。但是需要死迴圈不停地讀,沒有辦法動態地監聽,吃相有些難看,不夠優雅。

最後通過翻閱資料,找到了一個極佳的方法,就是藉助Accessibility輔助功能來實現。

public class WindowChangeDetectingService extends AccessibilityService {
    private static final String TAG = "WCDService";

    @Override
    public void onCreate() {
        Log.d(TAG, "onCreate");
    }

    @Override
    public void onDestroy() {
        Log.d(TAG, "onDestroy");
    }

    @Override
    protected void onServiceConnected() {
        Log.d(TAG, "onServiceConnected");
        super.onServiceConnected();
        // Configure these here for compatibility with API 13 and below.
        AccessibilityServiceInfo config = new AccessibilityServiceInfo();
        config.eventTypes = AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
        config.feedbackType = AccessibilityServiceInfo.FEEDBACK_GENERIC;
        // Just in case this helps
        config.flags = AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
        setServiceInfo(config);
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        Log.d(TAG, "onAccessibilityEvent");
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
            if (event.getPackageName() != null && event.getClassName() != null) {
                ComponentName componentName = new ComponentName(
                        event.getPackageName().toString(),
                        event.getClassName().toString()
                );
                ActivityInfo activityInfo = tryGetActivity(componentName);
                boolean isActivity = activityInfo != null;
                if (isActivity) {
                    Log.d(TAG, "CurentActivity " + componentName.flattenToShortString());
                    ActivityManagerUtils.topActivityName = componentName.flattenToShortString();
                    ActivityManagerUtils.topPackageName = event.getPackageName().toString();
                }
            }
        }
    }

    private ActivityInfo tryGetActivity(ComponentName componentName) {
        try {
            return getPackageManager().getActivityInfo(componentName, 0);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }

    @Override
    public void onInterrupt() {}
}

需要一個繼承Accessbility的Service,在它的onAccessbilityEvent回撥方法裡監聽到視窗狀態改變的事件,從中獲取Activity的資訊。

需要在AndroidManifest裡面配置:

        <service
            android:name=".WindowChangeDetectingService"
            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/accessibilityservice"/>
        </service>
<?xml version="1.0" encoding="utf-8"?>
<!-- These options MUST be specified here in order for the events to be received on first
 start in Android 4.1.1 -->
<accessibility-service
    xmlns:tools="http://schemas.android.com/tools"
    android:accessibilityEventTypes="typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagIncludeNotImportantViews"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_service_description"
    xmlns:android="http://schemas.android.com/apk/res/android"
    tools:ignore="UnusedAttribute"/>

這個Service不需要自己手動啟動,只要在手機的Setiings Accessbility裡面開啟註冊了這個Service的APP對應的Accessbility的功能,這個Service就會自動啟動。

附:我自己做的顯示當前頁面Activity資訊的APP,CurrentActivity。

GitHub:   https://github.com/Haocxx/CurrentActivity