Launcher3 UI初始化分析

本文深入解析了Launcher启动器的工作原理,从入口Launcher类的onCreate方法开始,详细介绍了如何初始化和配置Launcher,包括设置StrictMode策略、加载配置信息、绑定UI和数据的过程。文章重点阐述了mModel.startLoader方法的作用,即绑定UI和数据,以及如何加载配置数据、数据库信息,和AllApp页的包信息。同时,文章还讲解了数据与UI绑定的细节,包括图标、快捷列表、Widget的UI绑定过程。

一. 入口Launcher类,在其onCreate方法中初始化

  @Override
    protected void onCreate(Bundle savedInstanceState) {
        if (DEBUG_STRICT_MODE) {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()   // or .detectAll() for all detectable problems
                    .penaltyLog()
                    .build());
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectLeakedSqlLiteObjects()
                    .detectLeakedClosableObjects()
                    .penaltyLog()
                    .penaltyDeath()
                    .build());
        }
        if (LauncherAppState.PROFILE_STARTUP) {
            Trace.beginSection("Launcher-onCreate");
        }

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.preOnCreate();
        }

        WallpaperColorInfo wallpaperColorInfo = WallpaperColorInfo.getInstance(this);
        wallpaperColorInfo.setOnThemeChangeListener(this);
        overrideTheme(wallpaperColorInfo.isDark(), wallpaperColorInfo.supportsDarkText());

        super.onCreate(savedInstanceState);

        LauncherAppState app = LauncherAppState.getInstance(this);

        // Load configuration-specific DeviceProfile
        mDeviceProfile = app.getInvariantDeviceProfile().getDeviceProfile(this);
        if (isInMultiWindowModeCompat()) {
            Display display = getWindowManager().getDefaultDisplay();
            Point mwSize = new Point();
            display.getSize(mwSize);
            mDeviceProfile = mDeviceProfile.getMultiWindowProfile(this, mwSize);
        }

        mOrientation = getResources().getConfiguration().orientation;
        mSharedPrefs = Utilities.getPrefs(this);
        mIsSafeModeEnabled = getPackageManager().isSafeMode();
        mModel = app.setLauncher(this);
        mModelWriter = mModel.getWriter(mDeviceProfile.isVerticalBarLayout());
        mIconCache = app.getIconCache();
        mAccessibilityDelegate = new LauncherAccessibilityDelegate(this);

        mDragController = new DragController(this);
        mAllAppsController = new AllAppsTransitionController(this);
        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this, mAllAppsController);

        mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);

        mAppWidgetHost = new LauncherAppWidgetHost(this);
        if (Utilities.ATLEAST_MARSHMALLOW) {
            mAppWidgetHost.addProviderChangeListener(this);
        }
        mAppWidgetHost.startListening();

        // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
        // this also ensures that any synchronous binding below doesn't re-trigger another
        // LauncherModel load.
        mPaused = false;

        mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);

        setupViews();
        mDeviceProfile.layout(this, false /* notifyListeners */);
        loadExtractedColorsAndColorItems();

        mPopupDataProvider = new PopupDataProvider(this);

        ((AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE))
                .addAccessibilityStateChangeListener(this);

        lockAllApps();

        restoreState(savedInstanceState);

        if (LauncherAppState.PROFILE_STARTUP) {
            Trace.endSection();
        }

        // We only load the page synchronously if the user rotates (or triggers a
        // configuration change) while launcher is in the foreground
        int currentScreen = PagedView.INVALID_RESTORE_PAGE;
        if (savedInstanceState != null) {
            currentScreen = savedInstanceState.getInt(RUNTIME_STATE_CURRENT_SCREEN, currentScreen);
        }
        if (!mModel.startLoader(currentScreen)) {
            // If we are not binding synchronously, show a fade in animation when
            // the first page bind completes.
            mDragLayer.setAlpha(0);
        } else {
            // Pages bound synchronously.
            mWorkspace.setCurrentPage(currentScreen);

            setWorkspaceLoading(true);
        }

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

        mRotationEnabled = getResources().getBoolean(R.bool.allow_rotation);
        // In case we are on a device with locked rotation, we should look at preferences to check
        // if the user has specifically allowed rotation.
        if (!mRotationEnabled) {
            mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext());
            mRotationPrefChangeHandler = new RotationPrefChangeHandler();
            mSharedPrefs.registerOnSharedPreferenceChangeListener(mRotationPrefChangeHandler);
        }

        if (PinItemDragListener.handleDragRequest(this, getIntent())) {
            // Temporarily enable the rotation
            mRotationEnabled = true;
        }

        // On large interfaces, or on devices that a user has specifically enabled screen rotation,
        // we want the screen to auto-rotate based on the current orientation
        setOrientation();

        setContentView(mLauncherView);

        // Listen for broadcasts
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        filter.addAction(Intent.ACTION_USER_PRESENT); // When the device wakes up + keyguard is gone
        registerReceiver(mReceiver, filter);
        mShouldFadeInScrim = true;

        getSystemUiController().updateUiState(SystemUiController.UI_STATE_BASE_WINDOW,
                Themes.getAttrBoolean(this, R.attr.isWorkspaceDarkText));

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onCreate(savedInstanceState);
        }
    }

