墨香帶你學(xué)Launcher之(二)-數(shù)據(jù)加載流程

上一篇墨香帶你學(xué)Launcher之-概述,我已經(jīng)介紹了Launcher的布局以及相關(guān)的界面跳轉(zhuǎn),今天我們繼續(xù)學(xué)習(xí),按照計劃,我們開始學(xué)習(xí)Launcher啟動之?dāng)?shù)據(jù)加載,主要是圖標(biāo)章办、Widget和文件夾的加載.

1.基礎(chǔ)知識


在介紹加載之前我先介紹一點需要用的相關(guān)知識:

  • Launcher:繼承Activity,是桌面的主界面,因此可知,桌面其實就是一個activity,只是和平常的應(yīng)用不同,他用來顯示圖標(biāo)哮内、Widget和文件夾等;

  • LauncherModel:繼承BroadcastReceiver,由此可知他是一個廣播接收器,用來接收廣播,另外,LauncherModel還主要加載數(shù)據(jù);

  • LauncherProvider:繼承ContentProvider,主要是處理數(shù)據(jù)庫操作;

  • LauncherAppState:單例模式的全局管理類,主要是初始化一些對象,注冊廣播等.

  • Compat:兼容包,帶有這個后綴的都是做兼容處理的類.

2.默認(rèn)圖標(biāo)配置


我們在買回新的手機(jī)或者第一次安裝新的Launcher后,會發(fā)現(xiàn)手機(jī)的第一頁已經(jīng)有了一些應(yīng)用的圖標(biāo)和時鐘或者天氣插件,那么這個是怎么實現(xiàn)的呢?其實,手機(jī)在出廠的時候或者Launcher發(fā)到市場的時候已經(jīng)默認(rèn)排布了一些應(yīng)用,在第一啟動時就會加載并且判斷手機(jī)中是否有這些圖標(biāo),如果有則顯示到固定位置,這個位置其實是已經(jīng)寫好的.下面我們看看這個位置到底在哪里寫好的.

下面是Launcher的資源文件,我們看這個比我們平時的多一個xml文件夾,里面有很多xml文件,那么這些是做什么用的,我來解釋一下,有三個文件,分別為default_workspace_4x4.xml,default_workspace_5x5.xml和default_workspace_5x6.xml,這三個文件就是我們默認(rèn)的布局文件,后面的跟著的4x4、5x5和5x6表示桌面圖標(biāo)的列數(shù)和行數(shù),也就是4行4列,5行5列,5行6列,這個怎么用我們后面再說.

launcher01.png

我們先看一下default_workspace_4x4.xml這個文件中的代碼:

launcher02.png

第20行是一個include的文件,在xml文件夾中的名字dw_phone_hotseat文件,我們后面在看,接著看上圖的下面的代碼,下面是三個resolve文件,里面包含一些信息,screen表示第幾屏,x表示橫向的位置,y表示縱向的位置,那么這個位置怎定的呢,我來畫一個4x4的圖你就明白了:

launcher03.png

先看上半部分,就是我們說的4x4部分,沒一格表示一格圖標(biāo),在我們繪制圖標(biāo)的時候已經(jīng)分好了格,每格的大小,只要知道知道他的位置即可繪制圖標(biāo)到相應(yīng)的位置,那么代碼中的x,y就是這個圖標(biāo)的位置.上面resolve中還有兩個favorite,在第一個中最后面有個"APP_",這個我們一看就知道是應(yīng)用的屬性,其實這就表示我們配置了那個app在這個位置,我們再看一下上面介紹的hotseat那個xml文件:

launcher04.png

這個圖我只截圖了一部分,想看全部的可以下載我github上的源碼查看,其實只是重復(fù),我介紹一個就知道了,上一章我介紹過hotseat這個概念,其實就是我們手機(jī)最下面的那個四個或者五個最常用的app圖標(biāo),這個就是在這里面配置的,我以第一個為例來介紹這個Hotseat配置,我們先看第21行,這個比我們前面介紹的多個屬性就是這個container,之前的是沒有的,這個就表示容器,-101就是hotseat,也就是這個圖標(biāo)放置到Hotseat中,Hotseat只有一行,所以只有x在變,而y不變.

