在做一个显示当前顶部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 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 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不需要自己手动启动,只要在手机的Setiings Accessbility里面打开注册了这个Service的APP对应的Accessbility的功能,这个Service就会自动启动。
附:我自己做的显示当前页面Activity信息的APP,CurrentActivity。
GitHub: https://github.com/Haocxx/CurrentActivity