launcher|Android launcher加载流程


launcher加载流程 launcher的加载流程,从launcher.java的oncreate中,调用setupViews方法,初始化了桌面所显示的view,并设置监听。
1、桌面图标加载 桌面图标的加载,mModel.startLoader正式开始加载流程。LauncherModel中的LoaderTask主要负责了桌面加载的工作。从它的run方法开始入手看起:
step 1: loading workspace
loadAndBindWorkspace(); 方法,负责加载并绑定数据库中的图标。 该方法分为loadWorkspace和bindWorkspace两步完成。
1.1:加载默认配置 如果是第一次启动桌面,会调用LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary加载默认配置的文件。

mOpenHelper.loadDefaultConfig加载默认配置(R.xml.default_workspace默认配置文件)

loadFavorites方法,解析xml文件,判断配置项的类型、页面数、行列数、占位数、intent、title、等等,组装成info数据插入数据库中。
1.2:加载数据库中信息 查询出数据库信息后,根据itemType类型(应用、快捷方式、文件夹、widget)进行不同的逻辑处理。 应用和快捷方式,通过getShortcutInfo获取到info信息、并设置info的图标和名称。 下面设置info的id、intent、container、setScreenId、setCellX、setCellY、setSpanX、setSpanY并根据container把info添加到sBgWorkspaceItems(桌面item)或者所在文件夹的info中。
sBgFolders:文件夹的应用信息
sBgWorkspaceItems: 桌面、底边栏的应用信息
sBgAppWidgets:widget的info信息
sBgItemsIdMap:以上所有info信息都在这个里面
读取完数据库数据后,会删除无效数据,代码如下:


if( itemsToRemove.size() > 0 ) { ContentProviderClient client = contentResolver.acquireContentProviderClient( LauncherSettings.Favorites.CONTENT_URI ); // Remove dead items for( long id : itemsToRemove ) { if( DEBUG_LOADERS ) { Log.d( TAG , "Removed id = " + id ); } // Don't notify content observers try { client.delete( LauncherSettings.Favorites.getContentUri( id , false ) , null , null ); } catch( RemoteException e ) { Log.w( TAG , "Could not remove id = " + id ); } } }


防止配置错误,比如两个图标配置了相同的页面数行列数,配置错了包类名等等。
更新页面数:

if( loadedOldDb ) { initWorkspaceScreensByItems( context ); //cheyingkun add //解决“loadedOldDb判断异常时,默认配置的图标不显示”的问题。【c_0003093】 } else { initWorkspaceScreensByDB( context ); //cheyingkun add //解决“loadedOldDb判断异常时,默认配置的图标不显示”的问题。【c_0003093】 }


更新页面数分为两种:第一次加载时,根据默认配置的item,算出页面数并插入数据库、第二次加载,读取数据库中的数据来确定页面数。

sBgWorkspaceScreens:桌面页面数列表

1.3:绑定上面加载好的信息 bindWorkspace方法:

callbacks.startBinding(); 开始bind前,预置操作,清空桌面所有图标。

bindWorkspaceScreens( oldCallbacks , orderedScreenIds ); 中调用callbacks.bindScreens( orderedScreens ); 绑定页面。

把当前页和其他页item分开,然后先绑定当前页的item。代码如下:

// Separate the items that are on the current screen, and all the other remaining items filterCurrentWorkspaceItems( currentScreen , workspaceItems , currentWorkspaceItems , otherWorkspaceItems ); filterCurrentAppWidgets( currentScreen , appWidgets , currentAppWidgets , otherAppWidgets ); filterCurrentFolders( currentScreen , itemsIdMap , folders , currentFolders , otherFolders ); sortWorkspaceItemsSpatially( currentWorkspaceItems ); sortWorkspaceItemsSpatially( otherWorkspaceItems ); // Load items on the current page bindWorkspaceItems( oldCallbacks , currentWorkspaceItems , currentAppWidgets , currentFolders , null);


绑定完当前页面后,才去绑定其他页,代码如下:

// Load all the remaining pages (if we are loading synchronously, we want to defer this // work until after the first render) synchronized( mDeferredBindRunnables ) { mDeferredBindRunnables.clear(); } bindWorkspaceItems( oldCallbacks , otherWorkspaceItems , otherAppWidgets , otherFolders , ( isLoadingSynchronously ? mDeferredBindRunnables : null ) );

这些都做完之后,回调callbacks.finishBindingItems( isUpgradePath ); 绑定workspace完成。
step 2: loading all apps
该步操作loadAndBindAllApps方法中,具体加载了手机里的所有应用并同步应用信息数据给主菜单界面和小部件界面。 loadAllApps:
查询所有应用代码如下:

final PackageManager packageManager = mContext.getPackageManager(); final Intent mainIntent = new Intent( Intent.ACTION_MAIN , null ); mainIntent.addCategory( Intent.CATEGORY_LAUNCHER ); // Clear the list of apps mBgAllAppsList.clear(); // Query for the set of apps final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; apps = packageManager.queryIntentActivities( mainIntent , 0 );


把默认配置的图标加入apps中,(如果apps中没有的话)


//check items of mBgItems that apps not contains of for( ItemInfo info : sBgWorkspaceItems ) { if( info.getItemType() == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ) { ShortcutInfo shortcut = (ShortcutInfo)info; ResolveInfo rInfo = new ResolveInfo(); //cheyingkun del start //智能分类后,某些应用图标显示为机器人的问题 //rInfo.activityInfo = new ActivityInfo(); //rInfo.activityInfo.applicationInfo = new ApplicationInfo(); //cheyingkun del end if( shortcut.getIntent() != null && shortcut.getIntent().getComponent() != null ) { //cheyingkun start//智能分类后,某些应用图标显示为机器人的问题 //【问题原因】默认配置的图标,没有launcher、Main属性但是有包类名,会再次添加到apps里,使用自己new的rInfo,根据rInfo初始化appinfo,由于rInfo里没有icon信息,导致cache里缓存的图标变成机器人 //再次加载桌面时,获取cache里的图标,显示为机器人。比如智能分类; 比如广播删除快捷方式后,onResume里重新加载、等等。 //【解决方案】根据intent获取rinfo //cheyingkun del start //rInfo.activityInfo.applicationInfo.packageName = shortcut.getIntent().getComponent().getPackageName(); //rInfo.activityInfo.packageName = shortcut.getIntent().getComponent().getPackageName(); //rInfo.activityInfo.name =shortcut.getIntent().getComponent().getClassName(); //cheyingkun del end rInfo = packageManager.resolveActivity( shortcut.getIntent() , 0 ); //cheyingkun add //cheyingkun end} apps.add( rInfo ); } }


把apps里的应用排序:


// Sort the applications by name final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0; Collections.sort( apps , new LauncherModel.ShortcutNameComparator( packageManager , mLabelCache ) );


循环apps列表创建appinfo:

// Create the ApplicationInfos for( int i = 0 ; i < apps.size() ; i++ ) { ResolveInfo app = apps.get( i ); // This builds the icon bitmaps. mBgAllAppsList.add( new AppInfo( packageManager , app , mIconCache , mLabelCache ) ); }

这一步走完之后,我们就获得到了所有的应用信息,并转换成了appinfo。
bindAllApplications中:

同步应用数据给主菜单界面:mAppsCustomizeContent.setApps( apps ); 第一次初始化插件数据给小部件界面:mAppsCustomizeContent.onPackagesUpdated( LauncherModel.getSortedWidgetsAndShortcuts( this ) );
step 3:显示应用图标
改方法只有在单层桌面被调用: verifyApplications中,具体把所有应用排序并显示在桌面上。
addAndBindAddedItems( context , app_list , null , cb , null , false , !mIsLoaderTaskRunning );

addAndBindAddedItems中:
先对app_list数据处理:依次计算页面数、格子数、如果页面满了则放入下一页同时更新桌面页面数列表(只是数据部分)。
把应用数据添加到数据库:

// Add the ItemInfo to the db addItemToDatabase( context , mItemInfo , LauncherSettings.Favorites.CONTAINER_DESKTOP , coords.first , coords.second[0] , coords.second[1] , false );


然后更新桌面页面 :
// Update the workspace screens updateWorkspaceScreenOrder( context , workspaceScreens );

再然后,把图标add到对应位置 :
callbacks.bindItemsAdded( addedWorkspaceScreensFinal , addNotAnimated , addAnimated , allAppsApps , installApp , isLoadFinish );

2、主菜单/小部件的加载 主菜单数据
“step 2: loading all apps”讲到,mAppsCustomizeContent.setApps( apps )同步数据给主菜单界面。 AppsCustomizePagedView这个类负责小部件和主菜单界面。 setApps中给ArrayList mApps赋值,并调用updatePageCountsAndInvalidateData方法。 尝试更新页面数并且刷新view。然后调用到invalidateOnDataChange中,由于是第一次进入, !isDataReady() 成立,没有走下面的invalidatePageData方法刷新界面view,因为当前界面不是主菜单。 只有当点击进入主菜单时,才会真正的刷新出主菜单图标。 小部件数据
“step 2: loading all apps”讲到,mAppsCustomizeContent.onPackagesUpdated( LauncherModel.getSortedWidgetsAndShortcuts( this ) ); 第一次初始化小部件界面。
LauncherModel.getSortedWidgetsAndShortcuts方法:获取手机的所有插件和快捷方式列表。 获取插件列表:

AppWidgetManager.getInstance( context ).getInstalledProviders();


获取快捷方式列表:

Intent shortcutsIntent = new Intent( Intent.ACTION_CREATE_SHORTCUT ); List mShortcutsList = packageManager.queryIntentActivities( shortcutsIntent , 0 );


mAppsCustomizeContent.onPackagesUpdated则根据传入的列表数据初始化widgets信息。
初始化widgets信息时,需要判断当前widget所占格子数是否超过桌面行列数:
// Ensure that all widgets we show can be added on a workspace of this size int[] spanXY = Launcher.getSpanForWidget( mLauncher , widget ); int[] minSpanXY = Launcher.getMinSpanForWidget( mLauncher , widget ); int minSpanX = Math.min( spanXY[0] , minSpanXY[0] ); int minSpanY = Math.min( spanXY[1] , minSpanXY[1] ); if( minSpanX <= (int)grid.getNumColumns() && minSpanY <= (int)grid.getNumRows() ) { mWidgets.add( widget ); }

最后和主菜单加载一样会调用到updatePageCountsAndInvalidateData方法。
主菜单/小部件的显示
点击主菜单按钮时,进入主菜单界面。(或者点击小部件按钮进入小部件界面,下面以主菜单代码为例)


showAllApps( true , AppsCustomizePagedView.ContentType.Applications , true );


点击后调用到下面代码,开始进入主菜单的动画和动画前后的状态改变:

showAppsCustomizeHelper( animated , false , contentType );


状态改变代码如下:

dispatchOnLauncherTransitionPrepare( fromView , animated , false ); dispatchOnLauncherTransitionStart( fromView , animated , false ); dispatchOnLauncherTransitionEnd( fromView , animated , false ); dispatchOnLauncherTransitionPrepare( toView , animated , false ); dispatchOnLauncherTransitionStart( toView , animated , false ); dispatchOnLauncherTransitionEnd( toView , animated , false );


AppsCustomizePagedView中的onMeasure方法:

@Override protected void onMeasure( int widthMeasureSpec , int heightMeasureSpec ) { int width = MeasureSpec.getSize( widthMeasureSpec ); int height = MeasureSpec.getSize( heightMeasureSpec ); if( !isDataReady() ) { if( ( !mApps.isEmpty() ) && !mWidgets.isEmpty() ) { setDataIsReady(); setMeasuredDimension( width , height ); onDataReady( width , height ); } } super.onMeasure( widthMeasureSpec , heightMeasureSpec ); }


onDataReady( width , height ); 中加载了界面

更新页面:updatePageCounts(); 这里只是计算一下一共有多少页

刷新页面数据:invalidatePageData( Math.max( 0 , page ) , hostIsTransitioning ); 其中包括添加页面和添加页面的每一个图标。

添加页面


// Update all the pages syncPages();


添加图标

// Load any pages that are necessary for the current window of views loadAssociatedPages( mCurrentPage , immediateAndOnly );



syncPageItems( i , ( i == page ) && immediateAndOnly );


根据类型判断添加小部件还是widgets:

@Override public void syncPageItems( int page , boolean immediate ) { if( mContentType == ContentType.Widgets ) { syncWidgetPageItems( page , immediate ); } else { syncAppsPageItems( page , immediate ); } }


syncWidgetPageItems和syncAppsPageItems中具体实现每个view的添加,包括位置、所占格子数、文字、图片等等。
尾注: 【launcher|Android launcher加载流程】以上就是launcher的加载流程,写的有点乱。只是粗略的记录一下流程顺序。有些细节没有写得很详细,需要对照代码自行理解。如有问题,欢迎纠正。

    推荐阅读