到此基本的桌面默認(rèn)圖標(biāo)顯示配置就介紹完了,如果你需要默認(rèn)顯示哪個只需要配置這個文件即可.

3.Launcher啟動過程


下面我們開始介紹Launcher的啟動過程.分析Launcher的啟動過程要從源碼開始分析.在源碼中是通過startHomeActivityLocked這個方法調(diào)用的啟動Launcher,我們先看一下哪里開始調(diào)用的這個函數(shù),

launcher05.png

從上面的調(diào)用圖可知有三個地方調(diào)用了啟動Launcher的方法,這三個方法中首次啟動應(yīng)該是中間的那個systemReady方法,系統(tǒng)準(zhǔn)備過程中調(diào)用啟動Launcher,我們看一下systemReady方法是哪里調(diào)用的來驗證一下:

launcher06.png

從上代碼靜態(tài)分析圖來看最開始是在System.main方法開始的,正好這個方法就是啟動系統(tǒng)的一個入口,也就是在這個過程中啟動了Launcher,找到調(diào)用的地方后,我們來看一下startHomeActivityLocked是怎么啟動Launcher的,首先看一下源碼:

launcher07.png

我們看上面的3473行,獲取Intent,再看3451行,如果不為空,則啟動HomeActivity,我們看一下這個Intent是什么的Intent:

launcher08.png

上面的3424行,有個Intent.CATEGORY_HOME,我們在Intent中找到這個屬性的代碼:

launcher09.png

這個就是我們上一章講的設(shè)置app為launcher的屬性值.

通過上面這些分析可以看到系統(tǒng)是怎么啟動launcher的.下面我們看是介紹Launcher內(nèi)部是如何啟動的.

4.Launcher初始化


我們知道App的啟動是從Application開始的,但是我們最新的Launcher3中,谷歌工程師把這個類移除,再次之前的版本都是有這個類的,我在這提一下就是因為開發(fā)以前l(fā)auncher的時候遇到一個問題,就是在Application和ContentProvider同時存在時,ContentProvider的onCreate方法要比Application的onCreate方法先啟動,下面我們通過源碼分析來驗證這個問題.

啟動Application是從ActivityManagerService中的attachApplication方法開始的,代碼:


 public final void attachApplication(IApplicationThread thread) {
        synchronized (this) {
            int callingPid = Binder.getCallingPid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid);
            Binder.restoreCallingIdentity(origId);
        }
    }

接著調(diào)用attachApplicationLocked方法,代碼如下:


   private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
        app.makeActive(thread, mProcessStats);
        app.curAdj = app.setAdj = -100;
        app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
        app.forcingToForeground = null;
        updateProcessForegroundLocked(app, false, false);
        app.hasShownUi = false;
        app.debugging = false;
        app.cached = false;
        app.killedByAm = false;

        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);

        boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
        List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

        if (!normalMode) {
            Slog.i(TAG, "Launching preboot mode app: " + app);
        }

        if (DEBUG_ALL) Slog.v(
            TAG, "New app record " + app
            + " thread=" + thread.asBinder() + " pid=" + pid);
        try {
           ...

            ProfilerInfo profilerInfo = profileFile == null ? null
                    : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
            thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                    profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                    app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(mConfiguration), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked());
            updateLruProcessLocked(app, false, null);
            app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
        } catch (Exception e) {

            app.resetPackageList(mProcessStats);
            app.unlinkDeathRecipient();
            startProcessLocked(app, "bind fail", processName);
            return false;
        }

        ...

        return true;
    }

上面代碼中主要有一個thread.bindApplication方法來綁定application,接著看bindApplication代碼:

 public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
                Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
                Bundle coreSettings) {

            ...

            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableOpenGlTrace = enableOpenGlTrace;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            sendMessage(H.BIND_APPLICATION, data);
        }

準(zhǔn)備data數(shù)據(jù)偏序,然后發(fā)送消息到Handler记舆,Handler中處理消息的代碼如下:


public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {


                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;

            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }

