Launcher 3 Workspaceロードプロセスの概要(Aosp Pバージョンベース)

26416 ワード

Launcher 3 Workspaceロードプロセスの概要
前言
Aosp launcher 3のソースコードはAndroidの大バージョンの反復に従って、絶えず更新して、大体機能の変化は大きくありませんが、絶えずコードを再構築して最適化して、コードのパッケージと拡張性はますます良くなって、launcher開発者として、急いで勉強して、良い実現をタイムリーに実際の開発に運用しなければなりません.本稿では,最新のAndroid Pバージョンのlauncher 3に基づいて,この流れを分析する.
いくつかの重要なクラス
これらのクラスはlauncherの主要なフレームワーククラスと言えるが、それらを熟知していると、launcherについて基本的に大体理解している:-Launcher:launcherメインインタフェースは、Activityであり、電源を入れた後、システムによって自動的に起動される.-LauncherAppState:LauncherModel-LauncherModelを含む基本的なすべての重要なオブジェクトを初期化した単一のクラスです.これはworkspaceデータロードの最も核心的なクラスであり、workspaceデータのロードと更新を担当し、すべてのキャッシュを維持し、packageの様々な変化を監視します.-LoaderResults:このクラスはLauncher 3 Pバージョンで追加され、前にバインドされたviewの操作をカプセル化しました.-LauncherProvider:workspace関連のデータベース操作をカプセル化し、外部からこのprovider呼び出し操作-BgDataModel:このクラスはLauncher 3 Oバージョンで新規に追加され、memoryキャッシュをカプセル化し、すべてのapp、shortcut、folder、widget、screenなどのデータをキャッシュした-Workspace:これがデスクトップ上のグリッド表示アプリケーションアイコンのViewであり、左右にスライドし、複数ページを表示することができる.
ソースコードの浅い分析
1. Launcher.JAva onCreateメソッドの開始
    private LauncherModel mModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ......
        LauncherAppState app = LauncherAppState.getInstance(this);

        mModel = app.setLauncher(this);

        mModel.startLoader(currentScreen);//      
        ......
    }

onCreateでLauncherAppStateを呼び出す.getInstanceではLauncherAppStateの単一例が作成され、LauncherAppStateコンストラクション関数では多くの初期化操作が行われています.そのうちの1つはLauncherModelをインスタンス化し、その参照を保持します.次にmModelオブジェクト呼び出しstartLoaderを使用してworkspaceおよびall appsデータのロードを開始し、本稿では主にworkspaceのデータロードについて説明します.
LauncherAppStateコンストラクション関数は次のとおりです.
    private final LauncherModel mModel;

    private LauncherAppState(Context context) {
        ......
        mModel = new LauncherModel(this, mIconCache, AppFilter.newInstance(mContext));
    }

LauncherModelはここで一度だけインスタンス化されており、実際には単一のインスタンスの存在でもある.
2.LauncherModelでstartLoaderメソッドが吊り上げられる
    public boolean startLoader(int synchronousBindPage) {
        ......
        LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
                mBgAllAppsList, synchronousBindPage, mCallbacks);
        startLoaderForResults(loaderResults);
    }

    public void startLoaderForResults(LoaderResults results) {
        synchronized (mLock) {
            stopLoader();
            mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, sBgDataModel, results);
            runOnWorkerThread(mLoaderTask);//        
        }
    }

LoaderTaskはRunnableインタフェースを実現し、データのロード操作をカプセル化し、タスクとしてスレッドキューに追加される.
3.LoaderTask実行runメソッド
runメソッドは以下のように実現される.
    public void run() {
        ......

        try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
            TraceHelper.partitionSection(TAG, "step 1.1: loading workspace");
            loadWorkspace();//  workspace

            verifyNotStopped();
            TraceHelper.partitionSection(TAG, "step 1.2: bind workspace workspace");
            mResults.bindWorkspace();//  workspace

            // Notify the installer packages of packages with active installs on the first screen.
            TraceHelper.partitionSection(TAG, "step 1.3: send first screen broadcast");
            sendFirstScreenActiveInstallsBroadcast();

            // Take a break
            TraceHelper.partitionSection(TAG, "step 1 completed, wait for idle");
            waitForIdle();
            verifyNotStopped();

            // second step
            TraceHelper.partitionSection(TAG, "step 2.1: loading all apps");
            loadAllApps();//  all apps

            TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps");
            verifyNotStopped();
            mResults.bindAllApps();//  app apps     

            verifyNotStopped();
            TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache");
            updateIconCache();

            // Take a break
            TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle");
            waitForIdle();
            verifyNotStopped();

            // third step
            TraceHelper.partitionSection(TAG, "step 3.1: loading deep shortcuts");
            loadDeepShortcuts();

            verifyNotStopped();
            TraceHelper.partitionSection(TAG, "step 3.2: bind deep shortcuts");
            mResults.bindDeepShortcuts();

            // Take a break
            TraceHelper.partitionSection(TAG, "step 3 completed, wait for idle");
            waitForIdle();
            verifyNotStopped();

            // fourth step
            TraceHelper.partitionSection(TAG, "step 4.1: loading widgets");
            mBgDataModel.widgetsModel.update(mApp, null);

            verifyNotStopped();
            TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets");
            mResults.bindWidgets();

            transaction.commit();
        } catch (CancellationException e) {
            // Loader stopped, ignore
            TraceHelper.partitionSection(TAG, "Cancelled");
        }
        ......
    }

runメソッドでは、すべてのデータがロードされるまでloadWorkspace、bindWorkspace、loadAllApps、bindAllAppsの順になります.
4. loadWorkspace
ここでは主にWorkspaceのロードプロセスを分析します.
    private void loadWorkspace() {
        ......
        Log.d(TAG, "loadWorkspace: loading default favorites");
        LauncherSettings.Settings.call(contentResolver,
                LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES);

        synchronized (mBgDataModel) {
            mBgDataModel.clear();
            ......
            //   LoaderCursor Cursor      
            final LoaderCursor c = new LoaderCursor(contentResolver.query(
                    LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
        }
    }

loadWorkspaceメソッドは主に2つのことをしました.1つはデフォルト構成xmlファイルのitemsをロードし、デフォルトデータをデータベースに格納することです.もう1つはデータベースのデータをキャッシュにロードすることです.もちろん、さまざまな有効性チェックも行います.ここでは具体的な分析はしません.
ここでデフォルトの構成をデータベースにロードすることは、LauncherProviderのcallメソッドを呼び出して実現されます.コードは次のとおりです.
    @Override
    public Bundle call(String method, final String arg, final Bundle extras) {
        ......
        switch (method) {
            ......

            case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
                loadDefaultFavoritesIfNecessary();
                return null;
            }

        }
    }

LauncherProvideのcallメソッドは、loadDefaultFavoritesIfNecessaryを呼び出してデフォルトの構成をロードします.コードは次のとおりです.
    /**
     * Loads the default workspace based on the following priority scheme:
     *   1) From the app restrictions
     *   2) From a package provided by play store
     *   3) From a partner configuration APK, already in the system image
     *   4) The default configuration for the particular device
     */
    synchronized private void loadDefaultFavoritesIfNecessary() {
        SharedPreferences sp = Utilities.getPrefs(getContext());

        if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
            Log.d(TAG, "loading default workspace");

            AppWidgetHost widgetHost = mOpenHelper.newLauncherWidgetHost();
            AutoInstallsLayout loader = createWorkspaceLoaderFromAppRestriction(widgetHost);
            if (loader == null) {
                loader = AutoInstallsLayout.get(getContext(),widgetHost, mOpenHelper);
            }
            if (loader == null) {
                final Partner partner = Partner.get(getContext().getPackageManager());
                if (partner != null && partner.hasDefaultLayout()) {
                    final Resources partnerRes = partner.getResources();
                    int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                            "xml", partner.getPackageName());
                    if (workspaceResId != 0) {
                        loader = new DefaultLayoutParser(getContext(), widgetHost,
                                mOpenHelper, partnerRes, workspaceResId);
                    }
                }
            }

            final boolean usingExternallyProvidedLayout = loader != null;
            if (loader == null) {
                loader = getDefaultLayoutParser(widgetHost);
            }

            // There might be some partially restored DB items, due to buggy restore logic in
            // previous versions of launcher.
            mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
            // Populate favorites table with initial favorites
            if ((mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader) <= 0)
                    && usingExternallyProvidedLayout) {
                // Unable to load external layout. Cleanup and load the internal layout.
                mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
                mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(),
                        getDefaultLayoutParser(widgetHost));
            }
            clearFlagEmptyDbCreated();
        }
    }

