决定来写博客了,最近刚开始学习android源码,研究了一段时间的Launcher,现在把我这段时间解决的几个问题在这里记录一下,也算是一个总结,废话少说,开始了。

首先我研究的源码是android4.4.2版本的Launcher2,android版本可以从编译后的out/target/product/项目名/system/下的build.prop文件中查看ro.build.version.release=4.4.2。

Launcher的结构体系非常庞大,刚开始学习可以用\android-sdk-windows\tools下的hierarchyviewer.bat工具进行查看层级结构图,

我们知道Launcher启动的那个界面是桌面,中间显示应用的界面是workspace,里面有好几个cellLayout,最下面是一排hotseat,hotseat的中间是MainMenu按钮,点击后可以进入到菜单界面,这个显示applications和widgets的界面是AppsCustomizePagedView.

手机开机以后,launcher就启动了,Launcher.java是Launcher的启动类,它是一个activity,在他的onCreate()方法中有setContentView(R.Layout.Launcher)。在onCreate()方法中的第一步就是获取LauncherApplication对象。在LauncherApplication的onCreate()方法中新建了一个IconCache对象,mIconCache=new IconCache(this),紧接着new了一个LauncherModel对象mModel=LauncherModel(this.mIconCache)。LaucnherModel是一个与数据库相关的类,他有一个继承Runnable的内部类LoadTask,Launcher启动从数据库中读取数据并把图标和小工具添加上去的时候用的就是他。Launcher.java的onCreate()和onResume()方法中都有调用LauncherModel的startLoader()方法,这个方法里有一句代码sWorker.post(mLoaderTask),sWorker是一个handler对象,mLoaderTask是一个LoaderTask,前面说过,LoaderTask是继承Runnable,这个方法启动后会执行LoaderTask的run()方法,进入到run方法中可以看到,他里面调用了LoadTask的loadAndBindAllApps()方法,进入到loadAndBindAllApps()方法中看到又调用了LoadTask的loadAndBindAllApplist()方法,进入到loadAndBindAllApplist方法中看到又调用了LoadTask的loadAllApps()方法,这个方法是用来返回ApplicationInfo类型的集合的,在这个方法中我们看到了一句代码:mIconCache.getTitleAndIcon(info,resolveInfo,mLabelCache)!!!!!!

注意,关键点来了,getTitleAndIcon这个方法是IconCache.java类的一个方法,这个方法是用来加载应用图标和标题的。这个方法里为了得到CacheEntry(包含title和icon)对象调用了自身的cacheLocked方法,代码如下:

CacheEntry entry=cacheLocked(application.componentName,info,labelCache);

cacheLocked中获得icon的代码如下:

retVal.icon=DataUtil.getInstance().createIconBitmap(getFullResIcon(info),mContext);

retVal是一个CacheEntry

getFullResIcon(info)得到的是每个应用对应的icon。DataUtil.getInstance()得到的并不是DataUtil的实例,他调用Class.forname("com.android.Launcher2.LauncherDataUtil")创建的是

LauncherDataUtil的实例,LauncherDataUtil是DataUtil的子类,然后再调用LauncherDataUtil

的createIconBitmap方法,此方法中调用的是Utilities的createIconBitmap方法。

说了这么多终于要开始写代码了,Utilities的createIconBitmap方法是用来创建一个icon的,原始及添加代码如下


static Bitmap createIconBitmap(Drawable icon, Context context) {
         synchronized (sCanvas) { // we share the statics :-(
             if (sIconWidth == -1) {
             //此方法是通过xml文件的值初始化一些尺寸,是iconCache的一个方法
                 initStatics(context);
             }
//给width和height赋值
             int width = sIconWidth;
             int height = sIconHeight;


             if (icon instanceof PaintDrawable) {
                 PaintDrawable painter = (PaintDrawable) icon;
                 painter.setIntrinsicWidth(width);
                 painter.setIntrinsicHeight(height);
             } else if (icon instanceof BitmapDrawable) {
                 // Ensure the bitmap has a density.
                 BitmapDrawable bitmapDrawable = (BitmapDrawable) icon;
                 Bitmap bitmap = bitmapDrawable.getBitmap();
                 if (bitmap.getDensity() == Bitmap.DENSITY_NONE) {
                     bitmapDrawable.setTargetDensity(context.getResources().getDisplayMetrics());
                 }
             }
             //获取本来icon的尺寸
             int sourceWidth = icon.getIntrinsicWidth();
             int sourceHeight = icon.getIntrinsicHeight();
             if (sourceWidth > 0 && sourceHeight > 0) {
                 // There are intrinsic sizes.
                 //将xml文件配置的尺寸和应用icon本来尺寸进行对比,再缩放
                 //打印Log发现并没有走这里的if和if else,只有在加载最后一个应用图标是走了else if,不知道为什么
                 if (width < sourceWidth || height < sourceHeight) {
                     // It's too big, scale it down.
                     final float ratio = (float) sourceWidth / sourceHeight;
                     if (sourceWidth > sourceHeight) {
                         height = (int) (width / ratio);
                     } else if (sourceHeight > sourceWidth) {
                         width = (int) (height * ratio);
                     }
                 } else if (sourceWidth < width && sourceHeight < height) {
                     // Don't scale up the icon
                     width = sourceWidth;
                     height = sourceHeight;
                 }
             }


             // no intrinsic size --> use default size
             //sIconTextureWidth是等于sIconWidth的,将值赋给textureWidth
             int textureWidth = sIconTextureWidth;
             int textureHeight = sIconTextureHeight;
//创建一个空的bitmap,这个bitmap是用来放背景图片的bitmap的
             final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
                     Bitmap.Config.ARGB_8888);
             final Canvas canvas = sCanvas;
             //在画布上传入空的bitmap
             canvas.setBitmap(bitmap);


             final int left = (textureWidth-width) / 2;
             final int top = (textureHeight-height) / 2;


             @SuppressWarnings("all") // suppress dead code warning
             final boolean debug = false;
             if (debug) {
                 // draw a big box for the icon for debugging
                 canvas.drawColor(sColors[sColorIndex]);
                 if (++sColorIndex >= sColors.length) sColorIndex = 0;
                 Paint debugPaint = new Paint();
                 debugPaint.setColor(0xffcccc00);
                 canvas.drawRect(left, top, left+width, top+height, debugPaint);
             }
             ///这里是添加的代码 add begin
             if(true){
             //从drawable下得到背景图片的bitmap
             Bitmap backBitmap=BitmapFactory.decodeResource(context.getResource(),R.draw.icon_background);
             //得到背景图片的长宽
             int backWidth=backBitmap.getWidth();
             int backHeight=backBitmap.getHeight();
             //如果背景图片的长宽和icon的不相等
             if(backWidth!sIconWidth||backHeight!=sIconHeight){
             //new一个矩阵对象
             Matrix matrix=new Matrix();
             //设置缩放倍数
             matrix.postScale((float)sIconWidth/backWidth,(float)sIconHeight/backHeight);
             //将背景图片画到画布上
             canvas.drawBitmap(Bitmap.createBitmap(backBitmap,0,0,backWidth,backHeight,matrix,true),0.0f,0.0f,null);
             }else{
             //如果相等,直接画
             canvas.drawBitmap(backBitmap,0.0f,0.0f,null);
             }              
             }
             ///结束添加 add end
                         
//sOldBounds是一个Rect对象,设置边界值,这个主要是为了保存
             sOldBounds.set(icon.getBounds());
             
             //这一步很重要,设置drawable的边界矩阵的左上右下坐标
             //打印发现这里的left和top都为0,说明这里的坐标只是相对于当前icon所在的view的坐标,改动如下    //modify begin
             //icon.setBounds(left, top, left+width, top+height);     icon.setBounds((int)(left+texttureWidth*0.2),(int)(top+texttureWidth*0.2),(int)((left+width)*0.8),(int)((top+height)*0.8)); 
    //modify end
             //将drawable画到画布上
             icon.draw(canvas);
              
             //还原边界值
             icon.setBounds(sOldBounds);
             //将画布置空
             canvas.setBitmap(null);
//最终返回的是之前新建的被画了背景图片的bitmap
             return bitmap;
         }
     }

以上的注释都是我自己加的,经过修改代码,最终实现了想要的效果