[start-activity]Launcher3

概述

在launcher中点击应用来启动应用。

launcher发一个intent来启动应用。

1. 应用图标

通过android studio的tools -> layout inspector工具可以看到,桌面上的app图标使用的是DoubleShadowBubbleTextView类,第二级菜单栏的app图标使用的是BubbleTextView类

桌面图标属于workspace中

第二级菜单栏属于AllAppsRecyclerView中,是滚动栏

image-20210703120354779

源码解析

应用层

1. launcher3

1.1 launcher启动应用的调用栈

execStartActivity:1682, Instrumentation (android.app)
startActivityForResult:5194, Activity (android.app)
startActivityForResult:1499, Launcher (com.android.launcher3)
startActivity:5519, Activity (android.app)
startActivitySafely:161, BaseDraggingActivity (com.android.launcher3)
startActivitySafely:1818, Launcher (com.android.launcher3)
startAppShortcutOrInfoActivity:270, ItemClickHandler (com.android.launcher3.touch)
onClick:95, ItemClickHandler (com.android.launcher3.touch)
lambda$getInstance$0:76, ItemClickHandler (com.android.launcher3.touch)
onClick:-1, -$$Lambda$ItemClickHandler$_rHy-J5yxnvGlFKWSh6CdrSf-lA (com.android.launcher3.touch)
performClick:7259, View (android.view)
performClickInternal:7236, View (android.view)
access$3600:801, View (android.view)
run:27881, View$PerformClick (android.view)
handleCallback:883, Handler (android.os)
dispatchMessage:100, Handler (android.os)
loop:214, Looper (android.os)
main:7356, ActivityThread (android.app)
invoke:-1, Method (java.lang.reflect)
run:492, RuntimeInit$MethodAndArgsCaller (com.android.internal.os)
main:930, ZygoteInit (com.android.internal.os)

1.2 应用图标onClick事件注册-setOnClickListener-都是ItemClickHandler

// AllAppsGridAdapter.java文件
    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        switch (viewType) {
            case VIEW_TYPE_ICON:
                BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
                        R.layout.all_apps_icon, parent, false);
                icon.setOnClickListener(ItemClickHandler.INSTANCE);
                icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
                icon.setLongPressTimeoutFactor(1f);
                icon.setOnFocusChangeListener(mIconFocusListener);

                // Ensure the all apps icon height matches the workspace icons in portrait mode.
                icon.getLayoutParams().height = mLauncher.getDeviceProfile().allAppsCellHeightPx;
                return new ViewHolder(icon);
            case VIEW_TYPE_EMPTY_SEARCH:
                return new ViewHolder(mLayoutInflater.inflate(R.layout.all_apps_empty_search,
                        parent, false));
            case VIEW_TYPE_SEARCH_MARKET:
                View searchMarketView = mLayoutInflater.inflate(R.layout.all_apps_search_market,
                        parent, false);
                searchMarketView.setOnClickListener(v -> mLauncher.startActivitySafely(
                        v, mMarketSearchIntent, null, AppLaunchTracker.CONTAINER_SEARCH));
                return new ViewHolder(searchMarketView);
            case VIEW_TYPE_ALL_APPS_DIVIDER:
                return new ViewHolder(mLayoutInflater.inflate(
                        R.layout.all_apps_divider, parent, false));
            case VIEW_TYPE_WORK_TAB_FOOTER:
                View footer = mLayoutInflater.inflate(R.layout.work_tab_footer, parent, false);
                return new ViewHolder(footer);
            default:
                throw new RuntimeException("Unexpected view type");
        }
    }

// Launcher.java文件,创建快捷方式
    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;
    }

1.3 startActivitySafely-启动应用

    public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
            @Nullable String sourceContainer) {
        if (!hasBeenResumed()) {
            // Workaround an issue where the WM launch animation is clobbered when finishing the
            // recents animation into launcher. Defer launching the activity until Launcher is
            // next resumed.
            addOnResumeCallback(() -> startActivitySafely(v, intent, item, sourceContainer));
            UiFactory.clearSwipeSharedState(true /* finishAnimation */);
            return true;
        }
// 走这里
        boolean success = super.startActivitySafely(v, intent, item, sourceContainer);
        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);
        }
        return success;
    }

