Android 桌面加载图标过程分析
Android 桌面加载图标过程分析
蔡小鹏 发表于5个月前
Android 桌面加载图标过程分析
  • 发表于 5个月前
  • 阅读 1734
  • 收藏 62
  • 点赞 4
  • 评论 8

桌面应用图标流程

前言

本人工作上碰到这么一个需求,开发一款滤镜引擎,将桌面上所有的图标进行统一的滤镜化,这就需要了解一下整个桌面去取图标的过程,了解了整个过程,找到真正拿图标的地方,在真正取图标的地方将图片进行替换,或者滤镜化,之前分析情况,现在整理下,与大家分享。本文所用的代码,是基于Android 5.1

桌面组件介绍

一级页面

  • 一级菜单

    • WorkSpace:他是一个ViewGroup,要想在桌面上显示东西,就得往这个ViewGroup里添加自己的View

    • BubbleTextView:他是一个TextView,上方是图标,下方是名称,在桌面上的图标都是由这个类表示

    • FolderIcon:他也是一个ViewGroup,用来表示桌面上的文件夹图标,里面添加了缩略处理过的bitmap,他的背景图片就是文件夹的形状

    • HotSeat: 他是个FrameLayout,是桌面下方的固定快捷区,包含了几个常用的图标,中间的AllApp按钮是固定位置,也是一个TextView

抽屉桌面

  • 抽屉页面 组件

    • PagedView:他是一个viewgroup,代表进入抽屉页后的界面,应用图标需要添加到这个viewgoup里面才能显示,一个或几个PagedView 承载了手机上所有的应用图标
    • PagedViewIcon:他是一个TextView,和BubblTextView一样,只是在抽屉容器里换了个名字

####桌面加载图标流程

  • 先来看一张流程图

    桌面加载流程图

  • 桌面Activity 也就是Launcher.java 类,该类里面维护了一个 LauncherModel,该对象会在onCreate 方法中去调用startLoader() 方法,

  • 下面看一下startLoader() 方法的源码,

      public void startLoader(boolean isLaunching, int synchronousBindPage) {
          synchronized (mLock) {
              if (DEBUG_LOADERS) {
                  Log.d(TAG, "startLoader isLaunching=" + isLaunching);
              }
    
              // Clear any deferred bind-runnables from the synchronized load process
              // We must do this before any loading/binding is scheduled below.
              mDeferredBindRunnables.clear();
    
              // Don't bother to start the thread if we know it's not going to do anything
              if (mCallbacks != null && mCallbacks.get() != null) {
                  // If there is already one running, tell it to stop.
                  // also, don't downgrade isLaunching if we're already running
                  isLaunching = isLaunching || stopLoaderLocked();
      			// 这搞了一个异步任务去加载
                  mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching);
                  if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
                      mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                  } else {
                      sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                      sWorker.post(mLoaderTask);
                  }
              }
          }
     	 }
    

    我们看到,这里面有个关键的类,loaderTask,见名只义,可以猜到这里面会起一个线程,去加载一些资源。看看里面去加载什么

  • LoaderTask.java

    可以看到LoaderTask实现了Runnable接口,直接去看该类的run() 方法

      public void run() {
          boolean isUpgrade = false;
    
          synchronized (mLock) {
              mIsLoaderTaskRunning = true;
          }
          // Optimize for end-user experience: if the Launcher is up and // running with the
          // All Apps interface in the foreground, load All Apps first. Otherwise, load the
          // workspace first (default).
          keep_running: {
              // Elevate priority when Home launches for the first time to avoid
              // starving at boot time. Staring at a blank home is not cool.
              synchronized (mLock) {
                  if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
                          (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
                  android.os.Process.setThreadPriority(mIsLaunching
                          ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
              }
              if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
    
      		//加载一级菜单的方法
      		isUpgrade = loadAndBindWorkspace();
    
              if (mStopped) {
                  break keep_running;
              }
    
              // Whew! Hard work done.  Slow us down, and wait until the UI thread has
              // settled down.
              synchronized (mLock) {
                  if (mIsLaunching) {
                      if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
                      android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
                  }
              }
              waitForIdle();
    
              // second step
              if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
              //加载二级菜单里面的方法
      		loadAndBindAllApps();
    
              // Restore the default thread priority after we are done loading items
              synchronized (mLock) {
                  android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
              }
          }
    
          // Update the saved icons if necessary
          if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
          synchronized (sBgLock) {
              for (Object key : sBgDbIconCache.keySet()) {
                  updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
              }
              sBgDbIconCache.clear();
          }
    
          if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
              // Ensure that all the applications that are in the system are
              // represented on the home screen.
              if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
                  verifyApplications();
              }
          }
    
          // Clear out this reference, otherwise we end up holding it until all of the
          // callback runnables are done.
          mContext = null;
    
          synchronized (mLock) {
              // If we are still the last one to be scheduled, remove ourselves.
              if (mLoaderTask == this) {
                  mLoaderTask = null;
              }
              mIsLoaderTaskRunning = false;
          }
      }
    

    可以看到在该类中主要有两个方法,

    • loadAndBindWorkSpace(), WorkSpace是一级菜单里面的容器类,该方法是加载一及菜单的方法
    • loadAndBindAllapp() ,这是抽屉内二级菜单的加载方法

    下面着重分析下这两个方法的加载流程

