Launcher启动流程及初始化

前言

前面我们学习了SystemServer的启动流程,也了解了AMS是如何启动起来并通过Binder注册到ServiceManger内的,OK,本文基于这俩篇基础继续来学习Launcher。

  • Launcher是如何启动起来的
  • Launcher启动起来之后自身的流程是怎样初始化的

PS:本文的流程分析基于android_2.3.7,高版本的源码和本篇文章流程分析略有出入,请注意自己当前源码的版本。

流程代码分析

1.Launcher是如何启动起来的

流程图

frameworks\base\services\java\com\android\server\SystemServer.java$ServerThread#run()



class ServerThread extends Thread {
    private static final String TAG = "SystemServer";

    ......

    @Override
    public void run() {
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN,
            SystemClock.uptimeMillis());

        ......
        ((ActivityManagerService)ActivityManagerNative.getDefault())
                .systemReady(new Runnable() {
            public void run() {
                Slog.i(TAG, "Making services ready");

                if (statusBarF != null) statusBarF.systemReady2();
                if (batteryF != null) batteryF.systemReady();
                if (connectivityF != null) connectivityF.systemReady();
                if (dockF != null) dockF.systemReady();
                if (usbF != null) usbF.systemReady();
                if (uiModeF != null) uiModeF.systemReady();
                if (recognitionF != null) recognitionF.systemReady();
                Watchdog.getInstance().start();

                // It is now okay to let the various system services start their
                // third party code...

                if (appWidgetF != null) appWidgetF.systemReady(safeMode);
                if (wallpaperF != null) wallpaperF.systemReady();
                if (immF != null) immF.systemReady();
                if (locationF != null) locationF.systemReady();
                if (throttleF != null) throttleF.systemReady();
            }
        });

      ......
    }
}

这个getDefault实际上返回的就是Ams。强转之后去调用systemReady()。

frameworks\base\services\java\com\android\server\am\ActivityManagerService.java#systemReady()

    public void systemReady(final Runnable goingCallback) {
        // In the simulator, startRunning will never have been called, which
        // normally sets a few crucial variables. Do it here instead.
         ......

        synchronized(this) {
         ......

            mMainStack.resumeTopActivityLocked(null);
        }
    }

systemReady内调用了mMainStack的resumeTopActivityLocked方法,点进去。
延伸思考:ActivityStack mMainStack是什么?

frameworks\base\services\java\com\android\server\am\ActivityStack.java#resumeTopActivityLocked()


    /**
     * Ensure that the top activity in the stack is resumed.
     *
     * @param prev The previously resumed activity, for when in the process
     * of pausing; can be null to call from elsewhere.
     *
     * @return Returns true if something is being resumed, or false if
     * nothing happened.
     */
    final boolean resumeTopActivityLocked(ActivityRecord prev) {
        // Find the first activity that is not finishing.
        ActivityRecord next = topRunningActivityLocked(null);

        // Remember how we'll process this pause/resume situation, and ensure
        // that the state is reset however we wind up proceeding.
        final boolean userLeaving = mUserLeaving;
        mUserLeaving = false;

        if (next == null) {
            // There are no more activities!  Let's just start up the
            // Launcher...
            if (mMainStack) {
                return mService.startHomeActivityLocked();
            }
        }

        next.delayedResume = false;

        ......

        return true;
    }

ActivityStack.javaresumeTopActivityLocked()方法内又去调用了mService.startHomeActivityLocked(),这个mService就是ActivityManagerService
那么再回到ActivityManagerService去看startHomeActivityLocked()

frameworks\base\core\java\android\app\ActivityManagerService.java#startHomeActivityLocked()

  boolean startHomeActivityLocked() {

        if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL
                && mTopAction == null) {
            // We are running in factory test mode, but unable to find
            // the factory test app, so just sit around displaying the
            // error message and don't try to start anything.
            return false;
        }

        Intent intent = new Intent(
            mTopAction,
            mTopData != null ? Uri.parse(mTopData) : null);

        intent.setComponent(mTopComponent);

        if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
            intent.addCategory(Intent.CATEGORY_HOME);
        }

        //向PKMS查询满足条件的ActivityInfo
        ActivityInfo aInfo =
            intent.resolveActivityInfo(mContext.getPackageManager(),
                    STOCK_PM_FLAGS);

        if (aInfo != null) {
            intent.setComponent(new ComponentName(
                    aInfo.applicationInfo.packageName, aInfo.name));
            // Don't do this if the home app is currently being
            // instrumented.
            //在正常情况下,app应该为null,因为刚开机,Home进程肯定还没启动
            ProcessRecord app = getProcessRecordLocked(aInfo.processName,
                    aInfo.applicationInfo.uid);

            if (app == null || app.instrumentationClass == null) {
                //启动Home
                intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK);
                mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo,
                        null, null, 0, 0, 0, false, false);
            }
        }

        return true;

    }

