墨香带你学Launcher之(二)-数据加载流程

上一篇墨香带你学Launcher之-概述,我已经介绍了Launcher的布局以及相关的界面跳转,今天我们继续学习,按照计划,我们开始学习Launcher启动之数据加载,主要是图标、Widget和文件夹的加载.

1.基础知识


在介绍加载之前我先介绍一点需要用的相关知识:

  • Launcher:继承Activity,是桌面的主界面,因此可知,桌面其实就是一个activity,只是和平常的应用不同,他用来显示图标、Widget和文件夹等;

  • LauncherModel:继承BroadcastReceiver,由此可知他是一个广播接收器,用来接收广播,另外,LauncherModel还主要加载数据;

  • LauncherProvider:继承ContentProvider,主要是处理数据库操作;

  • LauncherAppState:单例模式的全局管理类,主要是初始化一些对象,注册广播等.

  • Compat:兼容包,带有这个后缀的都是做兼容处理的类.

2.默认图标配置


我们在买回新的手机或者第一次安装新的Launcher后,会发现手机的第一页已经有了一些应用的图标和时钟或者天气插件,那么这个是怎么实现的呢?其实,手机在出厂的时候或者Launcher发到市场的时候已经默认排布了一些应用,在第一启动时就会加载并且判断手机中是否有这些图标,如果有则显示到固定位置,这个位置其实是已经写好的.下面我们看看这个位置到底在哪里写好的.

下面是Launcher的资源文件,我们看这个比我们平时的多一个xml文件夹,里面有很多xml文件,那么这些是做什么用的,我来解释一下,有三个文件,分别为default_workspace_4x4.xml,default_workspace_5x5.xml和default_workspace_5x6.xml,这三个文件就是我们默认的布局文件,后面的跟着的4x4、5x5和5x6表示桌面图标的列数和行数,也就是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部分,没一格表示一格图标,在我们绘制图标的时候已经分好了格,每格的大小,只要知道知道他的位置即可绘制图标到相应的位置,那么代码中的x,y就是这个图标的位置.上面resolve中还有两个favorite,在第一个中最后面有个"APP_",这个我们一看就知道是应用的属性,其实这就表示我们配置了那个app在这个位置,我们再看一下上面介绍的hotseat那个xml文件:

launcher04.png

这个图我只截图了一部分,想看全部的可以下载我github上的源码查看,其实只是重复,我介绍一个就知道了,上一章我介绍过hotseat这个概念,其实就是我们手机最下面的那个四个或者五个最常用的app图标,这个就是在这里面配置的,我以第一个为例来介绍这个Hotseat配置,我们先看第21行,这个比我们前面介绍的多个属性就是这个container,之前的是没有的,这个就表示容器,-101就是hotseat,也就是这个图标放置到Hotseat中,Hotseat只有一行,所以只有x在变,而y不变.

到此基本的桌面默认图标显示配置就介绍完了,如果你需要默认显示哪个只需要配置这个文件即可.

3.Launcher启动过程


下面我们开始介绍Launcher的启动过程.分析Launcher的启动过程要从源码开始分析.在源码中是通过startHomeActivityLocked这个方法调用的启动Launcher,我们先看一下哪里开始调用的这个函数,

launcher05.png

从上面的调用图可知有三个地方调用了启动Launcher的方法,这三个方法中首次启动应该是中间的那个systemReady方法,系统准备过程中调用启动Launcher,我们看一下systemReady方法是哪里调用的来验证一下:

launcher06.png

从上代码静态分析图来看最开始是在System.main方法开始的,正好这个方法就是启动系统的一个入口,也就是在这个过程中启动了Launcher,找到调用的地方后,我们来看一下startHomeActivityLocked是怎么启动Launcher的,首先看一下源码:

launcher07.png

我们看上面的3473行,获取Intent,再看3451行,如果不为空,则启动HomeActivity,我们看一下这个Intent是什么的Intent:

launcher08.png

上面的3424行,有个Intent.CATEGORY_HOME,我们在Intent中找到这个属性的代码:

launcher09.png

这个就是我们上一章讲的设置app为launcher的属性值.

通过上面这些分析可以看到系统是怎么启动launcher的.下面我们看是介绍Launcher内部是如何启动的.

4.Launcher初始化


我们知道App的启动是从Application开始的,但是我们最新的Launcher3中,谷歌工程师把这个类移除,再次之前的版本都是有这个类的,我在这提一下就是因为开发以前launcher的时候遇到一个问题,就是在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);
        }
    }

接着调用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);
        }

准备data数据,然后发送消息到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));
        }

根据消息类型BIND_APPLICATION来判断调用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);
        }
    }

在上面函数中调用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,
           
            ...
            
        }

      ...
      
    }

调用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;
    }

上面代码中有个关键方法: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();
        }
    }

我们看到在最后调用了ContentProvider.this.onCreate()这个方法,然后会返回到handleBindApplication方法中执行mInstrumentation.callApplicationOnCreate(app)方法,代码如下:


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

因此我们看到ContentProvider的onCreate方法比Application的onCreate方法调用早。这里只是简单介绍详细过程去看源码。

我现在讲解的是基于最新的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;
}