二.绑定UI和数据

1.在onCreate中调用了mModel.startLoader(currentScreen)绑定UI和数据

public boolean startLoader(int synchronousBindPage) {
        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
        synchronized (mLock) {
            // Don't bother to start the thread if we know it's not going to do anything
            if (mCallbacks != null && mCallbacks.get() != null) {
                final Callbacks oldCallbacks = mCallbacks.get();
                // Clear any pending bind-runnables from the synchronized load process.
                mUiExecutor.execute(new Runnable() {
                            public void run() {
                                oldCallbacks.clearPendingBinds();
                            }
                        });

                // If there is already one running, tell it to stop.
                stopLoader();
                LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
                        mBgAllAppsList, synchronousBindPage, mCallbacks);
                if (mModelLoaded && !mIsLoaderTaskRunning) {
                    // Divide the set of loaded items into those that we are binding synchronously,
                    // and everything else that is to be bound normally (asynchronously).
                    loaderResults.bindWorkspace();
                    // For now, continue posting the binding of AllApps as there are other
                    // issues that arise from that.
                    loaderResults.bindAllApps();
                    loaderResults.bindDeepShortcuts();
                    loaderResults.bindWidgets();
                    return true;
                } else {
                    startLoaderForResults(loaderResults);
                }
            }
        }
        return false;
    }

 

2.loadWorkspace为加载配置数据,主要注意点

 加载屏幕个数,

mBgDataModel.workspaceScreens.addAll(LauncherModel.loadWorkspaceScreensDb(context));

 

 public static ArrayList<Long> loadWorkspaceScreensDb(Context context) {
        final ContentResolver contentResolver = context.getContentResolver();
        final Uri screensUri = LauncherSettings.WorkspaceScreens.CONTENT_URI;

        // Get screens ordered by rank.
        return LauncherDbUtils.getScreenIdsFromCursor(contentResolver.query(
                screensUri, null, null, null, LauncherSettings.WorkspaceScreens.SCREEN_RANK));
    }

 

加载数据库配置信息

 final LoaderCursor c = new LoaderCursor(contentResolver.query(
                    LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);

            try {
                final int appWidgetIdIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.APPWIDGET_ID);
                final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.APPWIDGET_PROVIDER);
                final int spanXIndex = c.getColumnIndexOrThrow
                        (LauncherSettings.Favorites.SPANX);
                final int spanYIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.SPANY);
                final int rankIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.RANK);
                final int optionsIndex = c.getColumnIndexOrThrow(
                        LauncherSettings.Favorites.OPTIONS);

                final LongSparseArray<UserHandle> allUsers = c.allUsers;

根据itemType配置不同类型的数据,ITEM_TYPE_SHORTCUT,ITEM_TYPE_APPLICATION,ITEM_TYPE_DEEP_SHORTCUT,ITEM_TYPE_FOLDER,ITEM_TYPE_APPWIDGET,ITEM_TYPE_CUSTOM_APPWIDGET

3.loadAllApps(),加载All App页的所有包信息

            final List<LauncherActivityInfo> apps = mLauncherApps.getActivityList(null, user);

