Android9.0系统源码_获取当前运行在最顶层的Activity方法

前言

在 Android 开发中,由于某些需求常常需要获取当前顶层的 Activity 信息。比如 App 中获取顶层 Activity 界面信息来判断某一个 app 是否在前台运行、统计某一个 app 的使用时长、更有恶意程序通过监听界面伪造 app 进行盗号以及欺诈、自动化开发中通过顶层 Activity 进行页面元素定位点击(比如基于辅助功自动化、uiautomator 自动化)等等操作。 在逆向工程中,获取当前运行 app 运行顶层 activity 也比较常用。通过顶层 Activity 可以快速定位界面中的功能在哪一个页面。

一、获取当前运行的顶层 Activity的几种方式

1、调用ActivityManager的getRunningTasks方法

1)在AndroidManifest文件中添加权限:

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

2)获取顶层 activity 参考代码:

    private String getTopActivityByActivityManager() {
        ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> listTask = activityManager.getRunningTasks(0);
        String activityName = "";
        if (listTask != null && !listTask.isEmpty()) {
            ActivityManager.RunningTaskInfo runningTaskInfo = listTask.get(1);
            activityName = runningTaskInfo.topActivity.getClassName();
        }
       return activityName;
    }

2、调用UsageStatsManager的queryEvents方法:

1)在AndroidManifest文件中添加权限:

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

2)需要启动授权页面,让用户授权app获取应用使用情况统计权限。:

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

3)获取顶层 activity的参考代码:

    public String getTopActivityByUsageStatsManager() {
        long endTime = System.currentTimeMillis();
        long beginTime = endTime - 10000;

        UsageStatsManager usageStatsManager = (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE);
        String activityInfo = "";
        UsageEvents.Event event = new UsageEvents.Event();
        UsageEvents usageEvents = usageStatsManager.queryEvents(beginTime, endTime);
        while (usageEvents.hasNextEvent()) {
            usageEvents.getNextEvent(event);
            if (event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
                activityInfo = event.getPackageName() + "/" + event.getClassName();
            }
        }
        return activityInfo;
    }

3、使用adb命令
1)输入dumpsys指令

adb shell "dumpsys activity | grep "ResumedActivity:"

2)得到的结果如下所示:

ResumedActivity: ActivityRecord{17ea57d u10 com.example.appcenter/.activity.MainActivity t1000085}

二、ActivityManager的getRunningTasks方法源码分析

1、ActivityManager的getRunningTasks方法如下所示:

frameworks/base/core/java/android/app/ActivityManager.java

@SystemService(Context.ACTIVITY_SERVICE)
public class ActivityManager {
    ...代码省略...
    public List<RunningTaskInfo> getRunningTasks(int maxNum)
            throws SecurityException {
        try {
            return getService().getTasks(maxNum);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
    ...代码省略...
}

getRunningTasks方法内部直接调用了getService获取一个实例对象并调用该对象的getTasks方法。

2、ActivityManager的getService方法如下所示:

@TestApi
@SystemService(Context.ACTIVITY_TASK_SERVICE)
public class ActivityTaskManager {
    ...代码省略...
    public static IActivityManager getService() {
        return IActivityManagerSingleton.get();
    }

    private static final Singleton<IActivityManager> IActivityManagerSingleton =
            new Singleton<IActivityManager>() {
                @Override
                protected IActivityManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
                    return am;
                }
            };
    ...代码省略...
}

getService方法会返回一个IActivityTaskManager类型的单例对象。

4、在Android9系统源码中并不存在IActivityManager.java这样一个文件,只能找到 IActivityManager.aidl文件:

frameworks/base/core/java/android/app/IActivityManager.aidl

interface IActivityManager {
    ...代码省略...
   List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
    ...代码省略...
}

我们知道源码编译的时候会将aidl文件转化为 java 文件,IActivityManager的getTasks方法的调用最终是通过binder来实现跨进程通信的。而IActivityManager.aidl中getTasks方法的具体实现类,其实是ActivityManagerService。

5、ActivityManagerService的getTasks方法如下所示:

frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java

public class ActivityManagerService extends IActivityManager.Stub
        implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
    ...代码省略...
    final ActivityStackSupervisor mStackSupervisor;//Activity任务栈管理者
    ...代码省略...
    @Override
    public List<RunningTaskInfo> getTasks(int maxNum) {
        return getFilteredTasks(maxNum, ACTIVITY_TYPE_UNDEFINED, WINDOWING_MODE_UNDEFINED);
    }

    @Override
    public List<RunningTaskInfo> getFilteredTasks(int maxNum, @ActivityType int ignoreActivityType,
                                                  @WindowingMode int ignoreWindowingMode) {
        final int callingUid = Binder.getCallingUid();
        ArrayList<RunningTaskInfo> list = new ArrayList<>();

        synchronized (this) {
            if (DEBUG_ALL) Slog.v(TAG, "getTasks: max=" + maxNum);

            final boolean allowed = isGetTasksAllowed("getTasks", Binder.getCallingPid(),
                    callingUid);
            mStackSupervisor.getRunningTasks(maxNum, list, ignoreActivityType,
                    ignoreWindowingMode, callingUid, allowed);
        }

        return list;
    }
    ...代码省略...
}

