[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的一個方法:
然而僅僅過了一個Android OS版本,這個也無法生效了。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 ""; }
所以有人找出了這樣的方法:
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