手记

Android 桌面加载图标过程分析

桌面应用图标流程

前言

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

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

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

        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放到上面去

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

        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中。

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

    • bindWorkSpace()

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

loadAndBindAllapp()

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

    可以看到无论是一级桌面拿图标,还是抽屉页面拿图标,都是去走,IconCache的getIcon()方法,
    • 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()

      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赋值



作者:Android高级架构探索
链接:https://www.jianshu.com/p/c3d2e8cb196c


0人推荐
随时随地看视频
慕课网APP