ActivityManagerService的getTasks方法内部会再次调用getFilteredTasks方法,getFilteredTasks方法最终会调用ActivityStackSupervisor的getRunningTasks方法。

6、ActivityStackSupervisor的getRunningTasks方法如下所示:

frameworks/base/services/core/java/com/android/server/am/ActivityStackSupervisor.java

public class ActivityStackSupervisor extends ConfigurationContainer implements DisplayListener,
        RecentTasks.Callbacks {
    ...代码省略...
    private RunningTasks mRunningTasks;
    ...代码省略...
    void getRunningTasks(int maxNum, List<RunningTaskInfo> list,
            @ActivityType int ignoreActivityType, @WindowingMode int ignoreWindowingMode,
            int callingUid, boolean allowed) {
        mRunningTasks.getTasks(maxNum, list, ignoreActivityType, ignoreWindowingMode,
                mActivityDisplays, callingUid, allowed);
    }
    ...代码省略...
}

getRunningTasks内部再次调用了RunningTasks的getTasks方法。
7、RunningTasks这个类的代码并不多,如下所示:

class RunningTasks {

    private static final Comparator<TaskRecord> LAST_ACTIVE_TIME_COMPARATOR =
            (o1, o2) -> Long.signum(o2.lastActiveTime - o1.lastActiveTime);
    private final TaskRecord.TaskActivitiesReport mTmpReport = new TaskRecord.TaskActivitiesReport();
    private final TreeSet<TaskRecord> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
    private final ArrayList<TaskRecord> mTmpStackTasks = new ArrayList<>();

    void getTasks(int maxNum, List<RunningTaskInfo> list, @ActivityType int ignoreActivityType,
            @WindowingMode int ignoreWindowingMode, SparseArray<ActivityDisplay> activityDisplays,
            int callingUid, boolean allowed) {
        if (maxNum <= 0) {
            return;
        }
        mTmpSortedSet.clear();//清空
        mTmpStackTasks.clear();//清空
        final int numDisplays = activityDisplays.size();//获取显示屏的数量
        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
            final ActivityDisplay display = activityDisplays.valueAt(displayNdx);//默认只有一个显示屏,所以displayNdx等于0
            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
                final ActivityStack stack = display.getChildAt(stackNdx);//获取屏幕所对应的Activity栈管理者
                stack.getRunningTasks(mTmpStackTasks, ignoreActivityType, ignoreWindowingMode,
                        callingUid, allowed);//获取正在运行的任务栈,按照最后活跃的时间序列将其存储在mTmpStackTasks中
                for (int i = mTmpStackTasks.size() - 1; i >= 0; i--) {
                    mTmpSortedSet.addAll(mTmpStackTasks);
                }
            }
        }
        final Iterator<TaskRecord> iter = mTmpSortedSet.iterator();
        while (iter.hasNext()) {
            if (maxNum == 0) {
                break;
            }

            final TaskRecord task = iter.next();
            list.add(createRunningTaskInfo(task));
            maxNum--;
        }
    }

    //将任务栈TaskRecord 转化为RunningTaskInfo对象
    private RunningTaskInfo createRunningTaskInfo(TaskRecord task) {
        task.getNumRunningActivities(mTmpReport);
        final RunningTaskInfo ci = new RunningTaskInfo();
        ci.id = task.taskId;
        ci.stackId = task.getStackId();
        ci.baseActivity = mTmpReport.base.intent.getComponent();
        ci.topActivity = mTmpReport.top.intent.getComponent();
        ci.lastActiveTime = task.lastActiveTime;
        ci.description = task.lastDescription;
        ci.numActivities = mTmpReport.numActivities;
        ci.numRunning = mTmpReport.numRunning;
        ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode();
        ci.resizeMode = task.mResizeMode;
        ci.configuration.setTo(task.getConfiguration());
        return ci;
    }
}

getTasks方法首先将mTmpSortedSet和mTmpStackTasks清空,然后获取当前显示屏的数量,默认只有一个显示屏,所以displayNdx等于0且循环会执行一次,紧接着获取默认屏幕所对应的Activity栈管理者ActivityStack,调用ActivityStack的getRunningTasks方法获取正在运行的任务栈,将其存储在mTmpStackTasks中;随后会将mTmpStackTasks整个添加到mTmpSortedSet中,mTmpSortedSet会按照最后活跃时间来对条目进行排序。然后获取mTmpSortedSet的迭代器,依次将TaskRecord转化为RunningTaskInfo实例对象并存储到getTasks方法参数list中,这样上层就能拿到当前最上层的Activity信息了。

image.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容