Android L launcher3启动流程

1. LauncherApplication
  oncreate()
      获取 LauncherAppState实例

2. LauncherAppState
    getInstance();
     new  LauncherAppState()
        判断屏幕尺寸,获取 density,创建widge preview Db(widget 预览图数据库)
        初始化IconCache实例

public  IconCache(Context context) {
    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 第一次启动的时候会用到这个文件来加载自定的默认桌面布局)
     设置
DeviceProfile的一些数值
对预定义的 DeviceProfile  进行排序,获取最接近当前屏幕参数的(width,height)的 DeviceProfile
closestProfile. numRows网格行数对应x, closestProfile. numColumns对应y。  closestProfile. defaultLayoutId对应 默认的布局文件id
计算 iconsize 
onAvailableSizeChanged iconTextSize (计算规则暂时没搞清楚)
android L以上版本在LauncherAppsCompatVL子类构造方法中直接获取系统服务LauncherApps
 
      
LauncherAppsCompatVL(Context context) {
    super();
    mLauncherApps = (LauncherApps) context.getSystemService("launcherapps");
}
 
      
LauncherApps是Android L新加入的Activity管理的基类
定义了一些Activity管理的方法(空实现,实现应该在其子类中)
 
      
定义了抽象类CallBack(回调类)
CallBack中定义了回调方法,类似PackageMonitor监听的类容,用来回调package状态发生变化PACKAGE_XXX
grid.layout( this ) 计算布局的数值
setupView 初始化界面
省略一些其他操作(contentobserver注册,appwidgetmanager等)

进入重头戏加载桌面图标
mModel . startLoader
通过调用LauncherModel里的startLoader进行加载

4.LauncherModel.java
LauncherModel通过子类 LoaderTask,实现了Runnable接口,另起一个线程加载图标
进入LoadTask run 方法
public void  run() {
// .....省略非关键代码
         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, ArrayList screenIds) {
          ....
          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等数据之后,将屏幕数据也插入到数据库
      进入下一步
synchronized 
( sBgLock ) {
    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 HashSet installingPkgs = 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 HashMap occupied = 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  
 
    
      List apps = 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
    以及暂时没有理解的问题都将放在后续分析。

你可能感兴趣的:(android学习)