根據(jù)消息類型BIND_APPLICATION來判斷調(diào)用handleBindApplication方法院尔,

 private void handleBindApplication(AppBindData data) {
 
        ...
        
        try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            Application app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;

            // don't bring up providers in restricted mode; they may depend on the
            // app's custom Application class
            if (!data.restrictedBackupMode) {
                List<ProviderInfo> providers = data.providers;
                if (providers != null) {
                
                    //安裝ContentProviders
                    installContentProviders(app, providers);
                    
                    // For process that contains content providers, we want to
                    // ensure that the JIT is enabled "at some point".
                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                }
            }

            ...

            try {
                //啟動Application的onCreate方法
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                        + ": " + e.toString(), e);
                }
            }
        } finally {
            StrictMode.setThreadPolicy(savedPolicy);
        }
    }

在上面函數(shù)中調(diào)用installContentProviders方法來安裝ContentProvider御板,代碼如下:

 private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<IActivityManager.ContentProviderHolder> results =
            new ArrayList<IActivityManager.ContentProviderHolder>();

        for (ProviderInfo cpi : providers) {
            if (DEBUG_PROVIDER) {
                StringBuilder buf = new StringBuilder(128);
                buf.append("Pub ");
                buf.append(cpi.authority);
                buf.append(": ");
                buf.append(cpi.name);
                Log.i(TAG, buf.toString());
            }
            IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,
           
            ...
            
        }

      ...
      
    }

調(diào)用installProvider返回一個IActivityManager.ContentProviderHolder對象说榆,我們看這個方法里面做了哪些處理虚吟,

private IActivityManager.ContentProviderHolder installProvider(Context context,
            IActivityManager.ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;         if (holder == null || holder.provider == null) {
            if (DEBUG_PROVIDER || noisy) {
                Slog.d(TAG, "Loading provider " + info.authority + ": "
                        + info.name);
            }
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            if (context.getPackageName().equals(ai.packageName)) {
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
                c = mInitialApplication;
            } else {
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            if (c == null) {
                Slog.w(TAG, "Unable to get context for package " +
                      ai.packageName +
                      " while loading content provider " +
                      info.name);
                return null;
            }
            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                //獲取ContentProvider
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                    Slog.e(TAG, "Failed to instantiate class " +
                          info.name + " from sourceDir " +
                          info.applicationInfo.sourceDir);
                    return null;
                }
                if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);
                    
                // XXX Need to create the correct context for this provider.
                localProvider.attachInfo(c, info);
                
            } catch (java.lang.Exception e) {
                if (!mInstrumentation.onException(null, e)) {
                    throw new RuntimeException(
                            "Unable to get provider " + info.name
                            + ": " + e.toString(), e);
                }
                return null;
            }
        } else {
            provider = holder.provider;
            if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                    + info.name);
        }    
        
        ...

        return retHolder;
    }

上面代碼中有個關(guān)鍵方法:localProvider.attachInfo(c, info),這個方法就是添加Provider的签财,代碼如下:

private void attachInfo(Context context, ProviderInfo info, boolean testing) {

        /*
         * Only allow it to be set once, so after the content service gives
         * this to us clients can't change it.
         */
        if (mContext == null) {
            mContext = context;
            if (context != null) {
                mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
                        Context.APP_OPS_SERVICE);
            }
            mMyUid = Process.myUid();
            if (info != null) {
                setReadPermission(info.readPermission);
                setWritePermission(info.writePermission);
                setPathPermissions(info.pathPermissions);
                mExported = info.exported;
            }
            ContentProvider.this.onCreate();
        }
    }

我們看到在最后調(diào)用了ContentProvider.this.onCreate()這個方法串慰,然后會返回到handleBindApplication方法中執(zhí)行mInstrumentation.callApplicationOnCreate(app)方法,代碼如下:


 public void callApplicationOnCreate(Application app) {
      app.onCreate();
 }

因此我們看到ContentProvider的onCreate方法比Application的onCreate方法調(diào)用早荠卷。這里只是簡單介紹詳細(xì)過程去看源碼模庐。

我現(xiàn)在講解的是基于最新的Launcher3代碼,因此我們這個Launcher中沒有Application油宜,所以程序啟動最開始的是ContentProvider的onCreate方法掂碱,代碼如下:

public boolean onCreate() {
        final Context context = getContext();
        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        mOpenHelper = new DatabaseHelper(context);
        StrictMode.setThreadPolicy(oldPolicy);
        LauncherAppState.setLauncherProvider(this);
        return true;
}