代码中处理的事情不多,主要是启动严苛模式和创建数据库,关于严苛模式的具体信息看官方文档或者博客,都有很详细的讲解,然后将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();

        //动态设置各布局的参数
        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();
        }
    }

代码比较多我们看一下执行过程图:

launcher11.png

首先是启动严苛模式,准备回调接口,初始化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");
        }
        
        //初始化固定的设备配置
        mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);
        
        //初始化图标管理工具
        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();
    }

然后初始化手机固件信息对象DeviceProfile,初始化拖拽管理器DragController,然后初始化小部件管理器,加载布局,初始化桌面各个控件,并且设置各个控件的位置:

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

        //其他省略
        ...

    }

这里就是动态设置桌面各个控件的位置及宽高等属性。当所有信息初始化完成后,就开始调用mModel.startLoader方法来加载应用数据。下面我们详细来讲数据加载流程。

5.Launcher数据加载


数据加载主要是从LauncherModel中的startLoader方法开始,先看一下这个方法做的事情:

launcher12.png

这里的事情不多,主要是调用LoaderTask这个任务,LoaderTask实现了Runnable这个接口,因此首先执行润run方法,我么看一下这个run方法里面做了哪些事情,

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

                if (mStopped) {
                    break keep_running;
                }

                waitForIdle();

                ...
                
                loadAndBindAllApps();
            }

          ...
          
        }

在这个方法中主要是三件事,我们用时序图表一下:

launcher13.png

首先是执行loadAndBindWorkspace方法:

 private void loadAndBindWorkspace() {
 
            ...
            
            //判断workspace是否已经加载
            if (!mWorkspaceLoaded) {
                loadWorkspace();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mWorkspaceLoaded = true;
                }
            }

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

这里面主要是执行loadWorkspace和bindWorkspace,也就是加载workspace的应用并且进行绑定。先看loadWorkspace方法,代码很多,我们只贴关键部分:

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 {
                   //从数据库查询解析出来的所有应用信息
                   ...

                    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) {
                                            //检测数据库(从xml文件解析出来存入数据库的)中取出来的app包是否存在
                                            boolean validPkg = launcherApps.isPackageEnabledForProfile(
                                                    cn.getPackageName(), user);
                                            //检测数据库(从xml文件解析出来存入数据库的)中取出来的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);
                }

               ...
            }
        }

首先是调用loadDefaultFavoritesIfNecessary这个方法,来解析我们上面讲的配置默认的桌面图标的xml文件,流程就是:初始化AutoInstallsLayout,然后调用LauncherProvider中的loadFavorites方法,在这个方法中调用AutoInstallsLayout中的loadLayout方法来解析配置的xml文件,在AutoInstallsLayout中通过对小部件,图标,文件夹等分类进行分辨解析,解析过程中如果有include标签,则对相应的xml文件进行解析,解析过程相对简单,不在做详细讲解,解析过程中将解析的各种信息存储到数据库中,以方便后面使用,当xml文件解析完成后,开始读取解析xml配置文件存储到数据库的数据,读取出来后,根据相应的类型(图标,小部件,文件夹等)进行判断,判断系统中这个应用是否存在,是否可用,如果可用则生成相应对象并存储到想定的map中,如果不存在则删除数据库中的数据,这样整个判断完成后数据库中的数据就只剩下系统中存在的配置应用过了。

加载完配置应用图标后,开始执行bindWorkspace方法绑定应用图标到桌面,代码略过,我们看一下UML图:

launcher14.png

通过上面的时序图,我们看到,首先执行过滤工作,比如这个图标是在workspace中还是在Hotseat中,不同的位置放置不同的分类,然后进行排序处理,然后执行bindWorkspaceScreens方法来绑定手机有几个屏幕,接着调用bindWorkspaceItems方法绑定当前屏幕的图标、文件夹和小插件信息,最后调用绑定其他屏幕的应用图标、文件夹和小插件,关于绑定我们下一章再讲。

接着执行LoadTask中的waitForIdle方法,改方法主要是等待加载数据结束。

最后执行loadAndBindAllApps方法来加载第二层的多有图标信息,看代码:

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

主要是如果已经加载了所有应用这只是执行绑定应用,如果没有加载则执行加载操作。下面看加载操作:

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方法获取所有应用启动界面的一个对象列表,然后根据LauncherActivityInfoCompat来初始化对应的app对象,这样就可以获取手机中所有的应用列表。获取完成后就执行绑定操作,最后调用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());
            }
        });
    }

在这个方法中首先调用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);
    }

上面代码中通过调用getWidgetProviders来获取所有小部件,通过shortcutsIntent来获取所有的跨界方式,最后通过mBgWidgetsModel.setWidgetsAndShortcuts方法把小部件和快捷方式放到WidgetsModel对象中,在后期加载中可以从这个里面获取小部件和快捷方式。

到这整个launcher的数据加载基本就完成了,还有很多细节没有讲,xml解析等,这个谷歌工程师设计都是非常好的,有兴趣的可以看看源码。

参考


Android系统默认Home应用程序(Launcher)的启动过程源代码分析

Android应用程序组件Content Provider的启动过程源代码分析


本文的源码是基于Android 6.0系统;

Launcher源码:Launcher3_mx

首发地址:墨香博客

公众账号:Code-MX

本文原创,转载请注明出处。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容