Android Framework 源码之旅 —— Launcher(踩坑)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Zsago/article/details/80320066

前言

万事开头难,准备工作铺垫了一大堆,就是为了接下来的源码阅读工作,这当然少不了借鉴前辈的经验了,本次就以研究应用层APP是怎么启动的为目的来进行探索。

这里做个备注,强烈建议直接在纯linux环境下进行这项工作,如另外创建一个ubuntu系统,而非在windows系统下进行,因为要想进行代码上的编译,始终还是要在linux环境下进行,而且使用源码编译提供的模拟器进行调试也更纯粹。

但事实上,往往事与愿违,第一步,就卡主了,不知道调试哪个进程,或者严格来说,是找不到前辈们说的com.android.launcher进程(用源码编译的eng模拟器不会出现这种情况),下面附上进程图:
这里写图片描述
这里我用的是android studio自带的模拟器,以为是模拟器的问题,换了Pixel系列的也不行,再换也还是找不到,随后换回了Nexus系列的模拟器,无意间发现com.google.android.apps.nexuslauncher进程,试着在应用点击处打断点,然后随便点一个应用打开,断点就捕捉到了!
只能感慨,做人要机智,思想要开阔啊!能够进行断点捕获了,接下来就可以尽情折腾在源码的海洋了。

正如上面备注所说,建议使用源码编译自带的模拟器进行调试,而非android studio自带的模拟器,因为从7.1开始,android studio自带的模拟器调试就没那么纯粹了,至少luncher功能调试就不能完全跟踪到内部参数信息,刚开始兴致勃勃在windows环境下用android studio自带模拟器调试,最终又重新装了个ubuntu的子系统,老老实实地用aosp_x86_64-eng模拟器进行调试了,绕了一大圈。这里算是一个踩坑记录!

失败经历

Launcher本质上也是一个应用,通常应用层开发的APP安装后都会在桌面留下一个图标,点击这个图标后,那么,相应的APP就开始启动了,而启动这个图标所对应APP的,正是Launcher系统应用。

Launcher应用的源码目录为packages/apps/Launcher3,至于还有一个packages/apps/Launcher2,暂不清楚它们的关系,从名称来看,姑且理解为低版本的Launcher应用启动程序吧。

刚开始看,不知道从哪看起,于是按照开发应用层APP的逻辑,首先打开packages/apps/Launcher3/AndroidManifest.xml,很容易就可以找到应用的入口页面了。

......

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.launcher3">

    ......

    <application
        android:backupAgent="com.android.launcher3.LauncherBackupAgent"
        android:fullBackupContent="@xml/backupscheme"
        android:fullBackupOnly="true"
        android:hardwareAccelerated="true"
        android:icon="@drawable/ic_launcher_home"
        android:label="@string/derived_app_name"
        android:largeHeap="@bool/config_largeHeap"
        android:restoreAnyVersion="true"
        android:supportsRtl="true"
        android:theme="@style/LauncherTheme">

        <!--
        Main launcher activity. When extending only change the name, and keep all the
        attributes and intent filters the same
        -->
        <activity
            android:name="com.android.launcher3.Launcher"
            android:clearTaskOnLaunch="true"
            android:configChanges="keyboard|keyboardHidden|navigation"
            android:enabled="true"
            android:launchMode="singleTask"
            android:resizeableActivity="true"
            android:resumeWhilePausing="true"
            android:screenOrientation="nosensor"
            android:stateNotNeeded="true"
            android:taskAffinity=""
            android:windowSoftInputMode="adjustPan">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.HOME" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.MONKEY" />
                <category android:name="android.intent.category.LAUNCHER_APP" />
            </intent-filter>
        </activity>

        ......

    </application>
</manifest>

虽然有几个不太熟悉的category,但也有熟悉的,如android.intent.category.HOME,记得在哪看到过它,但这都不是目前研究的重点了。找准研究入口,跳转到packages/apps/Launcher3/src/com/android/launcher3/Launcher.java
显然,页面已经进入了onResume状态,现在要想打断点,就不能在这些生命周期中打断点,而桌面图标是对应相应应用的入口,点击了它,就会启动相应的应用,点击,搞应用层开发了这么多,当然熟悉啦,onClick,找准这之类的关键词作为函数名的进行切入。
这里写图片描述
好像可行,那么打开这个函数,打一个有效的断点,然后在模拟器主界面随便点一个应用图标,看看断点是否会生效,结果没让人失望。
可以说,前面都只是准备工作,接下来才是正式内容。

	public void onClick(View v) {
        // Make sure that rogue clicks don't get through while allapps is launching, or after the
        // view has detached (it's possible for this to happen if the view is removed mid touch).
        if (v.getWindowToken() == null) {
            return;
        }

        if (!mWorkspace.isFinishedSwitchingState()) {
            return;
        }

        if (v instanceof Workspace) {
            if (mWorkspace.isInOverviewMode()) {
                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
                        LauncherLogProto.Action.Direction.NONE,
                        LauncherLogProto.ContainerType.OVERVIEW, mWorkspace.getCurrentPage());
                showWorkspace(true);
            }
            return;
        }

        if (v instanceof CellLayout) {
            if (mWorkspace.isInOverviewMode()) {
                int page = mWorkspace.indexOfChild(v);
                getUserEventDispatcher().logActionOnContainer(LauncherLogProto.Action.Type.TOUCH,
                        LauncherLogProto.Action.Direction.NONE,
                        LauncherLogProto.ContainerType.OVERVIEW, page);
                mWorkspace.snapToPageFromOverView(page);
                showWorkspace(true);
            }
            return;
        }

        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
            onClickAppShortcut(v);
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                onClickFolderIcon(v);
            }
        } else if ((v instanceof PageIndicator) ||
            (v == mAllAppsButton && mAllAppsButton != null)) {
            onClickAllAppsButton(v);
        } else if (tag instanceof AppInfo) {
            startAppShortcutOrInfoActivity(v);
        } else if (tag instanceof LauncherAppWidgetInfo) {
            if (v instanceof PendingAppWidgetHostView) {
                onClickPendingWidget((PendingAppWidgetHostView) v);
            }
        }
    }