4.loadDeepShortcuts();加载deep short

5.mResults.bindWorkspace(),数据和UI绑定,通过callback回调,callback即是Launcher,Launcher用 了MVC模式,Launcher是

View,LauncherModel是Model,方法里面需注意点是

callbacks.bindScreens(orderedScreenIds); // 展示屏幕数目

bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor); // 绑定图标,快捷列表,Widget的UI

其里面最重要的方法是bindItems,绑定图标,快捷列表,Widget的逻辑放在这里,bindItems里workspace.addInScreenFromBind(view, item)是把各个View加载到每一屏中

public void bindItems(final List<ItemInfo> items, final boolean forceAnimateIcons) {
        Runnable r = new Runnable() {
            public void run() {
                bindItems(items, forceAnimateIcons);
            }
        };
        if (waitUntilResume(r)) {
            return;
        }

        // Get the list of added items and intersect them with the set of items here
        final AnimatorSet anim = LauncherAnimUtils.createAnimatorSet();
        final Collection<Animator> bounceAnims = new ArrayList<>();
        final boolean animateIcons = forceAnimateIcons && canRunNewAppsAnimation();
        Workspace workspace = mWorkspace;
        long newItemsScreenId = -1;
        int end = items.size();
        for (int i = 0; i < end; i++) {
            final ItemInfo item = items.get(i);

            // Short circuit if we are loading dock items for a configuration which has no dock
            if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT &&
                    mHotseat == null) {
                continue;
            }

            final View view;
            switch (item.itemType) {
                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: {
                    ShortcutInfo info = (ShortcutInfo) item;
                    view = createShortcut(info);
                    break;
                }
                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: {
                    view = FolderIcon.fromXml(R.layout.folder_icon, this,
                            (ViewGroup) workspace.getChildAt(workspace.getCurrentPage()),
                            (FolderInfo) item);
                    break;
                }
                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: {
                    view = inflateAppWidget((LauncherAppWidgetInfo) item);
                    if (view == null) {
                        continue;
                    }
                    break;
                }
                default:
                    throw new RuntimeException("Invalid Item Type");
            }

             /*
             * Remove colliding items.
             */
            if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
                CellLayout cl = mWorkspace.getScreenWithId(item.screenId);
                if (cl != null && cl.isOccupied(item.cellX, item.cellY)) {
                    View v = cl.getChildAt(item.cellX, item.cellY);
                    Object tag = v.getTag();
                    String desc = "Collision while binding workspace item: " + item
                            + ". Collides with " + tag;
                    if (FeatureFlags.IS_DOGFOOD_BUILD) {
                        throw (new RuntimeException(desc));
                    } else {
                        Log.d(TAG, desc);
                        getModelWriter().deleteItemFromDatabase(item);
                        continue;
                    }
                }
            }
            workspace.addInScreenFromBind(view, item);
            if (animateIcons) {
                // Animate all the applications up now
                view.setAlpha(0f);
                view.setScaleX(0f);
                view.setScaleY(0f);
                bounceAnims.add(createNewAppBounceAnimation(view, i));
                newItemsScreenId = item.screenId;
            }
        }

        if (animateIcons) {
            // Animate to the correct page
            if (newItemsScreenId > -1) {
                long currentScreenId = mWorkspace.getScreenIdForPageIndex(mWorkspace.getNextPage());
                final int newScreenIndex = mWorkspace.getPageIndexForScreenId(newItemsScreenId);
                final Runnable startBounceAnimRunnable = new Runnable() {
                    public void run() {
                        anim.playTogether(bounceAnims);
                        anim.start();
                    }
                };
                if (newItemsScreenId != currentScreenId) {
                    // We post the animation slightly delayed to prevent slowdowns
                    // when we are loading right after we return to launcher.
                    mWorkspace.postDelayed(new Runnable() {
                        public void run() {
                            if (mWorkspace != null) {
                                mWorkspace.snapToPage(newScreenIndex);
                                mWorkspace.postDelayed(startBounceAnimRunnable,
                                        NEW_APPS_ANIMATION_DELAY);
                            }
                        }
                    }, NEW_APPS_PAGE_MOVE_DELAY);
                } else {
                    mWorkspace.postDelayed(startBounceAnimRunnable, NEW_APPS_ANIMATION_DELAY);
                }
            }
        }
        workspace.requestLayout();
    }

