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閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容