####loadAndBindWorkSpace()

  • 这里加载主要分为两个流程一个是 loadWorkSpace 另一个是 bindWorkSpace,可以看下源代码

      private boolean loadAndBindWorkspace() {
          mIsLoadingAndBindingWorkspace = true;
    
          // Load the workspace
          if (DEBUG_LOADERS) {
              Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
          }
    
          boolean isUpgradePath = false;
          if (!mWorkspaceLoaded) {
              isUpgradePath = loadWorkspace();
              synchronized (LoaderTask.this) {
                  if (mStopped) {
                      return isUpgradePath;
                  }
                  mWorkspaceLoaded = true;
              }
          }
    
          // Bind the workspace
          bindWorkspace(-1, isUpgradePath);
          return isUpgradePath;
      }
    

    可以看到并没有直接去加载,而是先判断了一些条件,然后去加载

    • loadWorkSpace() 方法比较长大概分为三步,

      • 初始化后面要用到的对象实例

          final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
        
          final Context context = mContext;
          final ContentResolver contentResolver = context.getContentResolver();
          final PackageManager manager = context.getPackageManager();
          final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
          final boolean isSafeMode = manager.isSafeMode();
        
          LauncherAppState app = LauncherAppState.getInstance();
          DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
          int countX = (int) grid.numColumns;
          int countY = (int) grid.numRows;
        
      • 加载默认配置,并保存数据库中

          synchronized public void loadDefaultFavoritesIfNecessary(int origWorkspaceResId) {
              String spKey = LauncherAppState.getSharedPreferencesKey();
              SharedPreferences sp = getContext().getSharedPreferences(spKey, Context.MODE_PRIVATE);
        
              if (sp.getBoolean(EMPTY_DATABASE_CREATED, false)) {
                  int workspaceResId = origWorkspaceResId;
        
                  // Use default workspace resource if none provided
          		//如果workspaceResId=0,就会加载默认的配置(default_workspace_xxx.xml),并保存到数据库中
                  if (workspaceResId == 0) {
                      workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);
                  }
        
                  // Populate favorites table with initial favorites
                  SharedPreferences.Editor editor = sp.edit();
                  editor.remove(EMPTY_DATABASE_CREATED);
                  if (origWorkspaceResId != 0) {
                      editor.putInt(DEFAULT_WORKSPACE_RESOURCE_ID, origWorkspaceResId);
                  }
        
                  mOpenHelper.loadFavorites(mOpenHelper.getWritableDatabase(), workspaceResId);
                  mOpenHelper.setFlagJustLoadedOldDb();
                  editor.commit();
              }
          }
        
      • 读取数据库,获取需要加载的应用快捷方式

        此段代码较多,就是去读取数据库的一些操作,具体过程是根据一些不同的type 存到不同的list中。

    • bindWorkSpace()

      应用信息读取完之后,刚才的几个变量中就存储了该信息,然后将其绑定到workspace中去,这个过程也是很复杂的

      • 创建局部变量,将全局变量的信息复制过来,单独进行操作

          ArrayList<iteminfo> workspaceItems = new ArrayList<iteminfo>();
          ArrayList<launcherappwidgetinfo> appWidgets = new ArrayList<launcherappwidgetinfo>();
          HashMap<long, folderinfo=""> folders = new HashMap<long, folderinfo="">();
          HashMap<long, iteminfo=""> itemsIdMap = new HashMap<long, iteminfo="">();
          ArrayList<long> orderedScreenIds = new ArrayList<long>();
          synchronized (sBgLock) {
              workspaceItems.addAll(sBgWorkspaceItems);
              appWidgets.addAll(sBgAppWidgets);
              folders.putAll(sBgFolders);
              itemsIdMap.putAll(sBgItemsIdMap);
              orderedScreenIds.addAll(sBgWorkspaceScreens);
          }
        
          final boolean isLoadingSynchronously = synchronizeBindPage != PagedView.INVALID_RESTORE_PAGE;
          int currScreen = isLoadingSynchronously ? synchronizeBindPage : oldCallbacks.getCurrentWorkspaceScreen();
          if (currScreen >= orderedScreenIds.size()) {
              // There may be no workspace screens (just hotseat items and an empty page).
              currScreen = PagedView.INVALID_RESTORE_PAGE;
          }
          final int currentScreen = currScreen;// 当前screen
          final long currentScreenId = currentScreen < 0 ? INVALID_SCREEN_ID : orderedScreenIds.get(currentScreen);// 当前screen id
        
          // Load all the items that are on the current page first (and in the process, unbind
          // all the existing workspace items before we call startBinding() below.
          unbindWorkspaceItemsOnMainThread();// 先解除绑定
        
      • 根据item中的screenID将items分成当前screen和其他screen,并进行排序

          // Separate the items that are on the current screen, and all the other remaining items
          ArrayList<iteminfo> currentWorkspaceItems = new ArrayList<iteminfo>();// 存放当前workspace上的items
          ArrayList<iteminfo> otherWorkspaceItems = new ArrayList<iteminfo>();// 存放除当前workspace之外的items
          ArrayList<launcherappwidgetinfo> currentAppWidgets = new ArrayList<launcherappwidgetinfo>();// 存放当前workspace上的appwidgets
          ArrayList<launcherappwidgetinfo> otherAppWidgets = new ArrayList<launcherappwidgetinfo>();// 存放除当前workspace之外的appwidgets
          HashMap<long, folderinfo=""> currentFolders = new HashMap<long, folderinfo="">();// 存放当前workspace上的folder
          HashMap<long, folderinfo=""> otherFolders = new HashMap<long, folderinfo="">();// 存放除当前workspace之外的folder
        
          filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems, otherWorkspaceItems);// 过滤items,区分当前screen和其他screen上的items
          filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets, otherAppWidgets);// 同上
          filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders, otherFolders);// 同上
          sortWorkspaceItemsSpatially(currentWorkspaceItems);// 对workspace上的items进行排序,按照从上到下和从左到右的顺序
          sortWorkspaceItemsSpatially(otherWorkspaceItems);// 同上
        
      • runnable执行块,告诉workspace要开始绑定items了,startBinding方法在Launcher中实现,做一些清除工作

          // Tell the workspace that we're about to start binding items
          r = new Runnable() {
              public void run() {
                  Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                  if (callbacks != null) {
                      callbacks.startBinding();
                  }
              }
          };
          runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
        

        可以看一下实现类launcher中startBinding()方法

          public void startBinding() {
              // If we're starting binding all over again, clear any bind calls we'd postponed in
              // the past (see waitUntilResume) -- we don't need them since we're starting binding
              // from scratch again
              mBindOnResumeCallbacks.clear();
        
              // Clear the workspace because it's going to be rebound
              mWorkspace.clearDropTargets();
              mWorkspace.removeAllWorkspaceScreens();
        
              mWidgetsToAdvance.clear();
              if (mHotseat != null) {
                  mHotseat.resetLayout();
              }
          }
        

        可以看到主要做的是清除和重置工作

      • 绑定workspace screen

           bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
           具体实现在launcher中
        
      • Workspace绑定完成之后,就是将items、widgets和folders放到上面去

