想要很透彻的看懂launcher2的所有代码还是需要花费不是时间的,起码目前来说只能是有什么需求才会看需求相关的那部分代码了。
网上很多资料了,给桌面的快捷方式图标都加入统一的背景图使其风格统一,根据个人的欣赏水平区分可能有好有坏吧。
直接看代码了。
首先需要看的代码肯定是Launcher.java了,习惯性的先看onCreate了
看代码过程不表,直入主题看与本次内容有关的,onCreate中的这几行代码
if (!mRestoring) { if (sPausedFromUserAction) { // If the user leaves launcher, then we should just load items asynchronously when // they return. mModel.startLoader(true, -1); } else { // We only load the page synchronously if the user rotates (or triggers a // configuration change) while launcher is in the foreground mModel.startLoader(true, mWorkspace.getCurrentPage()); } }继续看,mModel是LauncherModel的对象,自然接下来看LauncherModel.java了,实际上面的函数可以看出来是一个加载相关的函数,实际就是加载整个桌面的过程。
看下完整的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, isLaunching); if (synchronousBindPage > -1 && mAllAppsLoaded && mWorkspaceLoaded) { mLoaderTask.runBindSynchronousPage(synchronousBindPage); } else { sWorkerThread.setPriority(Thread.NORM_PRIORITY); sWorker.post(mLoaderTask); } } } }这段代码中需要注意的只是一个异步加载的操作接口LoadTask,继续看LoadTask方法的核心代码
private void loadAndBindWorkspace() { mIsLoadingAndBindingWorkspace = true; // Load the workspace if (DEBUG_LOADERS) { Log.d(TAG, "loadAndBindWorkspace mWorkspaceLoaded=" + mWorkspaceLoaded); } if (!mWorkspaceLoaded) { loadWorkspace(); synchronized (LoaderTask.this) { if (mStopped) { return; } mWorkspaceLoaded = true; } } // Bind the workspace bindWorkspace(-1); }上面的代码部分是其中的一个函数,通过名称就可以知道这是一个加载和绑定workspace的函数。
继续看其中的loadWorkspace函数
因为这个函数代码较多暂时截取部分有用的贴出来
............ ShortcutInfo info; ........... switch (itemType) { case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: intentDescription = c.getString(intentIndex); try { intent = Intent.parseUri(intentDescription, 0); } catch (URISyntaxException e) { continue; } if (itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { info = getShortcutInfo(manager, intent, context, c, iconIndex, titleIndex, mLabelCache); } else { info = getShortcutInfo(c, context, iconTypeIndex, iconPackageIndex, iconResourceIndex, iconIndex, titleIndex); .........关注下ShortcutInfo这个类,这个类就是定义了一个快捷方式图标所包含的所有信息,里面就包含了快捷方式的图标信息。重点关注getShortcutInfo这个函数了
....... if (resolveInfo != null) { icon = mIconCache.getIcon(componentName, resolveInfo, labelCache); } // the db if (icon == null) { if (c != null) { icon = getIconFromCursor(c, iconIndex, context); } } // the fallback icon if (icon == null) { icon = getFallbackIcon(); info.usingFallbackIcon = true; } info.setIcon(icon);本次修改的重点就出现了,可以看到图标icon的获取方式已经出现了,可以看到方法中会有三种具体的方式来获取。分别为mIconCache.getIcon()和getIconFromCursor(),getFallbackIcon()
分别来看这两个函数,首先是getIcon这个函数属于IconCache.java,直接看函数代码
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; } } ......... public Bitmap getIcon(Intent intent) { synchronized (mCache) { final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, 0); ComponentName component = intent.getComponent(); if (resolveInfo == null || component == null) { return mDefaultIcon; } CacheEntry entry = cacheLocked(component, resolveInfo, null); return entry.icon; } }再接着看getIconFromCursor()函数,稍后再做解析
Bitmap getIconFromCursor(Cursor c, int iconIndex, Context context) { @SuppressWarnings("all") // suppress dead code warning final boolean debug = false; if (debug) { Log.d(TAG, "getIconFromCursor app=" + c.getString(c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE))); } byte[] data = c.getBlob(iconIndex); try { return Utilities.createIconBitmap( BitmapFactory.decodeByteArray(data, 0, data.length), context); } catch (Exception e) { return null; } }从上面两个函数的返回类型可以知道都是返回了Bitmap,那么可以肯定的是这时候返回的Bitmap就是我们后面在workspace所显示的应用的图标了。
那么也可以进一步去分析这两个函数,他们的有效返回值也依然是函数分别是cacheLocked()和Utilities.createIconBitmap()。那么我们接下来就可以确定我们所得到的应用图标都是在上述这两个函数内完成的,那么接下来继续看这两个函数。
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; }此函数就是应用图标从缓存提取并且作为最终显示的图标的过程,那么我们自然可以在这里完成对图标的背景添加,然后返回这个带了背景的应用图标。
具体的做法如下,以修改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; } //add icon background Bitmap background = null; Bitmap temp = Utilities.createIconBitmap(getFullResIcon(info), mContext); entry.icon = createCompoundBitmap(background,temp); /* entry.icon = Utilities.createIconBitmap( getFullResIcon(info), mContext);*/ } return entry; } public Bitmap createCompoundBitmap(Bitmap bg, Bitmap icon) { int bgWidth = bg.getWidth(); int bgHeight = bg.getHeight(); icon = scaleBitmap2(icon,0.8f,0.8f); final int iconWidth = icon.getWidth(); final int iconHeight = icon.getHeight(); Bitmap compoundBitmap = Bitmap.createBitmap(bgWidth, bgHeight, Config.ARGB_8888); Canvas canvas = new Canvas(compoundBitmap); canvas.drawBitmap(bg, 0, 0, null); canvas.drawBitmap(icon, (bgWidth - iconWidth) / 2, (bgHeight - iconHeight) / 2, null); canvas.save(Canvas.ALL_SAVE_FLAG); canvas.restore(); return compoundBitmap; } static Bitmap scaleBitmap2(Bitmap bm, float sx, float sy) { if (sx == 1.0f && sy == 1.0f) { return bm; } Matrix matrix = new Matrix(); matrix.postScale(sx, sy); return Bitmap.createBitmap(bm, 0, 0, bm.getWidth(), bm.getHeight(), matrix, true); }上述的代码就可以实现统一给所有图标设置背景,当然也可以给所有图标随即设置一张背景图,无非就是增加一个取随即数然后通过随机数来判断用那张背景图,此处不做展示了。因为很容易实现效果,截图就暂且不发了,只要按代码修改就可以看到效果。
另一种方法就是在Utilities.java中修改createIconBitmap(Drawable icon, Context context)这个函数。
做法也是道理相同,主要是给图标增加背景同时返回重新合成的Bitmap来显示。在这个函数里面修改简单来说就是将刚才的修改另外分开的两个函数进行整合全部写入这个函数中,代码就不贴了。
最后简单来说下这两个函数的区别,个人见解就是mIconCache.getIcon这个获取icon的函数在getShortcutInfo这个函数中是第一个用来获取应用信息存在的情况下应用程序图标的方法,那么可以从名字猜测应该是一个提取缓存中已经存在的icon图标,而另一个getIconFromCursor这个函数则是从已保存的数据库中进行检索来直接按应用程序去提取图标。两种方法达到的目的是一样的,无非是考虑更为周全的做法而已,对于我们需要实现统一背景的要求来说基本区别不大,两种做法应该都可。我的做法暂时是在IconCache做得,因为并未发现异常于是也就没再继续做尝试。需要研究的同学可以继续研究,今天需要写的内容就这些了。记录下便于以后调试。