这儿就是启动Launcher的事发现场了。代码内注释了//启动Home处。
0.先是为启动Intent设置了启动flag
1.然后设置了一个intent.addCategory(Intent.CATEGORY_HOME);
2.再调用mMainStack.startActivityLocked方法,这里Launcher就启动起来了。

启动之后,我们再去看Launcher的UI,可以想象到的是,至少有一个列表来展示各个App的图标,然后我们再重点深入去看它的点击事件,用于后续分析App的启动流程。

PS:
另外我试图追了下,mMainStack.startActivityLocked()这个方法,感觉涉及的东西挺多,一时半会也无法理解后续流程,这里我先放一放,等我系统的了解了ActivityRecord,TaskRecord,ActivityStack,WMS这部分知识的时候,再回过头来往下深究。

2.启动起来之后自身的流程是怎样初始化点击事件的

流程图

packages\apps\Launcher2\src\com\android\launcher2\Launcher.java#onCreate


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //获取launcher的application
        LauncherApplication app = ((LauncherApplication)getApplication());

        //LauncherMode
        mModel = app.setLauncher(this);

        //iconCatche
        mIconCache = app.getIconCache();
        mDragController = new DragController(this);

        //inflater
        mInflater = getLayoutInflater();

        //AppWidgetManager   android桌面控件相关
        mAppWidgetManager = AppWidgetManager.getInstance(this);
        mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
        mAppWidgetHost.startListening();

        if (PROFILE_STARTUP) {
            android.os.Debug.startMethodTracing("/sdcard/launcher");
        }

        loadHotseats();
        checkForLocaleChange();
        setWallpaperDimension();

        setContentView(R.layout.launcher);

        //UI初始化
        setupViews();

        //注册观察者
        registerContentObservers();

        lockAllApps();

        mSavedState = savedInstanceState;
        restoreState(mSavedState);

        if (PROFILE_STARTUP) {
            android.os.Debug.stopMethodTracing();
        }

        //重要 这里开启桌面及cellLayout的初始化
        if (!mRestoring) {
            mModel.startLoader(this, true);
        }

        // For handling default keys
        mDefaultKeySsb = new SpannableStringBuilder();
        Selection.setSelection(mDefaultKeySsb, 0);

        IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        registerReceiver(mCloseSystemDialogsReceiver, filter);
    }

1.setupViews(),初始化UI

2.注意一下这mModel.startLoader(this, true);。它是后续对屏幕WorkSpace初始化的一个关键节点。它比较重要.