1.4 startActivityForResult-启动应用

    public void startActivityForResult(Intent intent, int requestCode, Bundle options) {
        if (requestCode != -1) {
            mPendingActivityRequestCode = requestCode;
        }
        if (requestCode == -1	// requestCode == -1,走这里
                || !UiFactory.startActivityForResult(this, intent, requestCode, options)) {
            super.startActivityForResult(intent, requestCode, options);
        }
    }

2. ItemClickHandler

2.1 INSTANCE-单例模式-OnClickListener接口只创建一个对象

public static final OnClickListener INSTANCE = getInstance(null);
// 实现View的OnClickListener接口
    public static final OnClickListener getInstance(String sourceContainer) {
        // 调用ItemClickHandler的onClick函数
        return v -> onClick(v, sourceContainer);
    }

2.2 onClick-点击应用

// com.android.launcher3.views.DoubleShadowBubbleTextView{e30f73 VFED..CL. ...P.... 330,378-440,504 #6} , null
private static void onClick(View v, String sourceContainer) {
        // 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;

        Launcher launcher = Launcher.getLauncher(v.getContext());
        if (!launcher.getWorkspace().isFinishedSwitchingState()) return;
// WorkspaceItemInfo(id=6 type=APP container=desktop screen=1 cell(3,3) span(1,1) minSpan(1,1) rank=0 user=UserHandle{0} title=Gallery)
        Object tag = v.getTag();
        if (tag instanceof WorkspaceItemInfo) {	// 桌面快捷方式,走这里
            onClickAppShortcut(v, (WorkspaceItemInfo) tag, launcher, sourceContainer);
        } else if (tag instanceof FolderInfo) {
            if (v instanceof FolderIcon) {
                onClickFolderIcon(v);
            }
        } else if (tag instanceof AppInfo) {	// 第二级菜单的全部应用,走这里
            startAppShortcutOrInfoActivity(v, (AppInfo) tag, launcher,
                    sourceContainer == null ? CONTAINER_ALL_APPS: sourceContainer);
        } else if (tag instanceof LauncherAppWidgetInfo) {
            if (v instanceof PendingAppWidgetHostView) {
                onClickPendingWidget((PendingAppWidgetHostView) v, launcher);
            }
        }
    }

2.3 onClickAppShortcut-点击桌面的应用快捷方式

    public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher,
            @Nullable String sourceContainer) {
        if (shortcut.isDisabled()) {
            final int disabledFlags = shortcut.runtimeStatusFlags
                    & WorkspaceItemInfo.FLAG_DISABLED_MASK;
            if ((disabledFlags &
                    ~FLAG_DISABLED_SUSPENDED &
                    ~FLAG_DISABLED_QUIET_USER) == 0) {
                // If the app is only disabled because of the above flags, launch activity anyway.
                // Framework will tell the user why the app is suspended.
            } else {
                if (!TextUtils.isEmpty(shortcut.disabledMessage)) {
                    // Use a message specific to this shortcut, if it has one.
                    Toast.makeText(launcher, shortcut.disabledMessage, Toast.LENGTH_SHORT).show();
                    return;
                }
                // Otherwise just use a generic error message.
                int error = R.string.activity_not_available;
                if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) {
                    error = R.string.safemode_shortcut_error;
                } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 ||
                        (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) {
                    error = R.string.shortcut_not_available;
                }
                Toast.makeText(launcher, error, Toast.LENGTH_SHORT).show();
                return;
            }
        }

        // Check for abandoned promise
        if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
            String packageName = shortcut.intent.getComponent() != null ?
                    shortcut.intent.getComponent().getPackageName() : shortcut.intent.getPackage();
            if (!TextUtils.isEmpty(packageName)) {
                onClickPendingAppItem(v, launcher, packageName,
                        shortcut.hasStatusFlag(WorkspaceItemInfo.FLAG_INSTALL_SESSION_ACTIVE));
                return;
            }
        }

        // Start activities
        // 启动桌面快捷方式的应用
        startAppShortcutOrInfoActivity(v, shortcut, launcher, sourceContainer);
    }