代碼中處理的事情不多怜姿,主要是啟動嚴(yán)苛模式和創(chuàng)建數(shù)據(jù)庫,關(guān)于嚴(yán)苛模式的具體信息看官方文檔或者博客疼燥,都有很詳細(xì)的講解沧卢,然后將ContentProvider放置到整個Launcher的管理類LauncherAppState中,以方便獲取醉者。

接下來就是啟動Launcher,我么看一下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 (mLauncherCallbacks != null) {
            mLauncherCallbacks.preOnCreate();
        }

        super.onCreate(savedInstanceState);

        LauncherAppState.setApplicationContext(getApplicationContext());
        LauncherAppState app = LauncherAppState.getInstance();

        // Load configuration-specific DeviceProfile
        mDeviceProfile = getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE ?
                app.getInvariantDeviceProfile().landscapeProfile
                : app.getInvariantDeviceProfile().portraitProfile;

        mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
                Context.MODE_PRIVATE);
        mIsSafeModeEnabled = getPackageManager().isSafeMode();
        mModel = app.setLauncher(this);
        mIconCache = app.getIconCache();

        mDragController = new DragController(this);
        mInflater = getLayoutInflater();
        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);

        mStats = new Stats(this);

        mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);

        mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
        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;

        if (PROFILE_STARTUP) {
            android.os.Debug.startMethodTracing(
                    Environment.getExternalStorageDirectory() + "/launcher");
        }

        setContentView(R.layout.launcher);

        registerHomeKey();
        setupViews();

        //動態(tài)設(shè)置各布局的參數(shù)
        mDeviceProfile.layout(this);

        mSavedState = savedInstanceState;
        restoreState(mSavedState);

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

        if (!mRestoring) {
            if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
                // If the user leaves launcher, then we should just load items asynchronously when
                // they return.
                mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
            } else {
                // We only load the page synchronously if the user rotates (or triggers a
                // configuration change) while launcher is in the foreground
                mModel.startLoader(mWorkspace.getRestorePage());
            }
        }

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

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

        mRotationEnabled = Utilities.isRotationAllowedForDevice(getApplicationContext());
        // 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(), false);
        }

        // 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();

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onCreate(savedInstanceState);
            if (mLauncherCallbacks.hasLauncherOverlay()) {
                ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
                mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
                mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
                        mLauncherOverlayContainer, mLauncherOverlayCallbacks);
                mWorkspace.setLauncherOverlay(mLauncherOverlay);
            }
        }

        if (shouldShowIntroScreen()) {
            showIntroScreen();
        } else {
            showFirstRunActivity();
            showFirstRunClings();
        }
    }

代碼比較多我們看一下執(zhí)行過程圖:

launcher11.png

首先是啟動嚴(yán)苛模式但狭,準(zhǔn)備回調(diào)接口,初始化LauncherAppState:

    private LauncherAppState() {
        if (sContext == null) {
            throw new IllegalStateException("LauncherAppState inited before app context set");
        }

        if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) {
            MemoryTracker.startTrackingMe(sContext, "L");
        }
        
        //初始化固定的設(shè)備配置
        mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
        
        //初始化圖標(biāo)管理工具
        mIconCache = new IconCache(sContext, mInvariantDeviceProfile);
        
        //初始化Widget加載混存工具
        mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);

        mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
        mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
        
        //初始化廣播
        mModel = new LauncherModel(this, mIconCache, mAppFilter);

        LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);

        // Register intent receivers
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
        // For handling managed profiles
        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);

        //注冊廣播
        sContext.registerReceiver(mModel, filter);
        UserManagerCompat.getInstance(sContext).enableAndResetCache();
    }

然后初始化手機(jī)固件信息對象DeviceProfile撬即,初始化拖拽管理器DragController立磁,然后初始化小部件管理器,加載布局剥槐,初始化桌面各個控件唱歧,并且設(shè)置各個控件的位置:

public void layout(Launcher launcher) {
        FrameLayout.LayoutParams lp;
        boolean hasVerticalBarLayout = isVerticalBarLayout();
        final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());

        // Layout the search bar space
        View searchBar = launcher.getSearchDropTargetBar();
        lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
        if (hasVerticalBarLayout) {
            // Vertical search bar space -- The search bar is fixed in the layout to be on the left
            //                              of the screen regardless of RTL
            lp.gravity = Gravity.LEFT;
            lp.width = searchBarSpaceHeightPx;

            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
            targets.setOrientation(LinearLayout.VERTICAL);
            FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
            targetsLp.gravity = Gravity.TOP;
            targetsLp.height = LayoutParams.WRAP_CONTENT;

        } else {
            // Horizontal search bar space
            lp.gravity = Gravity.TOP;
            lp.height = searchBarSpaceHeightPx;

            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
            targets.getLayoutParams().width = searchBarSpaceWidthPx;
        }
        searchBar.setLayoutParams(lp);

        //其他省略
        ...

    }

這里就是動態(tài)設(shè)置桌面各個控件的位置及寬高等屬性。當(dāng)所有信息初始化完成后粒竖,就開始調(diào)用mModel.startLoader方法來加載應(yīng)用數(shù)據(jù)颅崩。下面我們詳細(xì)來講數(shù)據(jù)加載流程。

5.Launcher數(shù)據(jù)加載


數(shù)據(jù)加載主要是從LauncherModel中的startLoader方法開始蕊苗,先看一下這個方法做的事情:

launcher12.png

這里的事情不多沿后,主要是調(diào)用LoaderTask這個任務(wù),LoaderTask實現(xiàn)了Runnable這個接口朽砰,因此首先執(zhí)行潤run方法尖滚,我么看一下這個run方法里面做了哪些事情,

 public void run() {
            
            ...
            
            keep_running:
            {
                
                loadAndBindWorkspace();

                if (mStopped) {
                    break keep_running;
                }

                waitForIdle();

                ...
                
                loadAndBindAllApps();
            }

          ...
          
        }

在這個方法中主要是三件事锅移,我們用時序圖表一下:

launcher13.png

首先是執(zhí)行l(wèi)oadAndBindWorkspace方法:

 private void loadAndBindWorkspace() {
 
            ...
            
            //判斷workspace是否已經(jīng)加載
            if (!mWorkspaceLoaded) {
                loadWorkspace();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mWorkspaceLoaded = true;
                }
            }

            // Bind the workspace
            bindWorkspace(-1);
        }

這里面主要是執(zhí)行l(wèi)oadWorkspace和bindWorkspace熔掺,也就是加載workspace的應(yīng)用并且進(jìn)行綁定。先看loadWorkspace方法非剃,代碼很多置逻,我們只貼關(guān)鍵部分:

private void loadWorkspace() {
            //初始化一些值
            
            ...
            

            if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
                // append the user's Launcher2 shortcuts
                Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
                LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
            } else {
                // Make sure the default workspace is loaded
                Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
                LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
            }

            synchronized (sBgLock) {
                //初始化一些值
                ...

                try {
                   //從數(shù)據(jù)庫查詢解析出來的所有應(yīng)用信息
                   ...

                    while (!mStopped && c.moveToNext()) {
                        try {
                            int itemType = c.getInt(itemTypeIndex);
                            boolean restored = 0 != c.getInt(restoredIndex);
                            boolean allowMissingTarget = false;
                            container = c.getInt(containerIndex);

                            switch (itemType) {
                                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
                                
                                    ...
                                    
                                    try {
                                        intent = Intent.parseUri(intentDescription, 0);
                                        ComponentName cn = intent.getComponent();
                                        if (cn != null && cn.getPackageName() != null) {
                                            //檢測數(shù)據(jù)庫(從xml文件解析出來存入數(shù)據(jù)庫的)中取出來的app包是否存在
                                            boolean validPkg = launcherApps.isPackageEnabledForProfile(
                                                    cn.getPackageName(), user);
                                            //檢測數(shù)據(jù)庫(從xml文件解析出來存入數(shù)據(jù)庫的)中取出來的app組件是否存在
                                            boolean validComponent = validPkg &&
                                                    launcherApps.isActivityEnabledForProfile(cn, user);

                                            if (validComponent) {
                                            
                                                ...
                                                
                                            } else if (validPkg) {
                                               
                                               ...
                                               
                                            } else if (restored) {
                                                
                                                ...
                                                
                                            } else if (launcherApps.isAppEnabled(
                                                    manager, cn.getPackageName(),
                                                    PackageManager.GET_UNINSTALLED_PACKAGES)) {
                                               
                                               ...
                                               
                                            } else if (!isSdCardReady) {
                                                
                                                ...

                                            } else {
                                                
                                                ...
                                                
                                            }
                                        } else if (cn == null) {
                                            // For shortcuts with no component, keep them as they are
                                            restoredRows.add(id);
                                            restored = false;
                                        }
                                    } catch (URISyntaxException e) {
                                        Launcher.addDumpLog(TAG,
                                                "Invalid uri: " + intentDescription, true);
                                        itemsToRemove.add(id);
                                        continue;
                                    }

                                    ...

                                    if (info != null) {
                                        info.id = id;
                                        info.intent = intent;
                                        info.container = container;
                                        info.screenId = c.getInt(screenIndex);
                                        info.cellX = c.getInt(cellXIndex);
                                        info.cellY = c.getInt(cellYIndex);
                                        info.rank = c.getInt(rankIndex);
                                        info.spanX = 1;
                                        info.spanY = 1;
                                        info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
                                        ...

                                        switch (container) {
                                            case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                            case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                                                sBgWorkspaceItems.add(info);
                                                break;
                                            default:
                                                // Item is in a user folder
                                                FolderInfo folderInfo =
                                                        findOrMakeFolder(sBgFolders, container);
                                                folderInfo.add(info);
                                                break;
                                        }
                                        sBgItemsIdMap.put(info.id, info);
                                    } else {
                                        throw new RuntimeException("Unexpected null ShortcutInfo");
                                    }
                                    break;

                                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
                                
                                    ...
                                
                                    break;

                                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:
                                
                                    ...
                                                                    break;
                            }
                        } catch (Exception e) {
                            Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
                        }
                    }
                } finally {
                    ...
                }

                ...

                // Sort all the folder items and make sure the first 3 items are high resolution.
                for (FolderInfo folder : sBgFolders) {
                    Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                    int pos = 0;
                    for (ShortcutInfo info : folder.contents) {
                        if (info.usingLowResIcon) {
                            info.updateIcon(mIconCache, false);
                        }
                        pos++;
                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
                            break;
                        }
                    }
                }

                if (restoredRows.size() > 0) {
                    // Update restored items that no longer require special handling
                    ContentValues values = new ContentValues();
                    values.put(LauncherSettings.Favorites.RESTORED, 0);
                    contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
                            Utilities.createDbSelectionQuery(
                                    LauncherSettings.Favorites._ID, restoredRows), null);
                }

                if (!isSdCardReady && !sPendingPackages.isEmpty()) {
                    context.registerReceiver(new AppsAvailabilityCheck(),
                            new IntentFilter(StartupReceiver.SYSTEM_READY),
                            null, sWorker);
                }

                // Remove any empty screens
                ...

                // If there are any empty screens remove them, and update.
                if (unusedScreens.size() != 0) {
                    sBgWorkspaceScreens.removeAll(unusedScreens);
                    updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
                }

               ...
            }
        }

首先是調(diào)用loadDefaultFavoritesIfNecessary這個方法,來解析我們上面講的配置默認(rèn)的桌面圖標(biāo)的xml文件备绽,流程就是:初始化AutoInstallsLayout券坞,然后調(diào)用LauncherProvider中的loadFavorites方法,在這個方法中調(diào)用AutoInstallsLayout中的loadLayout方法來解析配置的xml文件肺素,在AutoInstallsLayout中通過對小部件恨锚,圖標(biāo),文件夾等分類進(jìn)行分辨解析倍靡,解析過程中如果有include標(biāo)簽猴伶,則對相應(yīng)的xml文件進(jìn)行解析,解析過程相對簡單,不在做詳細(xì)講解他挎,解析過程中將解析的各種信息存儲到數(shù)據(jù)庫中筝尾,以方便后面使用,當(dāng)xml文件解析完成后办桨,開始讀取解析xml配置文件存儲到數(shù)據(jù)庫的數(shù)據(jù)筹淫,讀取出來后,根據(jù)相應(yīng)的類型(圖標(biāo)呢撞,小部件损姜,文件夾等)進(jìn)行判斷,判斷系統(tǒng)中這個應(yīng)用是否存在殊霞,是否可用摧阅,如果可用則生成相應(yīng)對象并存儲到想定的map中,如果不存在則刪除數(shù)據(jù)庫中的數(shù)據(jù)脓鹃,這樣整個判斷完成后數(shù)據(jù)庫中的數(shù)據(jù)就只剩下系統(tǒng)中存在的配置應(yīng)用過了逸尖。

