App启动流程【1】Launcher 启动 App

从用户的角度来看,打开一个 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 。在这个方法中,有两个核心的部分:

  1. 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());
    }
  2. 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(), userintent.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

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!