packages\apps\Launcher2\src\com\android\launcher2\Launcher.java#setupViews


      /**
     * Finds all the views we need and configure them properly.
     */
    private void setupViews() {
        DragController dragController = mDragController;

        DragLayer dragLayer = (DragLayer) findViewById(R.id.drag_layer);
        dragLayer.setDragController(dragController);

        mAllAppsGrid = (AllAppsView)dragLayer.findViewById(R.id.all_apps_view);
        mAllAppsGrid.setLauncher(this);
        mAllAppsGrid.setDragController(dragController);
        ((View) mAllAppsGrid).setWillNotDraw(false); // We don't want a hole punched in our window.
        // Manage focusability manually since this thing is always visible
        ((View) mAllAppsGrid).setFocusable(false); 

        //Workspace 是一个ViewGroup 
        mWorkspace = (Workspace) dragLayer.findViewById(R.id.workspace);
        final Workspace workspace = mWorkspace;
        workspace.setHapticFeedbackEnabled(false);

        //删除区域  就是我们拖个图标往哪一放就删掉了
        DeleteZone deleteZone = (DeleteZone) dragLayer.findViewById(R.id.delete_zone);
        mDeleteZone = deleteZone;

        mHandleView = (HandleView) findViewById(R.id.all_apps_button);
        mHandleView.setLauncher(this);
        mHandleView.setOnClickListener(this);
        mHandleView.setOnLongClickListener(this);

        ImageView hotseatLeft = (ImageView) findViewById(R.id.hotseat_left);
        hotseatLeft.setContentDescription(mHotseatLabels[0]);
        hotseatLeft.setImageDrawable(mHotseatIcons[0]);
        ImageView hotseatRight = (ImageView) findViewById(R.id.hotseat_right);
        hotseatRight.setContentDescription(mHotseatLabels[1]);
        hotseatRight.setImageDrawable(mHotseatIcons[1]);

        mPreviousView = (ImageView) dragLayer.findViewById(R.id.previous_screen);
        mNextView = (ImageView) dragLayer.findViewById(R.id.next_screen);

        Drawable previous = mPreviousView.getDrawable();
        Drawable next = mNextView.getDrawable();
        mWorkspace.setIndicators(previous, next);

        mPreviousView.setHapticFeedbackEnabled(false);
        mPreviousView.setOnLongClickListener(this);
        mNextView.setHapticFeedbackEnabled(false);
        mNextView.setOnLongClickListener(this);

        //点击事件处理 从这里分析点击时app是如何启动起来的
        workspace.setOnLongClickListener(this);
        workspace.setDragController(dragController);
        workspace.setLauncher(this);

        //删除区域设置
        deleteZone.setLauncher(this);
        deleteZone.setDragController(dragController);
        deleteZone.setHandle(findViewById(R.id.all_apps_button_cluster));

        dragController.setDragScoller(workspace);
        dragController.setDragListener(deleteZone);
        dragController.setScrollView(dragLayer);
        dragController.setMoveTarget(workspace);

        // The order here is bottom to top.
        dragController.addDropTarget(workspace);
        dragController.addDropTarget(deleteZone);
    }

这里主要就是初始化了桌面widget区域,删除区域,以及最重要的WorkSpace区域。

packages\apps\Launcher2\src\com\android\launcher2\Workspace.java


/**
 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
 * screen contains a number of icons, folders or widgets the user can interact with.
 * A workspace is meant to be used with a fixed width only.

 工作区是一个宽的区域,有壁纸和有限的屏幕。
 每个屏幕都包含一些用户可以与之交互的图标、文件夹或小部件。工作空间只用于固定的宽度。
 */
public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
    ......
}

packages\apps\Launcher2\src\com\android\launcher2\Workspace.java


/**
 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
 * screen contains a number of icons, folders or widgets the user can interact with.
 * A workspace is meant to be used with a fixed width only.

 工作区是一个宽的区域,有壁纸和有限的屏幕。
 每个屏幕都包含一些用户可以与之交互的图标、文件夹或小部件。工作空间只用于固定的宽度。
 */
public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
    ......

    /**
     * Used to inflate the Workspace from XML.
     *
     * @param context The application's context.
     * @param attrs The attribtues set containing the Workspace's customization values.
     * @param defStyle Unused.
     */
    public Workspace(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);

        mWallpaperManager = WallpaperManager.getInstance(context);

        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Workspace, defStyle, 0);
        mDefaultScreen = a.getInt(R.styleable.Workspace_defaultScreen, 1);
        a.recycle();

        setHapticFeedbackEnabled(false);
        initWorkspace();
    }
}

packages\apps\Launcher2\src\com\android\launcher2\Workspace.java#initWorkspace()


/**
 * The workspace is a wide area with a wallpaper and a finite number of screens. Each
 * screen contains a number of icons, folders or widgets the user can interact with.
 * A workspace is meant to be used with a fixed width only.

 工作区是一个宽的区域,有壁纸和有限的屏幕。
 每个屏幕都包含一些用户可以与之交互的图标、文件夹或小部件。工作空间只用于固定的宽度。
 */
public class Workspace extends ViewGroup implements DropTarget, DragSource, DragScroller {
    ......

     /**
     * Initializes various states for this workspace.
     */
    private void initWorkspace() {
        Context context = getContext();
        mScrollInterpolator = new WorkspaceOvershootInterpolator();
        mScroller = new Scroller(context, mScrollInterpolator);
        mCurrentScreen = mDefaultScreen;
        Launcher.setScreen(mCurrentScreen);
        LauncherApplication app = (LauncherApplication)context.getApplicationContext();
        mIconCache = app.getIconCache();

        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = configuration.getScaledTouchSlop();
        mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
    }
}

1.Scroller初始化,用于滑动。
2.mIconCache,图标信息初始化。
3.到这里执行完了,我们并没有发现有初始化icon图标的操作。我们将函数一步步返回,回到Launcher中mModel.startLoader(this, true);去。

packages\apps\Launcher2\src\com\android\launcher2\LauncherModel.java#startLoader()

    public void startLoader(Context context, boolean isLaunching) {
        synchronized (mLock) {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
            }

            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {
                // If there is already one running, tell it to stop.
                LoaderTask oldTask = mLoaderTask;
                if (oldTask != null) {
                    if (oldTask.isLaunching()) {
                        // don't downgrade isLaunching if we're already running
                        isLaunching = true;
                    }
                    oldTask.stopLocked();
                }

                //LoaderTask初始化
                mLoaderTask = new LoaderTask(context, isLaunching);
                sWorker.post(mLoaderTask);
            }
        }
    }

1.初始化LoaderTask
2.执行LoaderTask

packages\apps\Launcher2\src\com\android\launcher2\LauncherModel.java$LoaderTask#run()



    /**
     * Runnable for the thread that loads the contents of the launcher:
     *   - workspace icons
     *   - widgets
     *   - all apps icons
     */
    private class LoaderTask implements Runnable {
        ......

        public void run() {
                ......

                // second step
                if (loadWorkspaceFirst) {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                    loadAndBindAllApps();
                } else {
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace");
                    loadAndBindWorkspace();//处理桌面上的图标
                }

                ......
        }
    }

这个任务里包含了所有的图标。

packages\apps\Launcher2\src\com\android\launcher2\LauncherModel.java$LoaderTask#loadAndBindWorkspace()


        private void loadAndBindWorkspace() {
            // Load the workspace

            ......
            // Bind the workspace
            bindWorkspace();
        }

packages\apps\Launcher2\src\com\android\launcher2\LauncherModel.java$LoaderTask#bindWorkspace()

 /**
         * Read everything out of our database.
         */
        private void bindWorkspace() {
            ......

            int N;
            // Tell the workspace that we're about to start firing items at it
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.startBinding();//1
                    }
                }
            });
            // Add the items to the workspace.
            N = mItems.size();
            for (int i=0; i<N; i+=ITEMS_CHUNK) {
                final int start = i;
                final int chunkSize = (i+ITEMS_CHUNK <= N) ? ITEMS_CHUNK : (N-i);
                mHandler.post(new Runnable() {
                    public void run() {
                        Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                        if (callbacks != null) {
                            callbacks.bindItems(mItems, start, start+chunkSize);//2
                        }
                    }
                });
            }
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.bindFolders(mFolders);//3
                    }
                }
            });
            // Wait until the queue goes empty.
            mHandler.post(new Runnable() {
                public void run() {
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "Going to start binding widgets soon.");
                    }
                }
            });
            // Bind the widgets, one at a time.
            // WARNING: this is calling into the workspace from the background thread,
            // but since getCurrentScreen() just returns the int, we should be okay.  This
            // is just a hint for the order, and if it's wrong, we'll be okay.
            // TODO: instead, we should have that push the current screen into here.
            final int currentScreen = oldCallbacks.getCurrentWorkspaceScreen();
            N = mAppWidgets.size();
            // once for the current screen
            for (int i=0; i<N; i++) {
                final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
                if (widget.screen == currentScreen) {
                    mHandler.post(new Runnable() {
                        public void run() {
                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                            if (callbacks != null) {
                                callbacks.bindAppWidget(widget);//4
                            }
                        }
                    });
                }
            }
            // once for the other screens
            for (int i=0; i<N; i++) {
                final LauncherAppWidgetInfo widget = mAppWidgets.get(i);
                if (widget.screen != currentScreen) {
                    mHandler.post(new Runnable() {
                        public void run() {
                            Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                            if (callbacks != null) {
                                callbacks.bindAppWidget(widget);//5
                            }
                        }
                    });
                }
            }
            // Tell the workspace that we're done.
            mHandler.post(new Runnable() {
                public void run() {
                    Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.finishBindingItems();//6
                    }
                }
            });
            // If we're profiling, this is the last thing in the queue.
            mHandler.post(new Runnable() {
                public void run() {
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound workspace in "
                            + (SystemClock.uptimeMillis()-t) + "ms");
                    }
                }
            });
        }