加載完配置應(yīng)用圖標(biāo)后,開始執(zhí)行bindWorkspace方法綁定應(yīng)用圖標(biāo)到桌面瘸右,代碼略過,我們看一下UML圖:

launcher14.png

通過上面的時序圖岩齿,我們看到太颤,首先執(zhí)行過濾工作,比如這個圖標(biāo)是在workspace中還是在Hotseat中盹沈,不同的位置放置不同的分類龄章,然后進(jìn)行排序處理,然后執(zhí)行bindWorkspaceScreens方法來綁定手機(jī)有幾個屏幕乞封,接著調(diào)用bindWorkspaceItems方法綁定當(dāng)前屏幕的圖標(biāo)做裙、文件夾和小插件信息,最后調(diào)用綁定其他屏幕的應(yīng)用圖標(biāo)肃晚、文件夾和小插件锚贱,關(guān)于綁定我們下一章再講。

接著執(zhí)行LoadTask中的waitForIdle方法关串,改方法主要是等待加載數(shù)據(jù)結(jié)束拧廊。

最后執(zhí)行l(wèi)oadAndBindAllApps方法來加載第二層的多有圖標(biāo)信息,看代碼:

 private void loadAndBindAllApps() {
            if (DEBUG_LOADERS) {
                Log.d(TAG, "loadAndBindAllApps mAllAppsLoaded=" + mAllAppsLoaded);
            }
            if (!mAllAppsLoaded) {
                loadAllApps();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                }
                updateIconCache();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mAllAppsLoaded = true;
                }
            } else {
                onlyBindAllApps();
            }
        }

主要是如果已經(jīng)加載了所有應(yīng)用這只是執(zhí)行綁定應(yīng)用晋修,如果沒有加載則執(zhí)行加載操作吧碾。下面看加載操作:

private void loadAllApps() {

            ...

            final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();

            // Clear the list of apps
            mBgAllAppsList.clear();
            for (UserHandleCompat user : profiles) {
            
                ...
            
               final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);
                
                // Fail if we don't have any apps
                // TODO: Fix this. Only fail for the current user.
                if (apps == null || apps.isEmpty()) {
                    return;
                }

                // Create the ApplicationInfos
                for (int i = 0; i < apps.size(); i++) {
                    LauncherActivityInfoCompat app = apps.get(i);
                    // This builds the icon bitmaps.
                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
                }
                
            ...
            
            // Huh? Shouldn't this be inside the Runnable below?
            final ArrayList<AppInfo> added = mBgAllAppsList.added;
            mBgAllAppsList.added = new ArrayList<AppInfo>();

            // Post callback on main thread
            mHandler.post(new Runnable() {
                public void run() {

                    final long bindTime = SystemClock.uptimeMillis();
                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.bindAllApplications(added);
                        if (DEBUG_LOADERS) {
                            Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - bindTime) + "ms");
                        }
                    } else {
                        Log.i(TAG, "not binding apps: no Launcher activity");
                    }
                }
            });
            ...

            loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
            ...
        }

上面代碼中通過mLauncherApps.getActivityList方法獲取所有應(yīng)用啟動界面的一個對象列表,然后根據(jù)LauncherActivityInfoCompat來初始化對應(yīng)的app對象墓卦,這樣就可以獲取手機(jī)中所有的應(yīng)用列表倦春。獲取完成后就執(zhí)行綁定操作,最后調(diào)用loadAndBindWidgetsAndShortcuts方法加載綁定小部件和快捷方式到小部件界面。

public void loadAndBindWidgetsAndShortcuts(final Callbacks callbacks, final boolean refresh) {

        runOnWorkerThread(new Runnable() {
            @Override
            public void run() {
                updateWidgetsModel(refresh);
                final WidgetsModel model = mBgWidgetsModel.clone();

                mHandler.post(new Runnable() {
                    @Override
                    public void run() {
                        Callbacks cb = getCallback();
                        if (callbacks == cb && cb != null) {
                            callbacks.bindAllPackages(model);
                        }
                    }
                });
                // update the Widget entries inside DB on the worker thread.
                LauncherAppState.getInstance().getWidgetCache().removeObsoletePreviews(
                        model.getRawList());
            }
        });
    }