2.4 startAppShortcutOrInfoActivity

    private static void startAppShortcutOrInfoActivity(View v, ItemInfo item, Launcher launcher,
            @Nullable String sourceContainer) {
        Intent intent;
        if (item instanceof PromiseAppInfo) {
            PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
            intent = promiseAppInfo.getMarketIntent(launcher);
        } else {
            // Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 pkg=com.android.gallery3d cmp=com.android.gallery3d/.app.GalleryActivity bnds=[350,426][460,552] }
            intent = item.getIntent();
        }
        if (intent == null) {
            throw new IllegalArgumentException("Input must have a valid intent");
        }
        if (item instanceof WorkspaceItemInfo) {
            WorkspaceItemInfo si = (WorkspaceItemInfo) item;
            if (si.hasStatusFlag(WorkspaceItemInfo.FLAG_SUPPORTS_WEB_UI)
                    && Intent.ACTION_VIEW.equals(intent.getAction())) {
                // make a copy of the intent that has the package set to null
                // we do this because the platform sometimes disables instant
                // apps temporarily (triggered by the user) and fallbacks to the
                // web ui. This only works though if the package isn't set
                intent = new Intent(intent);
                intent.setPackage(null);
            }
        }
        if (v != null && launcher.getAppTransitionManager().supportsAdaptiveIconAnimation()) {
            // Preload the icon to reduce latency b/w swapping the floating view with the original.
            FloatingIconView.fetchIcon(launcher, v, item, true /* isOpening */);
        }
        launcher.startActivitySafely(v, intent, item, sourceContainer);
    }

3. BaseDraggingActivity

3.1 startActivitySafely-启动应用

    public boolean startActivitySafely(View v, Intent intent, @Nullable ItemInfo item,
            @Nullable String sourceContainer) {
        if (mIsSafeModeEnabled && !Utilities.isSystemApp(this, intent)) {
            Toast.makeText(this, R.string.safemode_shortcut_error, Toast.LENGTH_SHORT).show();
            return false;
        }

        Bundle optsBundle = (v != null) ? getActivityLaunchOptionsAsBundle(v) : null;
        UserHandle user = item == null ? null : item.user;

        // Prepare intent
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        if (v != null) {
            intent.setSourceBounds(getViewBounds(v));
        }
        try { // false
            boolean isShortcut = (item instanceof WorkspaceItemInfo)
                    && (item.itemType == Favorites.ITEM_TYPE_SHORTCUT
                    || item.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT)
                    && !((WorkspaceItemInfo) item).isPromise();
            if (isShortcut) {
                // Shortcuts need some special checks due to legacy reasons.
                startShortcutIntentSafely(intent, optsBundle, item, sourceContainer);
                // user = 0
            } else if (user == null || user.equals(Process.myUserHandle())) {
                // Could be launching some bookkeeping activity
                // Bundle[{android:activity.remoteAnimationAdapter=android.view.RemoteAnimationAdapter@f0315e0, android:activity.animType=13}]
                startActivity(intent, optsBundle);
                AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(),
                        Process.myUserHandle(), sourceContainer);
            } else {
                LauncherAppsCompat.getInstance(this).startActivityForProfile(
                        intent.getComponent(), user, intent.getSourceBounds(), optsBundle);
                AppLaunchTracker.INSTANCE.get(this).onStartApp(intent.getComponent(), user,
                        sourceContainer);
            }
            getUserEventDispatcher().logAppLaunch(v, intent);
            getStatsLogManager().logAppLaunch(v, intent);
            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;
    }

应用接口层

4. Activity

frameworks/base/core/java/android/app/Activity.java

4.1 startActivity-启动应用

    public void startActivity(Intent intent, @Nullable Bundle options) {
        if (options != null) {	// 走这里
            startActivityForResult(intent, -1, options);
        } else {
            // Note we want to go through this call for compatibility with
            // applications that may have overridden the method.
            startActivityForResult(intent, -1);
        }
    }

