一、获取当前运行的顶层 Activity 的几种方式
在 Android 开发中,由于某些需求常常需要获取当前顶层的 Activity 信息。比如 App 中获取顶层 Activity 界面信息来判断某一个 app 是否在前台运行、统计某一个 app 的使用时长、更有恶意程序通过监听界面伪造 app 进行盗号以及欺诈、自动化开发中通过顶层 Activity 进行页面元素定位点击(比如基于辅助功自动化、uiautomator 自动化)等等操作。 在逆向工程中,获取当前运行 app 运行顶层 activity 也比较常用。通过顶层 Activity 可以快速定位界面中的功能在哪一个页面。
在 Android app 开发中,获取顶层 Activity 有以下方式:
- 低于 Android 5.0,情况下使用 getRunningTasks
首先需要在 Androidmanifest 中添加权限
1 | <uses - permission android:name = "android.permission.GET_TASKS" / >
|
代码如下:
1 2 | ActivityManager mAm = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
String activity_name = mAm.getRunningTasks( 1 ).get( 0 ).topActivity.getClassName();
|
- 高于安卓5.0使用UsageStatsManager获取到应用程序的运行情况。
在AndroidManifest文件中添加权限:
1 | <uses - permission android:name = "android.permission.PACKAGE_USAGE_STATS" / >
|
启动授权页面,需要用户授权app获取应用使用情况统计权限。跳转授权页面参考:
1 2 | Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS);
context.startActivity(intent);
|
获取顶层 activity 参考代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public static getTopActivity()
{
long endTime = System.currentTimeMillis();
long beginTime = endTime - 10000 ;
UsageStatsManager sUsageStatsManager = null;
if (sUsageStatsManager = = null) {
sUsageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
}
String result = "";
UsageEvents.Event event = new UsageEvents.Event();
UsageEvents usageEvents = sUsageStatsManager.queryEvents(beginT ime, endTime);
while (usageEvents.hasNextEvent()) {
usageEvents.getNextEvent(event);
if (event.getEventType() = = UsageEvents.Event.MOVE_TO_FOREGROUND) {
result = event.getPackageName() + "/" + event.getClassName();
}
}
if (!android.text.TextUtils.isEmpty(result)) {
return result;
}
}
return "";
}
|
除了 app 中使用代码获取顶层 Activity,也可以通过 adb 命令获取当前顶层的 Activity 信息,比如 windows 系统下 Android 10 手机可以用如下 adb 命令:
1 2 | C:\Users\Qiang>adb shell dumpsys activity |findstr "mResume"
mResumedActivity: ActivityRecord{ 12f93fd u0 com.sohu.inputmethod.sogou / .SogouIMEHomeActivity t50}
|
由于 ActivityManager->getRunningTasks 获取顶层 Activity 比较准确一些,以下将采用 ActivityManager->getRunningTasks 方式来进行研究实现。
二、源码追踪 ActivityManager getRunningTasks 调用
ActivityManager源码路径位于:
frameworks/base/core/java/android/app/ActivityManager.java
ActivityManager 中 getRunningTask 调用如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | / / getRunningTasks 方法实现
/ / 最终调用 getTaskService().getTasks
public List <RunningTaskInfo> getRunningTasks( int maxNum) throws SecurityException {
try {
return getTaskService().getTasks(maxNum);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
private static IActivityTaskManager getTaskService() {
return ActivityTaskManager.getService();
}
|
由以上代码分析可知,最终调用的是 ActivityTaskManager.getService().getTasks。接下来分析 ActivityTaskManager 中的调用情况。
ActivityTaskManager 源码路径:
frameworks/base/core/java/android/app/ActivityTaskManager.java
ActivityTaskManager 中 getService 实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 | / / getService 实现代码
public static IActivityTaskManager getService() {
return IActivityTaskManagerSingleton.get();
}
/ / IActivityTaskManagerSingleton 初始化代码
private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton = new Singleton<IActivityTaskManager>() {
@Override
protected IActivityTaskManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
return IActivityTaskManager.Stub.asInterface(b);
}
};
|
ActivityTaskManager.getService().getTasks 调用最终变成了 IActivityTaskManager.getTasks。在安卓源码中没有找到 IActivityTaskManager.java 文件,只找到 IActivityTaskManager.aidl 文件,说明 IActivityTaskManager.getTasks 调用最终转化了 binder 的进程间调用。安卓中 aidl 是用于 binder 通信定义服务器和客户端通信接口的一种描述语言,源码编译的时候会将 aidl 文件转化为 java 文件。
IActivityTaskManager.aidl 文件路径如下:
frameworks/base/core/java/android/app/IActivityTaskManager.aidl
在源码中搜索关键字“extends IActivityTaskManager"找到 ActivityTaskManagerService 文件使用了。那么说明 ActivityTaskManagerService 最终实现了 getTasks 的函数功能。以下追踪 ActivityTaskManagerService 中的调用情况:
ActivityTaskManagerService 代码路径:
frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
ActivityTaskManagerService 中 getTasks 代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | / / getTasks 实现代码,调用了 getFilteredTasks
@Override
public List <ActivityManager.RunningTaskInfo> getTasks( int maxNum) {
return getFilteredTasks(maxNum, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED);
}
/ / getFilteredTasks 实现代码
@Override
public List <ActivityManager.RunningTaskInfo> getFilteredTasks( int maxNum,@WindowConfiguration.ActivityType int ignoreActivityType,@WindowConfiguration.WindowingMode int ignoreWindowingMode) {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
final boolean crossUser = isCrossUserAllowed(callingPid, callingUid);
final int [] profileIds = getUserManager().getProfileIds(UserHandle.getUserId(callingUid), true);
ArraySet<Integer> callingProfileIds = new ArraySet<>();
for ( int i = 0 ; i < profileIds.length; i + + ) {
callingProfileIds.add(profileIds[i]);
}
ArrayList<ActivityManager.RunningTaskInfo> list = new ArrayList<>();
synchronized (mGlobalLock) {
if (DEBUG_ALL) Slog.v(TAG, "getTasks: max=" + maxNum);
final boolean allowed = isGetTasksAllowed( "getTasks" , callingPid, callingUid);
mRootActivityContainer.getRunningTasks(maxNum, list , ignoreActivityType,
ignoreWindowingMode, callingUid, allowed, crossUser, callingProfileIds);
}
return list ;
}
|
通过以上分析,已经知道系统如何实现 getRunningTask 函数功能的流程。借鉴 getRunningTask 实现,要增加一个获取顶层 Activity 的接口,只需要在 IActivityTaskManager.aidl 中增加一个接口,仿照 ActivityTaskManagerService 中 getTasks 的实现方式即可。
ActivityManager getRunningTasks大概的一个调用流程总结如下,图中省略了许多binder交互的细节:
三、为系统增加 getTopActivity 接口
- 在IActivityTaskManager.aidl文件中添加getTopActivity。添加内容如下:
1 2 3 | / / / ADD START
String getTopActivity();
/ / / ADD END
|
- 在ActivityTaskManagerService中重写getTopActivity方法,完成真正的功能,由于我们提供的接口是adb shell调用,所以此处没有处理权限检测,默认情况下adb shell有权限访问的。代码如下:
1 2 3 4 5 6 7 8 9 10 11 | / / / ADD START
@Override
public String getTopActivity()
{
List <ActivityManager.RunningTaskInfo> appTasks = getTasks( 1 );
if (null ! = appTasks && !appTasks.isEmpty()) {
return appTasks.get( 0 ).topActivity.getPackageName() + "/" + appTasks.get( 0 ).topActivity.getClassName();
}
return "";
}
/ / / ADD END
|
以上工作做好之后,我们就可以通过如下代码调用获取顶层 activity 了。代码如下:
1 | String topString = ActivityTaskManager.getService().getTopActivity();
|
四、系统增加 gettopactivity 命令
- 创建目录frameworks/base/cmds/gettopactivity,用来存放命令源码
1 | > qiang@ubuntu:~ / lineageOs$ mkdir - p frameworks / base / cmds / gettopactivity
|
- 在gettopactivity目录创建调用新增getTopActivity方法的源码文件src/com/android/commands/atm/Atm.java,在Atm.java中添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | package com.android.commands.atm;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SELinux;
import android.os.ServiceManager;
import android.util.AndroidException;
import java.io. File ;
import java.io.FileDescriptor;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintStream;
public class Atm {
private IActivityTaskManager mAtm;
public static void main(String[] args) {
(new Atm()).run();
}
public void run() throws Exception {
/ / 获取ActivityTaskManager对象
mAtm = ActivityTaskManager.getService();
if (mAtm = = null) {
System.err.println(NO_SYSTEM_ERROR_CODE);
throw new AndroidException( "Can't connect to activity task manager; is the system running?" );
}
try {
/ / 调用getTopActivity方法,获取顶层运行activity信息
String topString = mAtm.getTopActivity();
System.out.println( "TopActivity:" + topString);
} catch (RemoteException e) {
System.err.println(NO_SYSTEM_ERROR_CODE);
throw new AndroidException( "Can't call activity task manager; is the system running?" );
}
}
}
|
- 在gettopactivity目录下增加gettopactivity shell脚本,作为adb shell调用的命令。脚本内容如下:
1 2 3 4 | base = / system
export CLASSPATH = $base / framework / gettopactivity.jar
exec app_process $base / bin com.android.commands.atm.Atm "$@"
|
- 在gettopactivity目录下添加预编译可执行程序Android.mk脚本,内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | LOCAL_PATH: = $(call my - dir )
include $(CLEAR_VARS)
LOCAL_SRC_FILES : = \
$(call all - java - files - under, src)
LOCAL_MODULE : = gettopactivity
include $(BUILD_JAVA_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE : = gettopactivity
LOCAL_SRC_FILES : = gettopactivity
LOCAL_MODULE_CLASS : = EXECUTABLES
LOCAL_MODULE_TAGS : = optional
include $(BUILD_PREBUILT)
|
- 将gettopactivity模块加入到编译文件链接中。在之前的文章中已经讨论过了预编译二进制模块加入编译链放入文件路径:make/target/product/base_system.mk中。加入之后的部分内容如下:
1 2 3 4 5 6 7 8 9 | PRODUCT_PACKAGES + = \
myfridaserverarm64 \
gettopactivity \
...(省略)
|
五、编译系统测试效果
源码根目录执行编译:
source build/envsetup.sh</br>
breakfast oneplus3</br>
brunch oneplus3 </br>
刷机测试效果:
1 2 | C:\Users\Qiang>adb shell gettopactivity
TopActivity:com.starbucks.cn / com.starbucks.cn.ui.StarbucksLaunchActivity
|
阿里云助力开发者!2核2G 3M带宽不限流量!6.18限时价,开
发者可享99元/年,续费同价!
最后于 2021-4-12 22:05
被蟑螂一号编辑
,原因: