ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); mContext = context; mPackageManager = context.getPackageManager(); mUserManager = UserManagerCompat.getInstance(mContext); mLauncherApps = LauncherAppsCompat.getInstance(mContext); mIconDpi = activityManager.getLauncherLargeIconDensity(); // need to set mIconDpi before getting default icon UserHandleCompat myUser = UserHandleCompat.myUserHandle(); mDefaultIcons.put(myUser, makeDefaultIcon(myUser)); }
UserManagerCompat 用户管理兼容类(暂时这么称呼)应该是多用户管理相关的类抽象类
直接子类包括UserManagerCompatV16(适用于SDK16及以下版本)UseranagerCompatV17(适用17以上21以下)
UseranagerCompatVL(Android 5 以上版本)
getInstace()分别创建不同版本的子类
LauncherAppsCompat 应用监听兼容类(暂时这么称呼)相当于launcher2中LauncherModel的包变化的广播监听
android L以下版本在LauncherAppsCompatV16子类构造方法中
LauncherAppsCompatV16(Context context) { mPm = context.getPackageManager(); mContext = context; mPackageMonitor = new PackageMonitor(); }
创建内部PackageMonitor,继承了BroadCastReceiver监听Intent.ACTION_PACKAGE_XXXX广播(launcher2中,在LauncherModel中监听)
UserHandleCompat(用户交互兼容类?暂时不知道是干嘛用的)
mDefaultIcons放入默认图标,IconCache初始化完毕
继续new LauncherAppState()
mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class)); mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));
AppFilter BuildInfo暂时不知道这两个是干嘛用的默认为空loadByName 生成一个实例,猜测是过滤用的
LauncherAppsCompat.addOnAppsChangedCallback v
public abstract void addOnAppsChangedCallback(OnAppsChangedCallbackCompat listener);
(V16)注册上面提到的PACKAGE_XXX广播
(VL) 中通过WrappedCallback继承LauncherApps.Callback 通过LauncherApps.unregisterCallback注册回调
LauncherModel实现了OnAppsChangedCallbackCompat接口,PACKAGE_XXX状态变化,由LauncherModel进行管理
继续new LauncherAppState()
注册IntentFilter监听广播,注册LauncherModel监听广播,内容观察者监听favorite数据改变(类似Launcher2中操作)
3.Launcher.java
oncreate();
首先获取LauncherAppState实例(这些和launcher2差不多,只不过是文件变了)
初始化桌面网格
获取屏幕width,height取值范围,和当前真实值
初始化一些列DeviceProfile DeviceProfile 设备配置文件首先创建一个 DynamicGrid(动态网格) 初始化一些列 DeviceProfile (主要是默认布局文件default_workspace 第一次启动的时候会用到这个文件来加载自定的默认桌面布局)
android L以上版本在LauncherAppsCompatVL子类构造方法中直接获取系统服务LauncherApps
LauncherAppsCompatVL(Context context) { super(); mLauncherApps = (LauncherApps) context.getSystemService("launcherapps"); }
LauncherApps是Android L新加入的Activity管理的基类
定义了一些Activity管理的方法(空实现,实现应该在其子类中)
定义了抽象类CallBack(回调类)
CallBack中定义了回调方法,类似PackageMonitor监听的类容,用来回调package状态发生变化PACKAGE_XXX
isUpgrade = loadAndBindWorkspace();//加载并绑定workspace(homescreen)
loadAndBindAllApps();//加载绑定所有应用列表(抽屉界面)
}
先分析loadAndBindWorkspace
private boolean loadAndBindWorkspace() {
//......
isUpgradePath = loadWorkspace() //加载workspace
bindWorkspace(-1, isUpgradePath); //绑定到桌面
}
进入loadWorkspace(),擦,尼玛居然有600多行代码,一个方法里面,慢慢看吧
首先获取实例ContentResolve,PackageManager,AppWidgetManager,LauncherAppCompt
isSdCardReady = registerReceiver??干嘛用的sd卡准备好没有?怎么注册了一个system_ready广播,先放在这里吧
DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();//拿到设备配置信息实例 获取网格x y
LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts()//从launcher2的数据库中获取布局信息
加载的思路是,先查询launcher2的contentprovider 获取数据库数据
然后遍历Cursor,构建每一个应用的contentvalue(分shortcut ,folder)保存信息
然后启动一个sql 事务,插入数据库,然后保存屏幕数据,id和rank 同样使用事务
LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();//加载默认布局文件
做了半天的操作,什么workspaceloader, 什么partner,没搞懂是啥玩意,先放着
关键代码mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader)
执行加载操作,此处loader有个workspaceResId属性,表示默认布局的id
launcher3中有提供了带抽屉和不带抽屉两种布局 由getDefaultWorkspaceResourceId得到
具体控制由LauncherAppState.isDisableAllApps()
进入loadFavorites()中
关键代码int count = loader.loadLayout(db, screenIds)//
SimpleWorkspaceLoader loadlayout
关键代码mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), loader);//递归加载布局
尼玛150行左右的代码,要不要这样,慢慢看
private int loadFavoritesRecursive(SQLiteDatabase db, Resources res, int workspaceResourceId, ArrayListscreenIds) {
....
XmlResourceParser parser = res.getXml(workspaceResourceId);//加载默认布局文件,并解析xml
//加载默认布局,其实就是将xml文件中定义的数据,解析出来,构造成不同的javabean,然后保存到数据库中 //一步步看
//先构造了一个ContentValue,然后用ContentValue保存,screen,container等数据
values.put(LauncherSettings.Favorites.CONTAINER, container);
values.put(LauncherSettings.Favorites.SCREEN, screen);
values.put(LauncherSettings.Favorites.CELLX, x);
values.put(LauncherSettings.Favorites.CELLY, y);
......//根据解析出来的类型,进行不同的操作if (TAG_FAVORITE.equals(name)) { long id = addAppShortcut(db, values, parser);//addAppShortcut 方法里面执行的操作是,根据定义的package和class构造一个component//然后通过packagemanager获取应用,如果有,则获取应用信息,最后插入到数据库 added = id >= 0; } else if (TAG_APPWIDGET.equals(name)) { added = addAppWidget(parser, type, db, values);//类似addAppshortcut//先构造component,通过packagemanager.getReceiver()获取对应widget ,(widget类继承BroadcastReceiver),最后插入数据库} else if (TAG_SHORTCUT.equals(name)) {
long id = addUriShortcut(db, values, res, parser);
added = id >= 0;
} else if (TAG_RESOLVE.equals(name)) {//看resolve标签//resolve标签包含多条定义(可以包含多个favorite)//调用TAG_FAVORITE相同的方法添加,当找到一个应用之后,结束}else if (TAG_FOLDER.equals(name)) {// Folder contents are nested in this XML file added = loadFolder(db, values, res, parser);//文件夹//首先拿到文件夹资源id(在launcher的string中定义)//然后遍历folder下的数据,将container设为folder的id,其他过程和addappshortcut一样//最后会判断,如果文件夹中只有一个,则删除文件夹和里面的内容的数据,并把该数据container设为桌面重新插入到数据库//最后返回一个add (添加的数量) }}
//向数据库中插入文件夹数据 long folderId = addFolder(db, values);
递归加载完favorite等数据之后,将屏幕数据也插入到数据库
进入下一步
( sBgLock ) {synchronized
clearSBgDataStructures();//....//尼玛又是300多行
}
private void clearSBgDataStructures() { synchronized (sBgLock) { sBgWorkspaceItems.clear(); sBgAppWidgets.clear(); sBgFolders.clear(); sBgItemsIdMap.clear(); sBgDbIconCache.clear(); sBgWorkspaceScreens.clear(); }
先清理数据,这些集合会保存相信的数据
通过遍历每一个应用,然后addSessionInfoToCache会将应用的图标纳入IconCache进行管理
然后查询数据库,获取favorite中的数据(我没记错的话,此时应该只有default xml中的数据)
final HashSetinstallingPkgs = PackageInstallerCompat .getInstance(mContext).updateAndGetActiveSessionCache();
这个方法在api21之前,installingPkgs 是一个空的HashSet,
在api21之后,会调用mInstaller.getAllSessions()获取到所有的应用(猜测应该是已安装的)
for (SessionInfo info : mInstaller.getAllSessions()) {
addSessionInfoToCahce(info, user); if (info.getAppPackageName() != null) { activePackages.add(info.getAppPackageName()); } }
}
final Cursor c = contentResolver.query(contentUri, null, null, null, null); // +1 for the hotseat (it can be larger than the workspace) // Load workspace in reverse order to ensure that latest items are loaded first (and // before any earlier duplicates) final HashMapoccupied = new HashMap ();
ocuupide保存单元格是否被占用
AtomicBoolean deleteOnInvalidPlacement = new AtomicBoolean(false);//删除无效的位置
太尼玛多了,操作比较繁琐,看得眼睛累
大概的意思就是,先拿到所有安装的应用,加载数据库中的应用信息,判断所有应用中是否包含加载的应用
如果不包含,保存在itemtoremove中,检查位置是否被占用,如果被占用,存入itemtoremove中
最后遍历itemroremove,从数据库中删除
同时数据会保存到sBgWorkspaceItems中
对了,bind的时候会分批次,以及区分当前屏和其他屏,先加载当前屏,再加载其他屏
区分是根据screenid ,先将所有根据screenid 放进集合中
至此,loadworkspace()完毕,真特么累,太复杂了吧 这个流程,忍不住想吐槽一下
bindWorkspace()
即分批次,将内容添加到桌面上去(后面会专门开一篇来,分析bindworkspace)
workspace(homescreen 即主屏)加载完毕之后,还要加载剩下的应用,在loadAndBindAllApps中
loadAllApps();
关键代码:
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
//??????干嘛的这是,后面都没看到调用
//构造intent,这个intent表示,应用的入口而且定义显示桌面图标activity
Listapps = mLauncherApps.getActivityList(null, user);
//获取获取所有应用
//mLauncherApps.getActivityList(null, user)
//调用了LauncherApps的getActivityList(String packageName, UserHandle user)
//获取制定包名的list集合,package为null,我猜测应该是获取所有activity集合
//这个是在android l的实现
//再看一下LauncherAppComatV16上
//mPm.queryIntentActivities(mainIntent, 0) 通过packagemanager查询包含main以及launcher intent的ResolveInfo
//然后将所有的ResolveInfo存入集合中返回
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, mLabelCache)); }
//(先对app进行排序,根据应用名称),然后遍历list,将应用信息加入到mBgAllAppsList中
load 的流程结束,开始bind
//另起一个线程,调用callbacks.bindAllApplications(added);
//在launcher.java中实现
首先判断allapp是否可用(即是否显示抽屉)
然后将应用添加到不同的位置上(homescreen 或者 allapplist)
(后面会专门开一篇来分析,bind的过程)
//然后更新图标
//判断shortcutinfo 的icon和缓存中的是否为同一个,不是则更新,最后存入数据库,sBgDbIconCache
synchronized (sBgLock) { for (Object key : sBgDbIconCache.keySet()) { updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key)); } sBgDbIconCache.clear(); }
if (LauncherAppState.isDisableAllApps()) {
// Ensure that all the applications that are in the system are // represented on the home screen. if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) { verifyApplications(); } }
如果不显示抽屉,则确保所有app都被加载到home screen上
这个就是遍历Allapplist中的数据,然后filterItemInfos,添加被遗漏的icon(暂时没看懂filter这是怎么写的)
最后将遗漏的icon添加到homescreen上去
自此,LauncherModel就将所有的应用图标加载到桌面上了,并将这些数据都存入到数据库中。
然后,判断是否需要显示引导界面
launcher3的启动流程完毕,桌面终于起来了。
后续将另起新的文章来填坑,本文中涉及到的一些细节问题,如bindworkspace,bindallapplist
以及暂时没有理解的问题都将放在后续分析。