这里代码多,有许多bind方法,不必全部看,关注本文分析的主线,注释1处是准备开始bind做的一些准备,我们从注释2处切入进去。
看到它是一个抽象接口进行的一个bind操作。那么这个接口的实现是啥?它的实现类就是我们的Launcher这个类。

packages\apps\Launcher2\src\com\android\launcher2\Launcher.java#bindItems()【重点】


    /**
     * Bind the items start-end from the list.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end) {

        setLoadOnResume();

        final Workspace workspace = mWorkspace;

        for (int i=start; i<end; i++) {
            final ItemInfo item = shortcuts.get(i);
            mDesktopItems.add(item);
            switch (item.itemType) {
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                    final View shortcut = createShortcut((ShortcutInfo)item);
                    workspace.addInScreen(shortcut, item.screen, item.cellX, item.cellY, 1, 1,
                            false);  //1...
                    break;
                case LauncherSettings.Favorites.ITEM_TYPE_USER_FOLDER:
                    final FolderIcon newFolder = FolderIcon.fromXml(R.layout.folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
                            (UserFolderInfo) item);
                    workspace.addInScreen(newFolder, item.screen, item.cellX, item.cellY, 1, 1,
                            false); //2...
                    break;
                case LauncherSettings.Favorites.ITEM_TYPE_LIVE_FOLDER:
                    final FolderIcon newLiveFolder = LiveFolderIcon.fromXml(
                            R.layout.live_folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentScreen()),
                            (LiveFolderInfo) item);
                    workspace.addInScreen(newLiveFolder, item.screen, item.cellX, item.cellY, 1, 1,
                            false); //3...
                    break;
            }
        }

        workspace.requestLayout();
    }

1.这个方法就到了本文的主线目的地,首先它通过createShortcut方法来对每个item点击时启动的app进行初始化。
2.这里可以看到3处都调用了workspace.addInScreen。这里边会初始化CellLayout。一会我们再去WorkSpace这个类里边分析addInScreen这个方法

packages\apps\Launcher2\src\com\android\launcher2\Launcher.java#createShortcut(ShortcutInfo info)

  /**
     * Creates a view representing a shortcut.
     *
     * @param info The data structure describing the shortcut.
     *
     * @return A View inflated from R.layout.application.
     */
    View createShortcut(ShortcutInfo info) {
        return createShortcut(R.layout.application,
                (ViewGroup) mWorkspace.getChildAt(mWorkspace.getCurrentScreen()), info);
    }

packages\apps\Launcher2\src\com\android\launcher2\Launcher.java#createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info)


    /**
     * Creates a view representing a shortcut inflated from the specified resource.
     *
     * @param layoutResId The id of the XML layout used to create the shortcut.
     * @param parent The group the shortcut belongs to.
     * @param info The data structure describing the shortcut.
     *
     * @return A View inflated from layoutResId.
     */
    View createShortcut(int layoutResId, ViewGroup parent, ShortcutInfo info) {
        TextView favorite = (TextView) mInflater.inflate(layoutResId, parent, false);

        favorite.setCompoundDrawablesWithIntrinsicBounds(null,
                new FastBitmapDrawable(info.getIcon(mIconCache)),
                null, null);
        favorite.setText(info.title);
        favorite.setTag(info);
        favorite.setOnClickListener(this);//这里点击时 启动app

        return favorite;
    }

这里首先inflater出item的布局,然后设置text和OnClickListener,还有tag,这个tag是ApplicationInfo,里面包含了各种App信息,是从App的AndroidManifest.xml的标签中解析出来的。(TODO具体是如何解析的流程,本文暂时不去深究,后续分析到了这部分再补上)既然设置了点击事件,显然,点击后应该会打开对应的App才对。所以继续看onClick方法