4.2 startActivityForResult-启动应用

    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
            @Nullable Bundle options) {
        if (mParent == null) {	// mParent == null走这里
            options = transferSpringboardActivityOptions(options);
            Instrumentation.ActivityResult ar =		// 这里启动应用
                mInstrumentation.execStartActivity(
                    this, mMainThread.getApplicationThread(), mToken, this,
                    intent, requestCode, options);
            if (ar != null) {
                mMainThread.sendActivityResult(
                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                    ar.getResultData());
            }
            if (requestCode >= 0) {
                // If this start is requesting a result, we can avoid making
                // the activity visible until the result is received.  Setting
                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
                // activity hidden during this time, to avoid flickering.
                // This can only be done when a result is requested because
                // that guarantees we will get information back when the
                // activity is finished, no matter what happens to it.
                mStartedActivity = true;
            }

            cancelInputsAndStartExitTransition(options);
            // TODO Consider clearing/flushing other event sources and events for child windows.
        } else {
            if (options != null) {
                mParent.startActivityFromChild(this, intent, requestCode, options);
            } else {
                // Note we want to go through this method for compatibility with
                // existing applications that may have overridden it.
                mParent.startActivityFromChild(this, intent, requestCode);
            }
        }
    }

5. Instrumentation

frameworks/base/core/java/android/app/Instrumentation.java

5.1 execStartActivity

// who: Launcher;contextThread:activitythread;token:BinderProxy
// target:Launcher;intent:Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10200000 pkg=com.android.gallery3d cmp=com.android.gallery3d/.app.GalleryActivity bnds=[350,426][460,552] }
// requestCode:-1;options:Bundle[{android:activity.remoteAnimationAdapter=android.view.RemoteAnimationAdapter@f0315e0, android:activity.animType=13}]
@UnsupportedAppUsage
    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode:-1, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    ActivityResult result = null;
                    if (am.ignoreMatchingSpecificIntents()) {
                        result = am.onStartActivity(intent);
                    }
                    if (result != null) {
                        am.mHits++;
                        return result;
                    } else if (am.match(who, null, intent)) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return requestCode >= 0 ? am.getResult() : null;
                        }
                        break;
                    }
                }
            }
        }
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            // 这里调用activitytaskmanager来启动应用
            int result = ActivityTaskManager.getService()
                .startActivity(whoThread, who.getBasePackageName(), intent,
                        intent.resolveTypeIfNeeded(who.getContentResolver()),
                        token, target != null ? target.mEmbeddedID : null,
                        requestCode, 0, null, options);
            checkStartActivityResult(result, intent);
        } catch (RemoteException e) {
            throw new RuntimeException("Failure from system", e);
        }
        return null;
    }

补充

1. java

1.1 lambda表达式

// (argument) -> {expression body}
// argument代表匿名接口setOnClickListener的onClick函数的参数,View view
// 表达式主体是您要执行的代码,其实就是函数主体。可以是单个表达式或多行代码。

// 以button为例子
// 1. 创建了一个匿名类
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
   @Override
   public void onClick(View view) {
       doSomething();
   }
});
// 2. 改用lambda表达式,单行搞定
button.setOnClickListener(view -> doSomething());

1.2 匿名类-继承父类或者实现接口

一个类里面的匿名内部类

class Polygon {	// 父类
   public void display() {
      System.out.println("在 Polygon 类内部");
   }
}
interface Polygon {	// 接口
   public void display();
}

class AnonymousDemo {
   public void createClass() {

      // 创建的匿名类继承了 Polygon 类
      Polygon p1 = new Polygon() {
         public void display() {
            System.out.println("在匿名类内部。");
         }
      };
      p1.display();
   }
}

class Main {
   public static void main(String[] args) {
       AnonymousDemo an = new AnonymousDemo();
       an.createClass();
   }
}
输出:
在匿名类内部。

OnClickListener就是View类里面的一个接口

frameworks/base/core/java/android/view/View.java

    public interface OnClickListener {
        /**
         * Called when a view has been clicked.
         *
         * @param v The view that was clicked.
         */
        void onClick(View v);
    }

问题

1. windowtoken是啥
2. view的gettag是啥
2. 最近应用列表也是一个activity吗?

参考

1. 适用于Android的Java 8:使用Lambda表达式的简介代码
https://code.tutsplus.com/zh-hans/tutorials/java-8-for-android-cleaner-code-with-lambda-expressions--cms-29661
2. Java 匿名类
https://www.runoob.com/java/java-anonymous-class.html
posted @ 2021-07-04 12:25  pyjetson  阅读(378)  评论(0编辑  收藏  举报