zoukankan      html  css  js  c++  java
  • 《Android进阶》之第二篇 launcher

     1 public boolean addViewToCellLayout(View child, int index, int childId, LayoutParams params,
     2             boolean markCells) {
     3         final LayoutParams lp = params;
     4 
     5         // Hotseat icons - remove text
     6         if (child instanceof BubbleTextView) {
     7             BubbleTextView bubbleChild = (BubbleTextView) child;
     8             bubbleChild.setTextVisibility(!mIsHotseat);
     9         }
    10 
    11         child.setScaleX(getChildrenScale());
    12         child.setScaleY(getChildrenScale());
    13 
    14         // Generate an id for each view, this assumes we have at most 256x256 cells
    15         // per workspace screen
    16         if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
    17             // If the horizontal or vertical span is set to -1, it is taken to
    18             // mean that it spans the extent of the CellLayout
    19             if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
    20             if (lp.cellVSpan < 0) lp.cellVSpan = mCountY;
    21 
    22             child.setId(childId);
    23 
    24             mShortcutsAndWidgets.addView(child, index, lp);
    25 
    26             if (markCells) markCellsAsOccupiedForView(child);
    27 
    28             return true;
    29         }
    30         return false;
    31     }

    allapp这就是加载每个icon到view的那个位置

    1、将就的地方 launcher.java

      static int[] getSpanForWidget(Context context, ComponentName component, int minWidth,
                int minHeight) {
    //        Rect padding = AppWidgetHostView.getDefaultPaddingForWidget(context, component, null);
          
            Rect padding = new Rect(20, 20, 300, 300);
            // We want to account for the extra amount of padding that we are adding to the widget
            // to ensure that it gets the full amount of space that it has requested
            int requiredWidth = minWidth + padding.left + padding.right;
            int requiredHeight = minHeight + padding.top + padding.bottom;
            return CellLayout.rectToCell(requiredWidth, requiredHeight, null);
        }
    View Code

    2、launcher.java

     1 /**
     2      * Add the icons for all apps.
     3      *
     4      * Implementation of the method from LauncherModel.Callbacks.
     5      */
     6     public void bindAllApplications(final ArrayList<AppInfo> apps) {
     7         if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
     8             if (mIntentsOnWorkspaceFromUpgradePath != null) {
     9                 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
    10                     getHotseat().addAllAppsFolder(mIconCache, apps,
    11                             mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
    12                 }
    13                 mIntentsOnWorkspaceFromUpgradePath = null;
    14             }
    15         } else {
    16             if (mAppsCustomizeContent != null) {
    17                 mAppsCustomizeContent.setApps(apps);
    18             }
    19         }
    20     }
    View Code

    3、判断是否在桌面

    public boolean isAllAppsVisible() {
      return (mState == State.APPS_CUSTOMIZE) || (mOnResumeState == State.APPS_CUSTOMIZE);
    }

    1、Callbacks接口

    LauncherModel里面,需要先分析一个Callbacks接口。

     1 public interface Callbacks {
     2         public boolean setLoadOnResume();
     3         public int getCurrentWorkspaceScreen();
     4         public void startBinding();
     5         public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
     6                               boolean forceAnimateIcons);
     7         public void bindScreens(ArrayList<Long> orderedScreenIds);
     8         public void bindAddScreens(ArrayList<Long> orderedScreenIds);
     9         public void bindFolders(HashMap<Long,FolderInfo> folders);
    10         public void finishBindingItems(boolean upgradePath);
    11         public void bindAppWidget(LauncherAppWidgetInfo info);
    12         public void bindAllApplications(ArrayList<AppInfo> apps);
    13         public void bindAppsAdded(ArrayList<Long> newScreens,
    14                                   ArrayList<ItemInfo> addNotAnimated,
    15                                   ArrayList<ItemInfo> addAnimated,
    16                                   ArrayList<AppInfo> addedApps);
    17         public void bindAppsUpdated(ArrayList<AppInfo> apps);
    18         public void bindComponentsRemoved(ArrayList<String> packageNames,
    19                         ArrayList<AppInfo> appInfos,
    20                         boolean matchPackageNamesOnly);
    21         public void bindPackagesUpdated(ArrayList<Object> widgetsAndShortcuts);
    22         public void bindSearchablesChanged();
    23         public boolean isAllAppsButtonRank(int rank);
    24         public void onPageBoundSynchronously(int page);
    25         public void dumpLogsToLocalData();
    26     }

    Callbacks接口提供了很多接口,用于返回相关的数据给Launcher模块,下面我们对每个接口作用做个阐释。

    setLoadOnResume() :当Launcher.java类的Activity处于onPause的时候,如果重新恢复,需要调用onResume,此时需要在onResume调用这个接口,恢复Launcher数据。

    getCurrentWorkspace():获取屏幕序号(0~4)

    startBinding():通知Launcher开始加载数据。清空容器数据,重新加载

    bindItems(ArrayList<ItemInfo> shortcuts, int start, int end):加载App shortcut、Live Folder、widget到Launcher相关容器。

    bindFolders(HashMap<Long, FolderInfo> folders):加载folder的内容

    finishBindingItems():数据加载完成。

    bindAppWidget(LauncherAppWidgetInfo item):workspace加载APP 快捷方式

    bindAllApplications(final ArrayList<ApplicationInfo> apps):所有应用列表接着APP图标数据

    bindAppsAdded(ArrayList<ApplicationInfo> apps):通知Launcher新安装了一个APP,更新数据。

    bindAppsUpdated(ArrayList<ApplicationInfo> apps):通知Launcher一个APP更新了。(覆盖安装)

    bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent):通知Launcher,应用被删除

    bindPackagesUpdated():多个应用更新。

    isAllAppsVisible():返回所有应用列表是否可见状态。

    bindSearchablesChanged():Google搜索栏或者删除区域发生变化时通知Launcher

    2、数据加载流程

    Launcher.java类继承了Callbacks接口,并实现了该接口。LauncherModel里面会调用这些接口,反馈数据和状态给Launcher。数据加载总体分为两部分,一部分是加载workspace的数据,另一部分是加载All APP界面的数据。

    3、startLoader()

    下面我们先分析startLoader()接口,startLoader主要是启动了一个线程,用于加载数据。

     1  public void startLoader(boolean isLaunching, int synchronousBindPage) {
     2         synchronized (mLock) {
     3             if (DEBUG_LOADERS) {
     4                 Log.d(TAG, "startLoader isLaunching=" + isLaunching);
     5             }
     6 
     7             // Clear any deferred bind-runnables from the synchronized load process
     8             // We must do this before any loading/binding is scheduled below.
     9             mDeferredBindRunnables.clear();
    10 
    11             // Don't bother to start the thread if we know it's not going to do anything
    12             if (mCallbacks != null && mCallbacks.get() != null) {
    13                 // If there is already one running, tell it to stop.
    14                 // also, don't downgrade isLaunching if we're already running
    15                 isLaunching = isLaunching || stopLoaderLocked();
    16                 mLoaderTask = new LoaderTask(mApp.getContext(), isLaunching);
    17                 if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) {
    18                     mLoaderTask.runBindSynchronousPage(synchronousBindPage);
    19                 } else {
    20                     sWorkerThread.setPriority(Thread.NORM_PRIORITY);
    21                     sWorker.post(mLoaderTask);
    22                 }
    23             }
    24         }
    25     }

     4、LoaderTask的run()方法

     1  public void run() {
     2             boolean isUpgrade = false;
     3 
     4             synchronized (mLock) {
     5                 mIsLoaderTaskRunning = true;
     6             }
     7             // Optimize for end-user experience: if the Launcher is up and // running with the
     8             // All Apps interface in the foreground, load All Apps first. Otherwise, load the
     9             // workspace first (default).
    10             keep_running: {
    11                 // Elevate priority when Home launches for the first time to avoid
    12                 // starving at boot time. Staring at a blank home is not cool.
    13                 synchronized (mLock) {
    14                     if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to " +
    15                             (mIsLaunching ? "DEFAULT" : "BACKGROUND"));
    16                     android.os.Process.setThreadPriority(mIsLaunching
    17                             ? Process.THREAD_PRIORITY_DEFAULT : Process.THREAD_PRIORITY_BACKGROUND);
    18                 }
    19                 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
    20                 isUpgrade = loadAndBindWorkspace();
    21 
    22                 if (mStopped) {
    23                     break keep_running;
    24                 }
    25 
    26                 // Whew! Hard work done.  Slow us down, and wait until the UI thread has
    27                 // settled down.
    28                 synchronized (mLock) {
    29                     if (mIsLaunching) {
    30                         if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
    31                         android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    32                     }
    33                 }
    34                 waitForIdle();
    35 
    36                 // second step
    37                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
    38                 loadAndBindAllApps();
    39 
    40                 // Restore the default thread priority after we are done loading items
    41                 synchronized (mLock) {
    42                     android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
    43                 }
    44             }
    45 
    46             // Update the saved icons if necessary
    47             if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons");
    48             synchronized (sBgLock) {
    49                 for (Object key : sBgDbIconCache.keySet()) {
    50                     updateSavedIcon(mContext, (ShortcutInfo) key, sBgDbIconCache.get(key));
    51                 }
    52                 sBgDbIconCache.clear();
    53             }
    54 
    55             if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
    56                 // Ensure that all the applications that are in the system are
    57                 // represented on the home screen.
    58                 if (!UPGRADE_USE_MORE_APPS_FOLDER || !isUpgrade) {
    59                     verifyApplications();
    60                 }
    61             }
    62 
    63             // Clear out this reference, otherwise we end up holding it until all of the
    64             // callback runnables are done.
    65             mContext = null;
    66 
    67             synchronized (mLock) {
    68                 // If we are still the last one to be scheduled, remove ourselves.
    69                 if (mLoaderTask == this) {
    70                     mLoaderTask = null;
    71                 }
    72                 mIsLoaderTaskRunning = false;
    73             }
    74         }
    View Code
     1 if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
     2                 isUpgrade = loadAndBindWorkspace();
     3 
     4                 if (mStopped) {
     5                     break keep_running;
     6                 }
     7 
     8                 // Whew! Hard work done.  Slow us down, and wait until the UI thread has
     9                 // settled down.
    10                 synchronized (mLock) {
    11                     if (mIsLaunching) {
    12                         if (DEBUG_LOADERS) Log.d(TAG, "Setting thread priority to BACKGROUND");
    13                         android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    14                     }
    15                 }
    16                 waitForIdle();
    17 
    18                 // second step
    19                 if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
    20                 loadAndBindAllApps();

    5、workspace加载数据

    loadAndBindWorkspace()方法主要就是执行loadWorkspace()和 bindWorkspace()方法。
    下面分别对这两个方法进行分析。
     1 /** Returns whether this is an upgrade path */
     2         private boolean loadAndBindWorkspace() {
     3             mIsLoadingAndBindingWorkspace = true;
     4 
     5             // Load the workspace
     6             if (DEBUG_LOADERS) {
     7                 Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded);
     8             }
     9 
    10             boolean isUpgradePath = false;
    11             if (!mWorkspaceLoaded) {
    12                 isUpgradePath = loadWorkspace();
    13                 synchronized (LoaderTask.this) {
    14                     if (mStopped) {
    15                         return isUpgradePath;
    16                     }
    17                     mWorkspaceLoaded = true;
    18                 }
    19             }
    20 
    21             // Bind the workspace
    22             bindWorkspace(-1, isUpgradePath);
    23             return isUpgradePath;
    24         }

     workspace的数据加载总的来说也是按照元素属性来区分加载,分为App快捷方式、Widget、Folder元素。

    这几个元素分别加载到不同的容器里面。其中sItemsIdMap保存所有元素的id和ItemInfo组成的映射。其他

    元素分别加载到3个不同的容器里面,用于后面绑定数据用。

      1   /** Returns whether this is an upgradge path */
      2         private boolean loadWorkspace() {
      3             final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
      4 
      5             final Context context = mContext;
      6             final ContentResolver contentResolver = context.getContentResolver();
      7             final PackageManager manager = context.getPackageManager();
      8             final AppWidgetManager widgets = AppWidgetManager.getInstance(context);
      9             final boolean isSafeMode = manager.isSafeMode();
     10 
     11             LauncherAppState app = LauncherAppState.getInstance();
     12             DeviceProfile grid = app.getDynamicGrid().getDeviceProfile();
     13             int countX = (int) grid.numColumns;
     14             int countY = (int) grid.numRows;
     15 
     16             // Make sure the default workspace is loaded, if needed
     17             LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary(0);
     18 
     19             // Check if we need to do any upgrade-path logic
     20             boolean loadedOldDb = LauncherAppState.getLauncherProvider().justLoadedOldDb();
     21 
     22             synchronized (sBgLock) {
     23                 clearSBgDataStructures();
     24 
     25                 final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
     26                 final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
     27                 if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
     28                 final Cursor c = contentResolver.query(contentUri, null, null, null, null);
     29 
     30                 // +1 for the hotseat (it can be larger than the workspace)
     31                 // Load workspace in reverse order to ensure that latest items are loaded first (and
     32                 // before any earlier duplicates)
     33                 final HashMap<Long, ItemInfo[][]> occupied = new HashMap<Long, ItemInfo[][]>();
     34 
     35                 try {
     36                     final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID);
     37                     final int intentIndex = c.getColumnIndexOrThrow
     38                             (LauncherSettings.Favorites.INTENT);
     39                     final int titleIndex = c.getColumnIndexOrThrow
     40                             (LauncherSettings.Favorites.TITLE);
     41                     final int iconTypeIndex = c.getColumnIndexOrThrow(
     42                             LauncherSettings.Favorites.ICON_TYPE);
     43                     final int iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON);
     44                     final int iconPackageIndex = c.getColumnIndexOrThrow(
     45                             LauncherSettings.Favorites.ICON_PACKAGE);
     46                     final int iconResourceIndex = c.getColumnIndexOrThrow(
     47                             LauncherSettings.Favorites.ICON_RESOURCE);
     48                     final int containerIndex = c.getColumnIndexOrThrow(
     49                             LauncherSettings.Favorites.CONTAINER);
     50                     final int itemTypeIndex = c.getColumnIndexOrThrow(
     51                             LauncherSettings.Favorites.ITEM_TYPE);
     52                     final int appWidgetIdIndex = c.getColumnIndexOrThrow(
     53                             LauncherSettings.Favorites.APPWIDGET_ID);
     54                     final int appWidgetProviderIndex = c.getColumnIndexOrThrow(
     55                             LauncherSettings.Favorites.APPWIDGET_PROVIDER);
     56                     final int screenIndex = c.getColumnIndexOrThrow(
     57                             LauncherSettings.Favorites.SCREEN);
     58                     final int cellXIndex = c.getColumnIndexOrThrow
     59                             (LauncherSettings.Favorites.CELLX);
     60                     final int cellYIndex = c.getColumnIndexOrThrow
     61                             (LauncherSettings.Favorites.CELLY);
     62                     final int spanXIndex = c.getColumnIndexOrThrow
     63                             (LauncherSettings.Favorites.SPANX);
     64                     final int spanYIndex = c.getColumnIndexOrThrow(
     65                             LauncherSettings.Favorites.SPANY);
     66                     //final int uriIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.URI);
     67                     //final int displayModeIndex = c.getColumnIndexOrThrow(
     68                     //        LauncherSettings.Favorites.DISPLAY_MODE);
     69 
     70                     ShortcutInfo info;
     71                     String intentDescription;
     72                     LauncherAppWidgetInfo appWidgetInfo;
     73                     int container;
     74                     long id;
     75                     Intent intent;
     76 
     77                     while (!mStopped && c.moveToNext()) {
     78                         AtomicBoolean deleteOnItemOverlap = new AtomicBoolean(false);
     79                         try {
     80                             int itemType = c.getInt(itemTypeIndex);
     81 
     82                             switch (itemType) {
     83                             case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
     84                             case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
     85                                 id = c.getLong(idIndex);
     86                                 intentDescription = c.getString(intentIndex);
     87                                 try {
     88                                     intent = Intent.parseUri(intentDescription, 0);
     89                                     ComponentName cn = intent.getComponent();
     90                                     if (cn != null && !isValidPackageComponent(manager, cn)) {
     91                                         if (!mAppsCanBeOnRemoveableStorage) {
     92                                             // Log the invalid package, and remove it from the db
     93                                             Launcher.addDumpLog(TAG, "Invalid package removed: " + cn, true);
     94                                             itemsToRemove.add(id);
     95                                         } else {
     96                                             // If apps can be on external storage, then we just
     97                                             // leave them for the user to remove (maybe add
     98                                             // visual treatment to it)
     99                                             Launcher.addDumpLog(TAG, "Invalid package found: " + cn, true);
    100                                         }
    101                                         continue;
    102                                     }
    103                                 } catch (URISyntaxException e) {
    104                                     Launcher.addDumpLog(TAG, "Invalid uri: " + intentDescription, true);
    105                                     continue;
    106                                 }
    107 
    108                                 if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
    109                                     info = getShortcutInfo(manager, intent, context, c, iconIndex,
    110                                             titleIndex, mLabelCache);
    111                                 } else {
    112                                     info = getShortcutInfo(c, context, iconTypeIndex,
    113                                             iconPackageIndex, iconResourceIndex, iconIndex,
    114                                             titleIndex);
    115 
    116                                     // App shortcuts that used to be automatically added to Launcher
    117                                     // didn't always have the correct intent flags set, so do that
    118                                     // here
    119                                     if (intent.getAction() != null &&
    120                                         intent.getCategories() != null &&
    121                                         intent.getAction().equals(Intent.ACTION_MAIN) &&
    122                                         intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
    123                                         intent.addFlags(
    124                                             Intent.FLAG_ACTIVITY_NEW_TASK |
    125                                             Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
    126                                     }
    127                                 }
    128 
    129                                 if (info != null) {
    130                                     info.id = id;
    131                                     info.intent = intent;
    132                                     container = c.getInt(containerIndex);
    133                                     info.container = container;
    134                                     info.screenId = c.getInt(screenIndex);
    135                                     info.cellX = c.getInt(cellXIndex);
    136                                     info.cellY = c.getInt(cellYIndex);
    137                                     info.spanX = 1;
    138                                     info.spanY = 1;
    139                                     // Skip loading items that are out of bounds
    140                                     if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    141                                         if (checkItemDimensions(info)) {
    142                                             Launcher.addDumpLog(TAG, "Skipped loading out of bounds shortcut: "
    143                                                     + info + ", " + grid.numColumns + "x" + grid.numRows, true);
    144                                             continue;
    145                                         }
    146                                     }
    147                                     // check & update map of what's occupied
    148                                     deleteOnItemOverlap.set(false);
    149                                     if (!checkItemPlacement(occupied, info, deleteOnItemOverlap)) {
    150                                         if (deleteOnItemOverlap.get()) {
    151                                             itemsToRemove.add(id);
    152                                         }
    153                                         break;
    154                                     }
    155 
    156                                     switch (container) {
    157                                     case LauncherSettings.Favorites.CONTAINER_DESKTOP:
    158                                     case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
    159                                         sBgWorkspaceItems.add(info);
    160                                         break;
    161                                     default:
    162                                         // Item is in a user folder
    163                                         FolderInfo folderInfo =
    164                                                 findOrMakeFolder(sBgFolders, container);
    165                                         folderInfo.add(info);
    166                                         break;
    167                                     }
    168                                     sBgItemsIdMap.put(info.id, info);
    169 
    170                                     // now that we've loaded everthing re-save it with the
    171                                     // icon in case it disappears somehow.
    172                                     queueIconToBeChecked(sBgDbIconCache, info, c, iconIndex);
    173                                 } else {
    174                                     throw new RuntimeException("Unexpected null ShortcutInfo");
    175                                 }
    176                                 break;
    177 
    178                             case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
    179                                 id = c.getLong(idIndex);
    180                                 FolderInfo folderInfo = findOrMakeFolder(sBgFolders, id);
    181 
    182                                 folderInfo.title = c.getString(titleIndex);
    183                                 folderInfo.id = id;
    184                                 container = c.getInt(containerIndex);
    185                                 folderInfo.container = container;
    186                                 folderInfo.screenId = c.getInt(screenIndex);
    187                                 folderInfo.cellX = c.getInt(cellXIndex);
    188                                 folderInfo.cellY = c.getInt(cellYIndex);
    189                                 folderInfo.spanX = 1;
    190                                 folderInfo.spanY = 1;
    191 
    192                                 // Skip loading items that are out of bounds
    193                                 if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    194                                     if (checkItemDimensions(folderInfo)) {
    195                                         Log.d(TAG, "Skipped loading out of bounds folder");
    196                                         continue;
    197                                     }
    198                                 }
    199                                 // check & update map of what's occupied
    200                                 deleteOnItemOverlap.set(false);
    201                                 if (!checkItemPlacement(occupied, folderInfo,
    202                                         deleteOnItemOverlap)) {
    203                                     if (deleteOnItemOverlap.get()) {
    204                                         itemsToRemove.add(id);
    205                                     }
    206                                     break;
    207                                 }
    208 
    209                                 switch (container) {
    210                                     case LauncherSettings.Favorites.CONTAINER_DESKTOP:
    211                                     case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
    212                                         sBgWorkspaceItems.add(folderInfo);
    213                                         break;
    214                                 }
    215 
    216                                 sBgItemsIdMap.put(folderInfo.id, folderInfo);
    217                                 sBgFolders.put(folderInfo.id, folderInfo);
    218                                 break;
    219 
    220                             case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
    221                                 // Read all Launcher-specific widget details
    222                                 int appWidgetId = c.getInt(appWidgetIdIndex);
    223                                 String savedProvider = c.getString(appWidgetProviderIndex);
    224 
    225                                 id = c.getLong(idIndex);
    226 
    227                                 final AppWidgetProviderInfo provider =
    228                                         widgets.getAppWidgetInfo(appWidgetId);
    229 
    230                                 if (!isSafeMode && (provider == null || provider.provider == null ||
    231                                         provider.provider.getPackageName() == null)) {
    232                                     String log = "Deleting widget that isn't installed anymore: id="
    233                                         + id + " appWidgetId=" + appWidgetId;
    234                                     Log.e(TAG, log);
    235                                     Launcher.addDumpLog(TAG, log, false);
    236                                     itemsToRemove.add(id);
    237                                 } else {
    238                                     appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId,
    239                                             provider.provider);
    240                                     appWidgetInfo.id = id;
    241                                     appWidgetInfo.screenId = c.getInt(screenIndex);
    242                                     appWidgetInfo.cellX = c.getInt(cellXIndex);
    243                                     appWidgetInfo.cellY = c.getInt(cellYIndex);
    244                                     appWidgetInfo.spanX = c.getInt(spanXIndex);
    245                                     appWidgetInfo.spanY = c.getInt(spanYIndex);
    246                                     int[] minSpan = Launcher.getMinSpanForWidget(context, provider);
    247                                     appWidgetInfo.minSpanX = minSpan[0];
    248                                     appWidgetInfo.minSpanY = minSpan[1];
    249 
    250                                     container = c.getInt(containerIndex);
    251                                     if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP &&
    252                                         container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) {
    253                                         Log.e(TAG, "Widget found where container != " +
    254                                             "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!");
    255                                         continue;
    256                                     }
    257 
    258                                     appWidgetInfo.container = c.getInt(containerIndex);
    259                                     // Skip loading items that are out of bounds
    260                                     if (container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
    261                                         if (checkItemDimensions(appWidgetInfo)) {
    262                                             Log.d(TAG, "Skipped loading out of bounds app widget");
    263                                             continue;
    264                                         }
    265                                     }
    266                                     // check & update map of what's occupied
    267                                     deleteOnItemOverlap.set(false);
    268                                     if (!checkItemPlacement(occupied, appWidgetInfo,
    269                                             deleteOnItemOverlap)) {
    270                                         if (deleteOnItemOverlap.get()) {
    271                                             itemsToRemove.add(id);
    272                                         }
    273                                         break;
    274                                     }
    275                                     String providerName = provider.provider.flattenToString();
    276                                     if (!providerName.equals(savedProvider)) {
    277                                         ContentValues values = new ContentValues();
    278                                         values.put(LauncherSettings.Favorites.APPWIDGET_PROVIDER,
    279                                                 providerName);
    280                                         String where = BaseColumns._ID + "= ?";
    281                                         String[] args = {Integer.toString(c.getInt(idIndex))};
    282                                         contentResolver.update(contentUri, values, where, args);
    283                                     }
    284                                     sBgItemsIdMap.put(appWidgetInfo.id, appWidgetInfo);
    285                                     sBgAppWidgets.add(appWidgetInfo);
    286                                 }
    287                                 break;
    288                             }
    289                         } catch (Exception e) {
    290                             Launcher.addDumpLog(TAG, "Desktop items loading interrupted: " + e, true);
    291                         }
    292                     }
    293                 } finally {
    294                     if (c != null) {
    295                         c.close();
    296                     }
    297                 }
    298 
    299                 // Break early if we've stopped loading
    300                 if (mStopped) {
    301                     clearSBgDataStructures();
    302                     return false;
    303                 }
    304 
    305                 if (itemsToRemove.size() > 0) {
    306                     ContentProviderClient client = contentResolver.acquireContentProviderClient(
    307                             LauncherSettings.Favorites.CONTENT_URI);
    308                     // Remove dead items
    309                     for (long id : itemsToRemove) {
    310                         if (DEBUG_LOADERS) {
    311                             Log.d(TAG, "Removed id = " + id);
    312                         }
    313                         // Don't notify content observers
    314                         try {
    315                             client.delete(LauncherSettings.Favorites.getContentUri(id, false),
    316                                     null, null);
    317                         } catch (RemoteException e) {
    318                             Log.w(TAG, "Could not remove id = " + id);
    319                         }
    320                     }
    321                 }
    322 
    323                 if (loadedOldDb) {
    324                     long maxScreenId = 0;
    325                     // If we're importing we use the old screen order.
    326                     for (ItemInfo item: sBgItemsIdMap.values()) {
    327                         long screenId = item.screenId;
    328                         if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
    329                                 !sBgWorkspaceScreens.contains(screenId)) {
    330                             sBgWorkspaceScreens.add(screenId);
    331                             if (screenId > maxScreenId) {
    332                                 maxScreenId = screenId;
    333                             }
    334                         }
    335                     }
    336                     Collections.sort(sBgWorkspaceScreens);
    337 
    338                     LauncherAppState.getLauncherProvider().updateMaxScreenId(maxScreenId);
    339                     updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
    340 
    341                     // Update the max item id after we load an old db
    342                     long maxItemId = 0;
    343                     // If we're importing we use the old screen order.
    344                     for (ItemInfo item: sBgItemsIdMap.values()) {
    345                         maxItemId = Math.max(maxItemId, item.id);
    346                     }
    347                     LauncherAppState.getLauncherProvider().updateMaxItemId(maxItemId);
    348                 } else {
    349                     TreeMap<Integer, Long> orderedScreens = loadWorkspaceScreensDb(mContext);
    350                     for (Integer i : orderedScreens.keySet()) {
    351                         sBgWorkspaceScreens.add(orderedScreens.get(i));
    352                     }
    353 
    354                     // Remove any empty screens
    355                     ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
    356                     for (ItemInfo item: sBgItemsIdMap.values()) {
    357                         long screenId = item.screenId;
    358                         if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
    359                                 unusedScreens.contains(screenId)) {
    360                             unusedScreens.remove(screenId);
    361                         }
    362                     }
    363 
    364                     // If there are any empty screens remove them, and update.
    365                     if (unusedScreens.size() != 0) {
    366                         sBgWorkspaceScreens.removeAll(unusedScreens);
    367                         updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
    368                     }
    369                 }
    370 
    371                 if (DEBUG_LOADERS) {
    372                     Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms");
    373                     Log.d(TAG, "workspace layout: ");
    374                     int nScreens = occupied.size();
    375                     for (int y = 0; y < countY; y++) {
    376                         String line = "";
    377 
    378                         Iterator<Long> iter = occupied.keySet().iterator();
    379                         while (iter.hasNext()) {
    380                             long screenId = iter.next();
    381                             if (screenId > 0) {
    382                                 line += " | ";
    383                             }
    384                             for (int x = 0; x < countX; x++) {
    385                                 line += ((occupied.get(screenId)[x][y] != null) ? "#" : ".");
    386                             }
    387                         }
    388                         Log.d(TAG, "[ " + line + " ]");
    389                     }
    390                 }
    391             }
    392             return loadedOldDb;
    393         }
    View Code

    6、workspace绑定数据

    Launcher的内容绑定分为五步:分别对应着startBinding()、bindItems()、bindFolders()、 bindAppWidgets()、

    finishBindingItems()的调用。下面针对bindWorkspace做个简单的流程分析。

      1    /**
      2          * Binds all loaded data to actual views on the main thread.
      3          */
      4         private void bindWorkspace(int synchronizeBindPage, final boolean isUpgradePath) {
      5             final long t = SystemClock.uptimeMillis();
      6             Runnable r;
      7 
      8             // Don't use these two variables in any of the callback runnables.
      9             // Otherwise we hold a reference to them.
     10             final Callbacks oldCallbacks = mCallbacks.get();
     11             if (oldCallbacks == null) {
     12                 // This launcher has exited and nobody bothered to tell us.  Just bail.
     13                 Log.w(TAG, "LoaderTask running with no launcher");
     14                 return;
     15             }
     16 
     17             final boolean isLoadingSynchronously = (synchronizeBindPage > -1);
     18             final int currentScreen = isLoadingSynchronously ? synchronizeBindPage :
     19                 oldCallbacks.getCurrentWorkspaceScreen();
     20 
     21             // Load all the items that are on the current page first (and in the process, unbind
     22             // all the existing workspace items before we call startBinding() below.
     23             unbindWorkspaceItemsOnMainThread();
     24             ArrayList<ItemInfo> workspaceItems = new ArrayList<ItemInfo>();
     25             ArrayList<LauncherAppWidgetInfo> appWidgets =
     26                     new ArrayList<LauncherAppWidgetInfo>();
     27             HashMap<Long, FolderInfo> folders = new HashMap<Long, FolderInfo>();
     28             HashMap<Long, ItemInfo> itemsIdMap = new HashMap<Long, ItemInfo>();
     29             ArrayList<Long> orderedScreenIds = new ArrayList<Long>();
     30             synchronized (sBgLock) {
     31                 workspaceItems.addAll(sBgWorkspaceItems);
     32                 appWidgets.addAll(sBgAppWidgets);
     33                 folders.putAll(sBgFolders);
     34                 itemsIdMap.putAll(sBgItemsIdMap);
     35                 orderedScreenIds.addAll(sBgWorkspaceScreens);
     36             }
     37 
     38             ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<ItemInfo>();
     39             ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<ItemInfo>();
     40             ArrayList<LauncherAppWidgetInfo> currentAppWidgets =
     41                     new ArrayList<LauncherAppWidgetInfo>();
     42             ArrayList<LauncherAppWidgetInfo> otherAppWidgets =
     43                     new ArrayList<LauncherAppWidgetInfo>();
     44             HashMap<Long, FolderInfo> currentFolders = new HashMap<Long, FolderInfo>();
     45             HashMap<Long, FolderInfo> otherFolders = new HashMap<Long, FolderInfo>();
     46 
     47             // Separate the items that are on the current screen, and all the other remaining items
     48             filterCurrentWorkspaceItems(currentScreen, workspaceItems, currentWorkspaceItems,
     49                     otherWorkspaceItems);
     50             filterCurrentAppWidgets(currentScreen, appWidgets, currentAppWidgets,
     51                     otherAppWidgets);
     52             filterCurrentFolders(currentScreen, itemsIdMap, folders, currentFolders,
     53                     otherFolders);
     54             sortWorkspaceItemsSpatially(currentWorkspaceItems);
     55             sortWorkspaceItemsSpatially(otherWorkspaceItems);
     56 
     57             // Tell the workspace that we're about to start binding items
     58             r = new Runnable() {
     59                 public void run() {
     60                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
     61                     if (callbacks != null) {
     62                         callbacks.startBinding();
     63                     }
     64                 }
     65             };
     66             runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
     67 
     68             bindWorkspaceScreens(oldCallbacks, orderedScreenIds);
     69 
     70             // Load items on the current page
     71             bindWorkspaceItems(oldCallbacks, currentWorkspaceItems, currentAppWidgets,
     72                     currentFolders, null);
     73             if (isLoadingSynchronously) {
     74                 r = new Runnable() {
     75                     public void run() {
     76                         Callbacks callbacks = tryGetCallbacks(oldCallbacks);
     77                         if (callbacks != null) {
     78                             callbacks.onPageBoundSynchronously(currentScreen);
     79                         }
     80                     }
     81                 };
     82                 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
     83             }
     84 
     85             // Load all the remaining pages (if we are loading synchronously, we want to defer this
     86             // work until after the first render)
     87             mDeferredBindRunnables.clear();
     88             bindWorkspaceItems(oldCallbacks, otherWorkspaceItems, otherAppWidgets, otherFolders,
     89                     (isLoadingSynchronously ? mDeferredBindRunnables : null));
     90 
     91             // Tell the workspace that we're done binding items
     92             r = new Runnable() {
     93                 public void run() {
     94                     Callbacks callbacks = tryGetCallbacks(oldCallbacks);
     95                     if (callbacks != null) {
     96                         callbacks.finishBindingItems(isUpgradePath);
     97                     }
     98 
     99                     // If we're profiling, ensure this is the last thing in the queue.
    100                     if (DEBUG_LOADERS) {
    101                         Log.d(TAG, "bound workspace in "
    102                             + (SystemClock.uptimeMillis()-t) + "ms");
    103                     }
    104 
    105                     mIsLoadingAndBindingWorkspace = false;
    106                 }
    107             };
    108             if (isLoadingSynchronously) {
    109                 mDeferredBindRunnables.add(r);
    110             } else {
    111                 runOnMainThread(r, MAIN_THREAD_BINDING_RUNNABLE);
    112             }
    113         }

     上面就是Launcher的workspace绑定数据的过程,跟加载数据过程很相似,也是区分3中类型的元素进行加载。

    下面我们总结一下,workspace的加载和绑定数据的过程。我们现在回头看,可以发现,其实workspace里面就是

    存放了3中数据ItemInfo、FolderInfo、LauncherAppWidgetInfo。分别对应我们的APP快捷方式、文件夹、Widget

    数据。其中FolderInfo、LauncherAppWidgetInfo都是继承了ItemInfo。数据加载过程,就是从Launcher的数据库

    读取数据然后按元素属性分别放到3个ArrayList里面。绑定数据过程就是把3个ArrayList的队列关联到Launcher界面里面。

     7、ALL APP数据加载绑定

     1  private void loadAllApps() {
     2             final long loadTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
     3 
     4             final Callbacks oldCallbacks = mCallbacks.get();
     5             if (oldCallbacks == null) {
     6                 // This launcher has exited and nobody bothered to tell us.  Just bail.
     7                 Log.w(TAG, "LoaderTask running with no launcher (loadAllApps)");
     8                 return;
     9             }
    10 
    11             final PackageManager packageManager = mContext.getPackageManager();
    12             final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
    13             mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
    14 
    15             // Clear the list of apps
    16             mBgAllAppsList.clear();
    17 
    18             // Query for the set of apps
    19             final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
    20             List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
    21             if (DEBUG_LOADERS) {
    22                 Log.d(TAG, "queryIntentActivities took "
    23                         + (SystemClock.uptimeMillis()-qiaTime) + "ms");
    24                 Log.d(TAG, "queryIntentActivities got " + apps.size() + " apps");
    25             }
    26             // Fail if we don't have any apps
    27             if (apps == null || apps.isEmpty()) {
    28                 return;
    29             }
    30             // Sort the applications by name
    31             final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
    32             Collections.sort(apps,
    33                     new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
    34             if (DEBUG_LOADERS) {
    35                 Log.d(TAG, "sort took "
    36                         + (SystemClock.uptimeMillis()-sortTime) + "ms");
    37             }
    38 
    39             // Create the ApplicationInfos
    40             for (int i = 0; i < apps.size(); i++) {
    41                 ResolveInfo app = apps.get(i);
    42                 // This builds the icon bitmaps.
    43                 mBgAllAppsList.add(new AppInfo(packageManager, app,
    44                         mIconCache, mLabelCache));
    45             }
    46 
    47             // Huh? Shouldn't this be inside the Runnable below?
    48             final ArrayList<AppInfo> added = mBgAllAppsList.added;
    49             mBgAllAppsList.added = new ArrayList<AppInfo>();
    50 
    51             // Post callback on main thread
    52             mHandler.post(new Runnable() {
    53                 public void run() {
    54                     final long bindTime = SystemClock.uptimeMillis();
    55                     final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
    56                     if (callbacks != null) {
    57                         callbacks.bindAllApplications(added);
    58                         if (DEBUG_LOADERS) {
    59                             Log.d(TAG, "bound " + added.size() + " apps in "
    60                                 + (SystemClock.uptimeMillis() - bindTime) + "ms");
    61                         }
    62                     } else {
    63                         Log.i(TAG, "not binding apps: no Launcher activity");
    64                     }
    65                 }
    66             });
    67 
    68             if (DEBUG_LOADERS) {
    69                 Log.d(TAG, "Icons processed in "
    70                         + (SystemClock.uptimeMillis() - loadTime) + "ms");
    71             }
    72         }
     // Post callback on main thread  很重要

    AppInfo由四部分组成

       List<ResolveInfo> apps = packageManager.queryIntentActivities(mainIntent, 0);
    // 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)); }

    AllAPP的数据加载和绑定跟workspace的差不多,也是先加载数据然后绑定数据,通知Launcher。加载数据的时候

    从PackageManager获取所有已经安装的APK包信息,然后过滤只包含需要显示在所有应用列表的应用,需要包含

    ACTION_MAIN和CATEGORY_LAUNCHER两个属性。这个我们在编写应用程序的时候都应该知道。

    AllAPP加载跟workspace不同的地方是加载的同时,完成数据绑定的操作,也就是说第一次加载AllAPP页面的数据,

    会同时绑定数据到Launcher。第二次需要加载的时候,只会把数据直接绑定到Launcher,而不会重新搜索加载数据。

    Launcher启动加载和绑定数据就是这样完成。绑定完数据,Launcher就可以运行。

    绑定数据,回调接口

     1    /**
     2      * Add the icons for all apps.
     3      *
     4      * Implementation of the method from LauncherModel.Callbacks.
     5      */
     6     public void bindAllApplications(final ArrayList<AppInfo> apps) {
     7         if (AppsCustomizePagedView.DISABLE_ALL_APPS) {
     8             if (mIntentsOnWorkspaceFromUpgradePath != null) {
     9                 if (LauncherModel.UPGRADE_USE_MORE_APPS_FOLDER) {
    10                     getHotseat().addAllAppsFolder(mIconCache, apps,
    11                             mIntentsOnWorkspaceFromUpgradePath, Launcher.this, mWorkspace);
    12                 }
    13                 mIntentsOnWorkspaceFromUpgradePath = null;
    14             }
    15         } else {
    16             if (mAppsCustomizeContent != null) {
    17                 mAppsCustomizeContent.setApps(apps);
    18             }
    19         }
    20     }

    8、ALL APP显示:

      public void syncAppsPageItems(int page, boolean immediate) {
            // ensure that we have the right number of items on the pages
            final boolean isRtl = isLayoutRtl();
            int numCells = mCellCountX * mCellCountY;
            int startIndex = page * numCells;
            int endIndex = Math.min(startIndex + numCells, mApps.size());
            AppsCustomizeCellLayout layout = (AppsCustomizeCellLayout) getPageAt(page);
    
            layout.removeAllViewsOnPage();
            ArrayList<Object> items = new ArrayList<Object>();
            ArrayList<Bitmap> images = new ArrayList<Bitmap>();
            for (int i = startIndex; i < endIndex; ++i) {
                AppInfo info = mApps.get(i);
                PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate(
                        R.layout.apps_customize_application, layout, false);
                icon.applyFromApplicationInfo(info, true, this);
                icon.setOnClickListener(this);
                icon.setOnLongClickListener(this);
                icon.setOnTouchListener(this);
                icon.setOnKeyListener(this);
    
                int index = i - startIndex;
                int x = index % mCellCountX;
                int y = index / mCellCountX;
                if (isRtl) {
                    x = mCellCountX - x - 1;
                }
                layout.addViewToCellLayout(icon, -1, i, new CellLayout.LayoutParams(x,y, 1,1), false);
    
                items.add(info);
                images.add(info.iconBitmap);
            }
    
            enableHwLayersOnVisiblePages();
        }
    1  public void applyFromApplicationInfo(AppInfo info, boolean scaleUp,
    2             PagedViewIcon.PressedCallback cb) {
    3         mIcon = info.iconBitmap;
    4         mPressedCallback = cb;
    5         setCompoundDrawables(null, Utilities.createIconDrawable(mIcon),
    6                 null, null);
    7         setText(info.title);
    8         setTag(info);
    9     }

    将图标和title加载到控件上

    启动应用程序:

     1  @Override
     2     public void onClick(View v) {
     3         // When we have exited all apps or are in transition, disregard clicks
     4         if (!mLauncher.isAllAppsVisible() ||
     5                 mLauncher.getWorkspace().isSwitchingState()) return;
     6 
     7         if (v instanceof PagedViewIcon) {
     8             // Animate some feedback to the click
     9             final AppInfo appInfo = (AppInfo) v.getTag();
    10 
    11             // Lock the drawable state to pressed until we return to Launcher
    12             if (mPressedIcon != null) {
    13                 mPressedIcon.lockDrawableState();
    14             }
    15             mLauncher.startActivitySafely(v, appInfo.intent, appInfo);
    16             mLauncher.getStats().recordLaunch(appInfo.intent);
    17         } else if (v instanceof PagedViewWidget) {
    18             // Let the user know that they have to long press to add a widget
    19             if (mWidgetInstructionToast != null) {
    20                 mWidgetInstructionToast.cancel();
    21             }
    22             mWidgetInstructionToast = Toast.makeText(getContext(),R.string.long_press_widget_to_add,
    23                 Toast.LENGTH_SHORT);
    24             mWidgetInstructionToast.show();
    25 
    26             // Create a little animation to show that the widget can move
    27             float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
    28             final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
    29             AnimatorSet bounce = LauncherAnimUtils.createAnimatorSet();
    30             ValueAnimator tyuAnim = LauncherAnimUtils.ofFloat(p, "translationY", offsetY);
    31             tyuAnim.setDuration(125);
    32             ValueAnimator tydAnim = LauncherAnimUtils.ofFloat(p, "translationY", 0f);
    33             tydAnim.setDuration(100);
    34             bounce.play(tyuAnim).before(tydAnim);
    35             bounce.setInterpolator(new AccelerateInterpolator());
    36             bounce.start();
    37         }
    38     }

    卸载程序后,如何更新页面,探究:

     1     protected void invalidatePageData(int currentPage, boolean immediateAndOnly) {
     2         if (!mIsDataReady) {
     3             return;
     4         }
     5 
     6         if (mContentIsRefreshable) {
     7             // Force all scrolling-related behavior to end
     8             mScroller.forceFinished(true);
     9             mNextPage = INVALID_PAGE;
    10 
    11             // Update all the pages
    12             syncPages();
    13 
    14             // We must force a measure after we've loaded the pages to update the content width and
    15             // to determine the full scroll width
    16             measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
    17                     MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
    18 
    19             // Set a new page as the current page if necessary
    20             if (currentPage > -1) {
    21                 setCurrentPage(Math.min(getPageCount() - 1, currentPage));
    22             }
    23 
    24             // Mark each of the pages as dirty
    25             final int count = getChildCount();
    26             mDirtyPageContent.clear();
    27             for (int i = 0; i < count; ++i) {
    28                 mDirtyPageContent.add(true);
    29             }
    30 
    31             // Load any pages that are necessary for the current window of views
    32             loadAssociatedPages(mCurrentPage, immediateAndOnly);
    33             requestLayout();
    34         }
    35         if (isPageMoving()) {
    36             // If the page is moving, then snap it to the final position to ensure we don't get
    37             // stuck between pages
    38             snapToDestination();
    39         }
    40     }
    查询loadAssociatedPages

     安装程序后桌面出错:

  • 相关阅读:
    常见的行元素与块元素
    [转]SVN服务器部署并实现双机同步及禁止普通用户删除文件
    [转]Axure共享工程Shared Project(二):编辑修改和提交
    如何添加网络打印机
    [转]JSON 转换异常 死循环 There is a cycle in the hierarchy
    比较常用的Properties配置文件的使用方法示例
    解决Tomcat项目重复加载导致pemgen space内存溢出
    怎样批量删除.svn文件
    [转]前端工程师必须掌握的知识点
    Freemarker 使用
  • 原文地址:https://www.cnblogs.com/hixin/p/4382656.html
Copyright © 2011-2022 走看看