packages\apps\Launcher2\src\com\android\launcher2\Launcher.java#onClick()

    /**
     * Launches the intent referred by the clicked shortcut.
     *
     * @param v The view representing the clicked shortcut.
     */
    public void onClick(View v) {
        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {

            // Open shortcut  主要看这里 
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));

            //启动app
            startActivitySafely(intent, tag);

        } else if (tag instanceof FolderInfo) {
            handleFolderClick((FolderInfo) tag);
        } else if (v == mHandleView) {
            if (isAllAppsVisible()) {
                closeAllApps(true);
            } else {
                showAllApps(true);
            }
        }
    }

这里的intent.setSourceBounds很奇怪,这里我们先不管他,重点需要注意是Object tag = v.getTag();得到之前我们从createShortcut中设置的appInfo信息,所以当我们点击icon的时候才能准确的跳转到指定app。

这里也推翻了我最开始的一个猜想,我一开始想得很简单,laucher就一个列表,然后一个itemClickListener设置完点击启动app完事,但是经过分析发现,laucher点击启动app和绘制完全是分开的。

packages\apps\Launcher2\src\com\android\launcher2\Launcher.java#startActivitySafely()


    void startActivitySafely(Intent intent, Object tag) {
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        try {
            //去打开activity
            startActivity(intent);

        } catch (ActivityNotFoundException e) {
            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
            Log.e(TAG, "Unable to launch. tag=" + tag + " intent=" + intent, e);
        } catch (SecurityException e) {
            Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT).show();
            Log.e(TAG, "Launcher does not have the permission to launch " + intent +
                    ". Make sure to create a MAIN intent-filter for the corresponding activity " +
                    "or use the exported attribute for this activity. "
                    + "tag="+ tag + " intent=" + intent, e);
        }
    }

OK,到此本文的任务就完成了,但是我们接下来的还需要把剩下的另外一个方法分析完。这个方法就是WorkSpace.java#addInScreen,通过它我们才能了解到我们之前所猜想的”列表”这个东西。

packages\apps\Launcher2\src\com\android\launcher2\WorkSpace.java#addInScreen()


   /**
     * Adds the specified child in the specified screen. The position and dimension of
     * the child are defined by x, y, spanX and spanY.
     *
     * @param child The child to add in one of the workspace's screens.
     * @param screen The screen in which to add the child.
     * @param x The X position of the child in the screen's grid.
     * @param y The Y position of the child in the screen's grid.
     * @param spanX The number of cells spanned horizontally by the child.
     * @param spanY The number of cells spanned vertically by the child.
     * @param insert When true, the child is inserted at the beginning of the children list.
     */
    void addInScreen(View child, int screen, int x, int y, int spanX, int spanY, boolean insert) {

        ......

        final CellLayout group = (CellLayout) getChildAt(screen);
        CellLayout.LayoutParams lp = (CellLayout.LayoutParams) child.getLayoutParams();
        if (lp == null) {
            lp = new CellLayout.LayoutParams(x, y, spanX, spanY);// 1.cellLayout
        } else {
            lp.cellX = x;
            lp.cellY = y;
            lp.cellHSpan = spanX;
            lp.cellVSpan = spanY;
        }

        group.addView(child, insert ? 0 : -1, lp);

        if (!(child instanceof Folder)) {
            child.setHapticFeedbackEnabled(false);
            child.setOnLongClickListener(mLongClickListener);
        }
        if (child instanceof DropTarget) {
            mDragController.addDropTarget((DropTarget)child);
        }
    }

CellLayout及时我们前文猜想的列表,只是它并不是真正的列表,只是一个轻量级的GridView。这里简单说下,它有个内部类CellInfo存储了一堆的屏幕坐标,它是通过坐标来实现一个gridview的效果。其他它内部具体的实现本文暂时不进行深究。

下一篇分析当点击了launcher桌面图标之后执行startActivity(intent);的具体启动流程细节。也就是app启动流程

Thanks

《深入理解Android卷2》6.2.4章节

猜你喜欢

转载自blog.csdn.net/user11223344abc/article/details/80804839