上記の方法の注記のように、ここでは優先順位順に4つの構成が存在するかどうかを確認します.デフォルトではxmlファイルを構成し、カスタムROMであれば、システム内に構成し、apkの形式、または固定パスの形式で構成することができます.サードパーティLauncherの場合はapkに入れる必要があります.
5.LoaderResultsパッケージバインド
Android PにはLoaderResultsクラスが追加され、次のbindWorkspaceのようなインタフェースにバインドされたデータの操作がカプセル化され、コード構造がより合理的で明確になります.
    public void bindWorkspace() {
        Runnable r;
        ......

        // Tell the workspace that we're about to start binding items
        r = new Runnable() {
            public void run() {
                Callbacks callbacks = mCallbacks.get();
                if (callbacks != null) {
                    callbacks.clearPendingBinds();
                    callbacks.startBinding();
                }
            }
        };
        mUiExecutor.execute(r);

        // Bind workspace screens
        mUiExecutor.execute(new Runnable() {
            @Override
            public void run() {
                Callbacks callbacks = mCallbacks.get();
                if (callbacks != null) {
                    callbacks.bindScreens(orderedScreenIds);
                }
            }
        });

        Executor mainExecutor = mUiExecutor;
        // Load items on the current page.
        bindWorkspaceItems(currentWorkspaceItems, currentAppWidgets, mainExecutor);

        // In case of validFirstPage, only bind the first screen, and defer binding the
        // remaining screens after first onDraw (and an optional the fade animation whichever
        // happens later).
        // This ensures that the first screen is immediately visible (eg. during rotation)
        // In case of !validFirstPage, bind all pages one after other.
        final Executor deferredExecutor =
                validFirstPage ? new ViewOnDrawExecutor() : mainExecutor;

        mainExecutor.execute(new Runnable() {
            @Override
            public void run() {
                Callbacks callbacks = mCallbacks.get();
                if (callbacks != null) {
                    callbacks.finishFirstPageBind(
                            validFirstPage ? (ViewOnDrawExecutor) deferredExecutor : null);
                }
            }
        });

        bindWorkspaceItems(otherWorkspaceItems, otherAppWidgets, deferredExecutor);

        // Tell the workspace that we're done binding items
        r = new Runnable() {
            public void run() {
                Callbacks callbacks = mCallbacks.get();
                if (callbacks != null) {
                    callbacks.finishBindingItems();
                }
            }
        };
        deferredExecutor.execute(r);

    }

6. LauncherModel.Callbacksインタフェース
LauncherはLauncherModelを実現した.Callbacksインタフェース、コールバックインタフェースの更新を表示します.コードは以下の通りです.
    public interface Callbacks {

        ......
        //               ,    
        public void bindItems(List shortcuts, boolean forceAnimateIcons);//  items
        public void bindScreens(ArrayList orderedScreenIds);//  screen
        public void bindAllApplications(ArrayList apps);//      

    }

Launcherの実装では、バインド画面のコードは次のとおりです.
    @Override
    public void bindScreens(ArrayList orderedScreenIds) {
        // Make sure the first screen is always at the start.
        if (FeatureFlags.QSB_ON_FIRST_SCREEN &&
                orderedScreenIds.indexOf(Workspace.FIRST_SCREEN_ID) != 0) {
            orderedScreenIds.remove(Workspace.FIRST_SCREEN_ID);
            orderedScreenIds.add(0, Workspace.FIRST_SCREEN_ID);
            LauncherModel.updateWorkspaceScreenOrder(this, orderedScreenIds);
        } else if (!FeatureFlags.QSB_ON_FIRST_SCREEN && orderedScreenIds.isEmpty()) {
            // If there are no screens, we need to have an empty screen
            mWorkspace.addExtraEmptyScreen();
        }
        bindAddScreens(orderedScreenIds);

        // After we have added all the screens, if the wallpaper was locked to the default state,
        // then notify to indicate that it can be released and a proper wallpaper offset can be
        // computed before the next layout
        mWorkspace.unlockWallpaperFromDefaultPageOnNextLayout();
    }

BindAddScreensは実際に計算した画面の個数に基づいて、画面のViewを作成し、launcherの各画面に対応するViewはCellLayoutです.
Itemをバインドするコードは次のとおりです.
    @Override
    public void bindItems(final List items, final boolean forceAnimateIcons) {
        ......
        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:
                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_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);
        }

        .......
        workspace.requestLayout();
    }

bindItemsでは、タイプに対するViewがループして作成され、デスクトップにリフレッシュ表示されます.ここで作成されたViewは、最終的に見られるアプリケーションアイコン、フォルダ、ウィジェットです.
まとめ
以上、今回の分析のすべての内容は、android Pバージョンlauncher 3 workspaceのロードとバインドプロセスを主に議論し、深さ解析とは言えません.指摘を歓迎します.その後、いくつかの重要な詳細の実装、および開発中によく関連する需要修正点に深く入り込みます.