在这篇文章中,将为您介绍用户在桌面点击 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 启动了。
原文始发于微信公众号(八千里路山与海):App启动流程【1】Launcher 启动 App
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/85059.html