6.mResults.bindAllApps(); 绑定All App的UI

7.mResults.bindDeepShortcuts();绑定 Deep Shortcut UI

8.mResults.bindWidgets(); 绑定 Widget UI

至此,Launcher中的数据和UI绑定完成!!!

 

Launcher3 是 Android 系统中用于管理主屏幕的应用程序,其源码主要位于 Android 开源项目(AOSP)中。它负责实现主屏幕界面、应用程序抽屉、小部件管理、手势操作等功能。以下是对其源码结构和关键模块的深入解析: ### 核心架构与目录结构 Launcher3 的源码主要位于 `packages/apps/Launcher3` 目录下,其核心结构包括以下部分: - **`src/com/android/launcher3/`**:主包路径,包含 Launcher3 的主要类和功能实现。 - **`res/`**:资源文件目录,包括布局文件、drawable、样式定义等。 - **`AndroidManifest.xml`**:应用清单文件,声明了 Launcher3 的组件和权限需求。 ### 关键类与功能模块 #### `Launcher` 类 这是 Launcher3 的主 Activity,负责初始化整个用户界面并处理主屏幕的生命周期。它继承自 `Activity`,并在 `onCreate()` 中加载主布局文件 `R.layout.launcher`。`Launcher` 还管理着桌面的拖放操作、应用图标点击事件以及状态栏和导航栏的交互逻辑[^1]。 ```java public class Launcher extends Activity implements View.OnTouchListener, ... ``` #### `Workspace` 类 `Workspace` 是 `CellLayout` 的子类,用于管理主屏幕上的页面和图标布局。它支持多页切换、滑动手势以及图标拖拽功能。每个页面是一个 `CellLayout` 实例,可以放置应用图标和小部件[^1]。 #### `AppDrawer` 类 `AppDrawer` 实现了 Android 5.0 及以上版本中的垂直应用抽屉。它使用 `RecyclerView` 来高效地显示大量的应用图标,并支持搜索和分类功能。`AppDrawer` 的 UI 由 `R.layout.apps_customize_pane_content` 定义,并通过 `AppsCustomizePagedView` 进行内容填充和滚动处理[^1]。 #### `Folder` 类 该类实现了文件夹功能,允许用户将多个应用图标归类到一个文件夹中。文件夹打开后会显示一个透明的背景和内部图标列表,支持拖拽添加和移除操作。`Folder` 的动画和布局由 `FolderIcon` 和 `FolderInfo` 类协同管理[^1]。 #### `DragController` 类 `DragController` 是整个 Launcher 中拖放操作的核心控制器。它处理从图标拖动、小部件拖放,到页面切换等交互行为。它通过监听 `OnDragListener` 接口来协调拖动过程中的 UI 反馈和数据更新[^1]。 #### `Model` 类 `Model` 负责管理 Launcher 的数据模型,包括加载应用信息、小部件状态、桌面布局等。它通过 `LoaderTask` 异步加载数据,并在数据更新后通知 UI 进行刷新。`Model` 与 `Workspace` 和 `AppDrawer` 保持同步,确保数据一致性[^1]。 ### 性能优化与内存管理 Launcher3 在性能优化方面做了大量工作,包括: - 使用 `RecyclerView` 和 `ViewHolder` 模式优化列表滚动性能。 - 图标和小部件的懒加载机制,避免一次性加载过多资源。 - 通过 `Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND)` 设置后台线程优先级,减少对主线程的影响。 - 对壁纸和动画进行资源压缩和缓存处理,提升渲染效率。 ### 自定义与扩展 由于 Launcher3 是开源的,开发者可以根据需求进行定制化开发,例如: - 修改主题和 UI 样式以适配品牌设计。 - 添加新的手势操作或快捷入口。 - 集成第三方插件或 SDK,如天气插件、快捷设置面板。 - 支持多用户模式或企业级管理功能。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值