在這個方法中首先調(diào)用updateWidgetsModel方法睁本,代碼如下:

 void updateWidgetsModel(boolean refresh) {
        PackageManager packageManager = mApp.getContext().getPackageManager();
        final ArrayList<Object> widgetsAndShortcuts = new ArrayList<Object>();
        widgetsAndShortcuts.addAll(getWidgetProviders(mApp.getContext(), refresh));
        Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT);
        widgetsAndShortcuts.addAll(packageManager.queryIntentActivities(shortcutsIntent, 0));
        mBgWidgetsModel.setWidgetsAndShortcuts(widgetsAndShortcuts);
    }

上面代碼中通過調(diào)用getWidgetProviders來獲取所有小部件山叮,通過shortcutsIntent來獲取所有的跨界方式,最后通過mBgWidgetsModel.setWidgetsAndShortcuts方法把小部件和快捷方式放到WidgetsModel對象中添履,在后期加載中可以從這個里面獲取小部件和快捷方式屁倔。

到這整個launcher的數(shù)據(jù)加載基本就完成了,還有很多細(xì)節(jié)沒有講暮胧,xml解析等锐借,這個谷歌工程師設(shè)計都是非常好的,有興趣的可以看看源碼往衷。

參考


Android系統(tǒng)默認(rèn)Home應(yīng)用程序(Launcher)的啟動過程源代碼分析

Android應(yīng)用程序組件Content Provider的啟動過程源代碼分析


本文的源碼是基于Android 6.0系統(tǒng)钞翔;

Launcher源碼:Launcher3_mx

首發(fā)地址:墨香博客

公眾賬號:Code-MX

本文原創(chuàng),轉(zhuǎn)載請注明出處席舍。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末布轿,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子来颤,更是在濱河造成了極大的恐慌汰扭,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,454評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件福铅,死亡現(xiàn)場離奇詭異萝毛,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)滑黔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評論 3 385
  • 文/潘曉璐 我一進(jìn)店門笆包,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人略荡,你說我怎么就攤上這事庵佣。” “怎么了汛兜?”我有些...
    開封第一講書人閱讀 157,921評論 0 348
  • 文/不壞的土叔 我叫張陵巴粪,是天一觀的道長。 經(jīng)常有香客問我序无,道長验毡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,648評論 1 284
  • 正文 為了忘掉前任帝嗡,我火速辦了婚禮晶通,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘哟玷。我一直安慰自己狮辽,他們只是感情好一也,可當(dāng)我...
    茶點故事閱讀 65,770評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著喉脖,像睡著了一般椰苟。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上树叽,一...
    開封第一講書人閱讀 49,950評論 1 291
  • 那天舆蝴,我揣著相機(jī)與錄音,去河邊找鬼题诵。 笑死洁仗,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的性锭。 我是一名探鬼主播赠潦,決...
    沈念sama閱讀 39,090評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼草冈!你這毒婦竟也來了她奥?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,817評論 0 268
  • 序言:老撾萬榮一對情侶失蹤怎棱,失蹤者是張志新(化名)和其女友劉穎哩俭,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蹄殃,經(jīng)...
    沈念sama閱讀 44,275評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡携茂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,592評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了诅岩。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,724評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡带膜,死狀恐怖吩谦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膝藕,我是刑警寧澤式廷,帶...
    沈念sama閱讀 34,409評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站芭挽,受9級特大地震影響滑废,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜袜爪,卻給世界環(huán)境...
    茶點故事閱讀 40,052評論 3 316
  • 文/蒙蒙 一蠕趁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧辛馆,春花似錦俺陋、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诱咏。三九已至,卻和暖如春缴挖,著一層夾襖步出監(jiān)牢的瞬間袋狞,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評論 1 266
  • 我被黑心中介騙來泰國打工映屋, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留苟鸯,地道東北人。 一個月前我還...
    沈念sama閱讀 46,503評論 2 361
  • 正文 我出身青樓秧荆,卻偏偏與公主長得像倔毙,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子乙濒,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,627評論 2 350

推薦閱讀更多精彩內(nèi)容