有些代码是目前还不知道起什么作用的,不过也无所谓了,从函数名或变量命名,可以猜出个大概,而且也大可不必现在就搞清楚这些所有的用途,反正目的是研究应用层APP是怎么被启动的,仅此而已,最好的办法就是点击图标,然后打断点试,看点击图标后,函数会作何跳转。这里就以点击Settings应用为例(虽然Settings应用是系统级应用,但理论上来讲,用来作为研究应用启动应该是没问题的),来调试其启动流程。

首先点击图标,然后一步步断点下去,会看到
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
接下来又回到public boolean startActivitySafely(View v, Intent intent, ItemInfo item)函数
这里写图片描述
绕了一般圈,我只想说,实在受不了了,因为调试的内部参数完全没有,最终放弃windows下使用android studio自带模拟器进行调试研究。

正文

经历了上面的惨痛经历,又折腾了几天才把环境弄好,如果不用真机的话,编译源码时,建议还是选aosp_x86_64-eng,运行相对较快且调试方便
这里写图片描述
这下再看调试进程,很容易就发现com.android.launcher进程了
这里写图片描述
话不多说,直接开启进程调试,然后再在Launcher3中的主页面中打断点,发现没有起作用,一番疑神疑鬼地折腾后,终于在Launcher2中打断点成功,这下也就解释了上面说的Launcher2Launcher3的关系了。
按照之前的做法,在onClick中打断点进行捕获,结果发现在显示所有应用图标的页面点击应用图标没有进相应的断点
这里写图片描述
一番源码查看,反而被庞大的源码给弄昏了,所以研究源码就要定一个方向啊,非研究方向的源码,一知半解又有什么关系呢。果断借鉴前人的经验,就是点击APP图标会调用startActivitySafely函数,毫不犹豫,打断点,点击应用图标,真就捕获成功了
这里写图片描述
下面的调试信息中也有了具体的内部参数了,不枉我一番重新搭建环境啊。至于为什么就跳到startActivitySafely函数来了
这里写图片描述
一番取巧发现,是在packages/apps/Launcher2/src/com/android/launcher2/AppsCustomizePagedView.java中的onClick中执行的!
接下来可以安心研究Launcher启动流程了,从上面的startActivitySafely可以看出,接下来是进startActivity函数
这里写图片描述
这里写图片描述
不难看出,接下来就是进startActivity这个函数了,继续
这里写图片描述
一看这断点状态,就知道这不能调试,为什么呢,往android studio顶部目录上一看才恍然,这是进了sdk的源码处,那么,我手动进framework的相应源码处打断点呢
这里写图片描述
看这个断点状态,好像有戏,继续点调试进入下一步
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
然后继续向下调试就会发现,回到了LauncherstartActivity函数中
这里写图片描述
这里写图片描述
看来,整个APP启动流程就结束了,额?怎么有种漏掉了什么的感觉,没办法,重新来一次,回顾整个调试流程,遗漏点无疑是在startActivityForResult
这里写图片描述
这里写图片描述
这里写图片描述
从这个实例值IActivityManager$Stub$Proxy可以看出,这里通过AIDL进行跨进程通信
这里写图片描述
通过Alt+F7快捷键查找有哪些类实现了该接口
这里写图片描述
这里写图片描述
根据AIDL的使用流程,那么,实现类会实现这个Stub
这里写图片描述
这里写图片描述
这里写图片描述
断点状态显示不能调试,显然,未开启调试进程
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

public class ActivityManager {
	...
	private static final int FIRST_START_FATAL_ERROR_CODE = -100;
    private static final int LAST_START_FATAL_ERROR_CODE = -1;
    private static final int FIRST_START_SUCCESS_CODE = 0;
    private static final int LAST_START_SUCCESS_CODE = 99;
    private static final int FIRST_START_NON_FATAL_ERROR_CODE = 100;
    private static final int LAST_START_NON_FATAL_ERROR_CODE = 199;
	...
	public static final int START_NOT_VOICE_COMPATIBLE = FIRST_START_FATAL_ERROR_CODE + 3;
	...
	public static final int START_PERMISSION_DENIED = FIRST_START_FATAL_ERROR_CODE + 6;
	public static final int START_FORWARD_AND_REQUEST_CONFLICT = FIRST_START_FATAL_ERROR_CODE + 7;
	public static final int START_CLASS_NOT_FOUND = FIRST_START_FATAL_ERROR_CODE + 8;
	public static final int START_INTENT_NOT_RESOLVED = FIRST_START_FATAL_ERROR_CODE + 9;
	...
	public static final int START_SUCCESS = FIRST_START_SUCCESS_CODE;
	...
	public static final int START_SWITCHES_CANCELED = FIRST_START_NON_FATAL_ERROR_CODE;
	...
	public static final int START_ABORTED = FIRST_START_NON_FATAL_ERROR_CODE + 2;
	...
}

这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述
这里写图片描述

猜你喜欢

转载自blog.csdn.net/Zsago/article/details/80320066