携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第5天,点击查看活动详情
从用户的角度来看,打开一个 App 的最普遍的逻辑是点击桌面中的应用程序图标,然后应用程序会打开一个启动页,然后自动进入首页。
在这篇文章中,将为您介绍用户在桌面点击 App 图标后,到打开第一个 App 页面之间的事情。
在 Android 系统中,桌面本质上也是一个应用程序,通常它是 Launcher。市面上也有一些 Launcher 软件,用户可以下载不同的 Launcher 并替换系统提供的 Launcher ,以此来自定义桌面效果。所以,桌面就是 Launcher 应用程序。以此推断, Launcher 中展示的应用程序图标自然也就是在 Launcher 相关的代码中。
Laucher 的应用程序图标的数据类型是:
packages/apps/Launcher3/src/com/android/launcher3/model/data/AppInfo.java
它的父类型是 ItemInfo。 Launcher 中的应用程序图标、小组件、文件夹等 ,它们数据类都是 ItemInfo 的实现,ItemInfo 根据不同的 Type 进行区分:
/**
* One of {@link Favorites#ITEM_TYPE_APPLICATION},
* {@link Favorites#ITEM_TYPE_SHORTCUT},
* {@link Favorites#ITEM_TYPE_DEEP_SHORTCUT}
* {@link Favorites#ITEM_TYPE_FOLDER},
* {@link Favorites#ITEM_TYPE_APPWIDGET} or
* {@link Favorites#ITEM_TYPE_CUSTOM_APPWIDGET}.
*/
而对于每个 Item 的点击,是通过 ItemClickHandler 来进行处理的:
packages/apps/Launcher3/src/com/android/launcher3/touch/ItemClickHandler.java
在 Launcher 创建图标时,调用的 createShortcut 方法中,设置了点击事件 ItemClickHandler.INSTANCE :
public View createShortcut(ViewGroup parent, WorkspaceItemInfo info) {
BubbleTextView favorite = (BubbleTextView) LayoutInflater.from(parent.getContext())
.inflate(R.layout.app_icon, parent, false);
favorite.applyFromWorkspaceItem(info);
favorite.setOnClickListener(ItemClickHandler.INSTANCE);
favorite.setOnFocusChangeListener(mFocusHandler);
return favorite;
}
ItemClickHandler.INSTANCE 是一个 OnClickListener:
public static final OnClickListener INSTANCE = ItemClickHandler::onClick;
onClick 方法中只看核心的部分逻辑:
private static void onClick(View v) {
// ...
Object tag = v.getTag();
if (tag instanceof WorkspaceItemInfo) {
onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher);
} else if (tag instanceof FolderInfo) {
if (v instanceof FolderIcon) {
onClickFolderIcon(v);
}
} else if (tag instanceof AppInfo) {
startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher
);
} else if (tag instanceof LauncherAppWidgetInfo) {
if (v instanceof PendingAppWidgetHostView) {
onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
}
} else if (tag instanceof SearchActionItemInfo) {
onClickSearchAction(launcher, (SearchActionItemInfo) tag);
}
}
在 onClick 中,根据不同的 Tag 处理不同的逻辑:
- WorkspaceItemInfo
- FolderInfo
- AppInfo
- LauncherAppWidgetInfo
- onClickSearchAction
我们只关注 App 启动的流程,这里只看 AppInfo 类型,此时调用的方法是 startAppShortcutOrInfoActivity 方法:
private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher) {
// ...
Intent intent;
if (item instanceof ItemInfoWithIcon && (((ItemInfoWithIcon) item).runtimeStatusFlags & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
ItemInfoWithIcon appInfo = (ItemInfoWithIcon) item;
intent = new PackageManagerHelper(launcher).getMarketIntent(appInfo.getTargetComponent().getPackageName());
} else {
intent = item.getIntent();
}
if (intent == null) {
throw new IllegalArgumentException("Input must have a valid intent");
}
// ... 这里处理工作区中其他图标
launcher.startActivitySafely(v, intent, item);
}
在这个方法中,主要是构造了一个 Intent ,然后通过 startActivitySafely 方法启动一个 Intent 。在这个方法中,有两个核心的部分:
-
PackageManagerHelper 构造 Intent
intent = new PackageManagerHelper(launcher).getMarketIntent(appInfo.getTargetComponent().getPackageName());这里通过 packageName 构造了一个 Intent,packageName 是 AppInfo 提供的信息,这里没有用到什么 PackageManager :
public Intent getMarketIntent(String packageName) { return new Intent(Intent.ACTION_VIEW) .setData(new Uri.Builder() .scheme("market") .authority("details") .appendQueryParameter("id", packageName) .build()) .putExtra(Intent.EXTRA_REFERRER, new Uri.Builder().scheme("android-app") .authority(mContext.getPackageName()).build()); } -
Launcher 启动新页面,方法 startActivitySafely :
@Override public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { // ... boolean success = super.startActivitySafely(v, intent, item); if (success && v instanceof BubbleTextView) { // This is set to the view that launched the activity that navigated the user away // from launcher. Since there is no callback for when the activity has finished // launching, enable the press state and keep this reference to reset the press // state when we return to launcher. BubbleTextView btv = (BubbleTextView) v; btv.setStayPressed(true); addOnResumeCallback(() -> btv.setStayPressed(false)); } return success; }Launcher 的 startActivitySafely 方法调用的是父类的 startActivitySafely 方法,在这里处理了动画问题和图标按压状态的更新。Launcher 的父类是 BaseDraggingActivity ,它是启动 Activity 的主要逻辑:
public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item) { // ... try { boolean isShortcut = (item instanceof WorkspaceItemInfo) && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) && !((WorkspaceItemInfo) item).isPromise(); if (isShortcut) { // 由于遗留原因,快捷方式需要一些特殊检查。 startShortcutIntentSafely(intent, optsBundle, item); } else if (user == null || user.equals(Process.myUserHandle())) { // 可能会启动一些 bookkeeping Activity startActivity(intent, optsBundle); } else { getSystemService(LauncherApps.class).startMainActivity(intent.getComponent(), user, intent.getSourceBounds(), optsBundle); } if (item != null) { InstanceId instanceId = new InstanceIdSequence().newInstanceId(); logAppLaunch(getStatsLogManager(), item, instanceId); } return true; } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show(); Log.e(TAG, "Unable to launch. tag=" + item + " intent=" + intent, e); } return false; }因为 Launcher 继承自 BaseDraggingActivity,BaseDraggingActivity 又继承自 BaseActivity 。
无论是 BaseDraggingActivity 还是 BaseActivity 中都没有重写 startActivity 方法,所以这里的 startActivity 来自于
frameworks/base/core/java/android/app/Activity.java。到这里,应用程序的第一个 Activity 就通过 Launcher 启动了。