####loadAndBindAllapp()

  • 可以看到加载过程也是分为两步:如果所有app已经加载过了,就只需要绑定就行了,否则的话,加载所有app,第一次启动肯定是加载所有的,我们按照这种情况来分析

    • 1.获取需要显示到Launcher中的app列表,创建app图标

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

        final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
      
        final Callbacks oldCallbacks = mCallbacks.get();
        if (oldCallbacks == null) {
            // This launcher has exited and nobody bothered to tell us.  Just bail.
            Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
            return;
        }
      
        final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
      
        final List<userhandlecompat> profiles = mUserManager.getUserProfiles();
      
        // Clear the list of apps
        mBgAllAppsList.clear();// 清除所有app列表
        SharedPreferences prefs = mContext.getSharedPreferences(
                LauncherAppState.getSharedPreferencesKey(), Context.MODE_PRIVATE);
        for (UserHandleCompat user : profiles) {
            // Query for the set of apps
            final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
            List<launcheractivityinfocompat> apps = mLauncherApps.getActivityList(null, user);// 获取需要显示在Launcher上的activity列表
            if (DEBUG_LOADERS) {
                Log.d(TAG, "getActivityList took "
                        + (SystemClock.uptimeMillis()-qiaTime) + "ms for user " + user);
                Log.d(TAG, "getActivityList got " + apps.size() + " apps for user " + user);
            }
            // Fail if we don't have any apps
            // TODO: Fix this. Only fail for the current user.
            if (apps == null || apps.isEmpty()) {// 没有需要显示的,直接返回
                return;
            }
            // Sort the applications by name
            final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
            Collections.sort(apps, new LauncherModel.ShortcutNameComparator(mLabelCache));// 排序
            if (DEBUG_LOADERS) {
                Log.d(TAG, "sort took " + (SystemClock.uptimeMillis()-sortTime) + "ms");
            }
      
            // 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, mLabelCache));// 创建应用图标对象,并添加到所有APP列表中
      
            }
      
            if (ADD_MANAGED_PROFILE_SHORTCUTS && !user.equals(UserHandleCompat.myUserHandle())) {
                // Add shortcuts for packages which were installed while launcher was dead.
                String shortcutsSetKey = INSTALLED_SHORTCUTS_SET_PREFIX + mUserManager.getSerialNumberForUser(user);
                Set<string> packagesAdded = prefs.getStringSet(shortcutsSetKey, Collections.EMPTY_SET);
                HashSet<string> newPackageSet = new HashSet<string>();
      
                for (LauncherActivityInfoCompat info : apps) {
                    String packageName = info.getComponentName().getPackageName();
                    if (!packagesAdded.contains(packageName)
                            && !newPackageSet.contains(packageName)) {
                        InstallShortcutReceiver.queueInstallShortcut(info, mContext);
                    }
                    newPackageSet.add(packageName);
                }
      
                prefs.edit().putStringSet(shortcutsSetKey, newPackageSet).commit();
            }
        }
        // Huh? Shouldn't this be inside the Runnable below?
        final ArrayList added = mBgAllAppsList.added;// 获取自上次更新(notify()广播)后新增加的应用清单,如果是开机初次启动Launcher,那么added就是mBgAllAppsList
        mBgAllAppsList.added = new ArrayList();// 将AllAppsList的added清空,不影响后续新增的app
      
        // 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");
                }
            }
        });
      
        if (DEBUG_LOADERS) {
            Log.d(TAG, "Icons processed in "
                    + (SystemClock.uptimeMillis() - loadTime) + "ms");
        }
      
    • 绑定app--bindAllApplications

       	public void bindAllApplications(final ArrayList apps) {
       	    if (LauncherAppState.isDisableAllApps()) {// 判断是否禁用所有app,就是所有应用都显示在一级目录
       	        if (mIntentsOnWorkspaceFromUpgradePath != null) {
       	            if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
       	                getHotseat().addAllAppsFolder(mIconCache, apps,
       	                        mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
       	            }
       	            mIntentsOnWorkspaceFromUpgradePath = null;
       	        }
       	        if (mAppsCustomizeContent != null) {
       	            mAppsCustomizeContent.onPackagesUpdated(
       	                    LauncherModel.getSortedWidgetsAndShortcuts(this));
       	        }
       	    } else {
       	        if (mAppsCustomizeContent != null) {
       	            mAppsCustomizeContent.setApps(apps);
       	            mAppsCustomizeContent.onPackagesUpdated(LauncherModel.getSortedWidgetsAndShortcuts(this));
       	        }
       	    }
       	    if (mLauncherCallbacks != null) {
       	        mLauncherCallbacks.bindAllApplications(apps);
       	    }
          	}
      
    可以看到无论是一级桌面拿图标,还是抽屉页面拿图标,都是去走,IconCache的getIcon()方法,
    

####IconCache

  • getIcon()

      public Bitmap getIcon(ComponentName component, ResolveInfo resolveInfo,
                        HashMap<Object, CharSequence> labelCache) {
          synchronized (mCache) {
              if (resolveInfo == null || component == null) {
                  return null;
              }
    
              CacheEntry entry = cacheLocked(component, resolveInfo, labelCache);
              return entry.icon;
          }
      }
    

    这里判断一下条件,会去走cacheLocked() 方法

  • cacheLocked()

      private CacheEntry cacheLocked(ComponentName componentName, ResolveInfo info,
                                 HashMap<Object, CharSequence> labelCache) {
          CacheEntry entry = mCache.get(componentName);
          if (entry == null) {
              entry = new CacheEntry();
    
              mCache.put(componentName, entry);
    
              ComponentName key = LauncherModel.getComponentNameFromResolveInfo(info);
              if (labelCache != null && labelCache.containsKey(key)) {
                  entry.title = labelCache.get(key).toString();
              } else {
                  entry.title = info.loadLabel(mPackageManager).toString();
                  if (labelCache != null) {
                      labelCache.put(key, entry.title);
                  }
              }
              if (entry.title == null) {
                  entry.title = info.activityInfo.name;
              }
    
              entry.icon = Utilities.createIconBitmap(
                      getFullResIcon(info), mContext);         
    
          }
          return entry;
      }
    

    这个方法里面。就是最终去拿图标的方法,里面去拿一些必要信息,去给entry赋值

标签: Launcher flowChat
共有 人打赏支持
粉丝 15
博文 19
码字总数 44512
评论 (8)
pkxutao
这是直接修改系统代码? 请问做framework开发怎样测试呢? 不可能每次修改都刷机一次吧?
蔡小鹏

引用来自“pkxutao”的评论

这是直接修改系统代码? 请问做framework开发怎样测试呢? 不可能每次修改都刷机一次吧?
桌面是上层App呀,你可以单独搞出来,做测试,不一定每次都make,
pkxutao

引用来自“pkxutao”的评论

这是直接修改系统代码? 请问做framework开发怎样测试呢? 不可能每次修改都刷机一次吧?

引用来自“蔡小鹏”的评论

桌面是上层App呀,你可以单独搞出来,做测试,不一定每次都make,
喔,就怕关联太多,一个个类抠出来越抠越多
宇润
现在还有应用抽屉这种东西?安卓2.1时代的。。。
贪婪的白木晓
。;
Dewdrop

引用来自“pkxutao”的评论

这是直接修改系统代码? 请问做framework开发怎样测试呢? 不可能每次修改都刷机一次吧?

引用来自“蔡小鹏”的评论

桌面是上层App呀,你可以单独搞出来,做测试,不一定每次都make,

引用来自“pkxutao”的评论

喔,就怕关联太多,一个个类抠出来越抠越多
Launcher是独立的,AndroidStudio直接就可以跑起来。https://android.googlesource.com/platform/packages/apps/Launcher3
Mr_Qi
这个代码好眼熟,以前也是搞Launcher的~
OSC_ZJqUVa
��
×
蔡小鹏
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: