android Launcher 图标 加载/点击/拖动 的过程 1

    应朋友要求,把Launcher应用再详细解说一下。

    首先,我们需要去LauncherApplication里面看一下,因为这里没有两个成员变量对我们这一讲非常重要,它们就是

    public LauncherModel mModel;
    public IconCache mIconCache;


在LauncherApplication的onCreate()创建

        mIconCache = new IconCache(this);
        mModel = new LauncherModel(this, mIconCache);

IconCache很明显是应用Icon缓存,这个一会我们会讲到,LauncherModel是BroadcastReceiver,用来接受应用添加、删除、变动等等的广播,来做响应的操作。我们去LauncherModel的构造函数中看看

    LauncherModel(LauncherApplication app, IconCache iconCache) {
    
    
        mAppsCanBeOnExternalStorage = !Environment.isExternalStorageEmulated();
        mApp = app;
        mAllAppsList = new AllAppsList(iconCache);
        mIconCache = iconCache;
 
        mDefaultIcon = Utilities.createIconBitmap(
                mIconCache.getFullResDefaultActivityIcon(), app);
 
        final Resources res = app.getResources();
        mAllAppsLoadDelay = res.getInteger(R.integer.config_allAppsBatchLoadDelay);
        mBatchSize = res.getInteger(R.integer.config_allAppsBatchSize);
        Configuration config = res.getConfiguration();
        mPreviousConfigMcc = config.mcc;
    }


这里的mAllAppsList也就是AllAppsList,是用来存储我们应用信息的,十分重要,我们Launcher的一半操作是围绕这它进行的,在AllAppsList类中的重要变量

    /** The list off all apps. */
    public ArrayList<ApplicationInfo> data =
            new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
    /** The list of apps that have been added since the last notify() call. */
    public ArrayList<ApplicationInfo> added =
            new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
    /** The list of apps that have been removed since the last notify() call. */
    public ArrayList<ApplicationInfo> removed = new ArrayList<ApplicationInfo>();
    /** The list of apps that have been modified since the last notify() call. */
    public ArrayList<ApplicationInfo> modified = new ArrayList<ApplicationInfo>();
 
    private IconCache mIconCache;


从名字就可以知道这些是用来存储什么的,这里不再一一解释,我们看看ApplicationInfo里面都存储了应用的什么信息

    /**
     * The application name.
     */
    CharSequence title;
 
    /**
     * The intent used to start the application.
     */
    Intent intent;
 
    /**
     * A bitmap version of the application icon.
     */
    Bitmap iconBitmap;
 
    /**
     * The time at which the app was first installed.
     */
    long firstInstallTime;
 
    ComponentName componentName;


名字title、启动的Intent、iconBitmap、第一次安装时间firstInstallTime、ComponentName等等,具体这些干什么的,不用我一一解释了吧。

    好了,LauncherApplication我们就看到这里,然后我们进入Launcher中,Launcher是一个Activity,所以我们就先看它的onCreate函数,我们只截取我们关心的代码

        LauncherApplication app = ((LauncherApplication)getApplication());
        mModel = app.setLauncher(this);
 
        mIconCache = app.getIconCache();
        mDragController = new DragController(this);


这里获取我们刚才创建的LauncherApplication,然后获取到刚才创建的LauncherModel并把我们的Launcher传进去,接着获取到我们刚才创建的IconCache,最后就是创建DragController,这是我们拖动图标时用到的,后面会讲解到。由于Widget的管理和app差不多,这里就不再讲解只讲app。接着

        if (!mRestoring) {
    
    
            mModel.startLoader(this, true);
        }


去加载我们的应用

    public void startLoader(Context context, boolean isLaunching) {
    
    
        synchronized (mLock) {
    
    
            if (DEBUG_LOADERS) {
    
    
                Log.d(TAG, "startLoader isLaunching=" + isLaunching);
            }
 
            // 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(context, isLaunching);
                sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                sWorker.post(mLoaderTask);
            }
        }
    }


启动了一个线程LoaderTask来加载我们的应用,我们直接去LoaderTask的run()方法中查看

            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 (loadWorkspaceFirst) {
    
    
                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                    loadAndBindWorkspace();
                } else {
    
    
                    if (DEBUG_LOADERS) Log.d(TAG, "step 1: special: loading all apps");
                    loadAndBindAllApps();
                }
 
                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 (loadWorkspaceFirst) {
    
    
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                    loadAndBindAllApps();
                } else {
    
    
                    if (DEBUG_LOADERS) Log.d(TAG, "step 2: special: loading workspace");
                    loadAndBindWorkspace();
                }
 
                // Restore the default thread priority after we are done loading items
                synchronized (mLock) {
    
    
                    android.os.Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                }
            }


这里就决定了是先加载Apps还是先加载Workspace,这只我们只讲解Apps,也就是loadAndBindAllApps()

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


loadAllAppsFromPersistence()是从数据库加载应用,由于代码不同,可能有的没有这一过程,loadAllAppsByBatch()解析应用来加载应用,onlyBindAllApps()跳过了更新应用列表的过程,是loadAllAppsByBatch()的部分代码,不再讲解,这里我们只讲解loadAllAppsByBatch()

        private void loadAllAppsByBatch() {
    
    
            final long t = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
 
            // Don't use these two variables in any of the callback runnables.
            // Otherwise we hold a reference to them.
            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 (loadAllAppsByBatch)");
                return;
            }
 
            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
 
            final PackageManager packageManager = mContext.getPackageManager();
            List<ResolveInfo> apps = null;
 
            int N = Integer.MAX_VALUE;
 
            int startIndex;
            int i=0;
            int batchSize = -1;
            while (i < N && !mStopped) {
    
    
                if (i == 0) {
    
    
                    mAllAppsList.clear();
                    final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    apps = packageManager.queryIntentActivities(mainIntent, 0);
//                    for (ResolveInfo resolveInfo : apps) {
    
    
//                    	ActivityInfo activityInfo = resolveInfo.activityInfo;
//                    	if(activityInfo == null){
    
    
//                        	Log.v("Launcher", "loadAllAppsByBatch resolvePackageName:" + resolveInfo.resolvePackageName);
//                    	}else{
    
    
//                        	Log.v("Launcher", "loadAllAppsByBatch activityInfo:" + activityInfo.loadLabel(packageManager).toString());
//                    	}
//            		}
                    if (DEBUG_LOADERS) {
    
    
                        Log.d(TAG, "queryIntentActivities took "
                                + (SystemClock.uptimeMillis()-qiaTime) + "ms");
                    }
                    if (apps == null) {
    
    
                        return;
                    }
                    N = apps.size();
                    if (DEBUG_LOADERS) {
    
    
                        Log.d(TAG, "queryIntentActivities got " + N + " apps");
                    }
                    if (N == 0) {
    
    
                        // There are no apps?!?
                        return;
                    }
                    if (mBatchSize == 0) {
    
    
                        batchSize = N;
                    } else {
    
    
                        batchSize = mBatchSize;
                    }
 
                    final long sortTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    Collections.sort(apps,
                            new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
                    if (DEBUG_LOADERS) {
    
    
                        Log.d(TAG, "sort took "
                                + (SystemClock.uptimeMillis()-sortTime) + "ms");
                    }
                }
 
                final long t2 = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
 
                startIndex = i;
                for (int j=0; i<N && j<batchSize; j++) {
    
    
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }
 
                final boolean first = i <= batchSize;
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                final ArrayList<ApplicationInfo> added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList<ApplicationInfo>();
 
                mHandler.post(new Runnable() {
    
    
                    public void run() {
    
    
                        final long t = SystemClock.uptimeMillis();
                        if (callbacks != null) {
    
    
                        	isNeedSave = true;
                            if (first) {
    
    
                                callbacks.bindAllApplications(added);
                            } else {
    
    
                                callbacks.bindAppsAdded(added);
                            }
                            if (DEBUG_LOADERS) {
    
    
                                Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - t) + "ms");
                            }
                        } else {
    
    
                            Log.i(TAG, "not binding apps: no Launcher activity");
                        }
                    }
                });
 
                if (DEBUG_LOADERS) {
    
    
                    Log.d(TAG, "batch of " + (i-startIndex) + " icons processed in "
                            + (SystemClock.uptimeMillis()-t2) + "ms");
                }
 
                if (mAllAppsLoadDelay > 0 && i < N) {
    
    
                    try {
    
    
                        if (DEBUG_LOADERS) {
    
    
                            Log.d(TAG, "sleeping for " + mAllAppsLoadDelay + "ms");
                        }
                        Thread.sleep(mAllAppsLoadDelay);
                    } catch (InterruptedException exc) {
    
     }
                }
            }
 
            if (DEBUG_LOADERS) {
    
    
                Log.d(TAG, "cached all " + N + " apps in "
                        + (SystemClock.uptimeMillis()-t) + "ms"
                        + (mAllAppsLoadDelay > 0 ? " (including delay)" : ""));
            }
        }


首先获取Action为Intent.ACTION_MAIN,Category为Intent.CATEGORY_LAUNCHER的所有Apps

            final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
            mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
 
            final PackageManager packageManager = mContext.getPackageManager();
            List<ResolveInfo> apps = null;

                    mAllAppsList.clear();
                    final long qiaTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;
                    apps = packageManager.queryIntentActivities(mainIntent, 0);


并且把我们mAllAppsList清空,这个mAllAppsList我们前面说过,这里不再赘述,然后

                for (int j=0; i<N && j<batchSize; j++) {
    
    
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }


去加载我们的应用,ApplicationInfo之前我们也说过,但是前面只看了它存储了我们应用的什么信息,这里我们去看看它的构造函数

    public ApplicationInfo(PackageManager pm, ResolveInfo info, IconCache iconCache,
            HashMap<Object, CharSequence> labelCache) {
    
    
        final String packageName = info.activityInfo.applicationInfo.packageName;
 
        this.componentName = new ComponentName(packageName, info.activityInfo.name);
        this.container = ItemInfo.NO_ID;
        this.setActivity(componentName,
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
 
        try {
    
    
            int appFlags = pm.getApplicationInfo(packageName, 0).flags;
            if ((appFlags & android.content.pm.ApplicationInfo.FLAG_SYSTEM) == 0) {
    
    
                flags |= DOWNLOADED_FLAG;
 
                if ((appFlags & android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
    
    
                    flags |= UPDATED_SYSTEM_APP_FLAG;
                }
            }
            firstInstallTime = pm.getPackageInfo(packageName, 0).firstInstallTime;
        } catch (NameNotFoundException e) {
    
    
            Log.d(TAG, "PackageManager.getApplicationInfo failed for " + packageName);
        }
 
        iconCache.getTitleAndIcon(this, info, labelCache);
    }


首先,给ApplicationInfo中的componentName和container变量赋值,然后

        this.setActivity(componentName,
                Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);


也就是

    final void setActivity(ComponentName className, int launchFlags) {
    
    
        intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_LAUNCHER);
        intent.setComponent(className);
        intent.setFlags(launchFlags);
        itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_APPLICATION;
    }


给ApplicationInfo的intent赋值,这里我们可以看出通过intent就可以启动我们相对应的应用了。接着就是给ApplicationInfo的flags和firstInstallTime赋值,这些都不再详细解说,我们详细看看iconCache.getTitleAndIcon(this, info, labelCache)也就是

    public void getTitleAndIcon(ApplicationInfo application, ResolveInfo info,
            HashMap<Object, CharSequence> labelCache) {
    
    
        synchronized (mCache) {
    
    
            CacheEntry entry = cacheLocked(application.componentName, info, labelCache);
 
            application.title = entry.title;
            application.iconBitmap = createBitmap(application.componentName, entry.icon,
                    application);
        }
    }


CacheEntry也就是

    private static class CacheEntry {
    
    
        public Bitmap icon;
        public String title;
    }


只保存了应用的图标和名字。cacheLocked(application.componentName, info, labelCache)也就是

    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;
    }


首先从我们的缓存中获取,如果有就直接返回,如果没有就去获取。获取title也一样,先从缓存中获取,如果有就使用,如果没有就从应用的信息中获取,这里我们可以更改应用在Launcher中显示的名字,这些都容易理解,不做过多解释。接下来就是Icon的获取

            entry.icon = Utilities.createIconBitmap(
                    getFullResIcon(info), mContext);


我们看看getFullResIcon也就是

    public Drawable getFullResIcon(ResolveInfo info) {
    
    
        Resources resources;
        try {
    
    
            resources = mPackageManager.getResourcesForApplication(
                    info.activityInfo.applicationInfo);
        } catch (PackageManager.NameNotFoundException e) {
    
    
            resources = null;
        }
        if (resources != null) {
    
    
            int iconId = info.activityInfo.getIconResource();
            if (iconId != 0) {
    
    
                return getFullResIcon(resources, iconId);
            }
        }
        return getFullResDefaultActivityIcon();
    }


首先获取到我们加载应用的资源信息

            resources = mPackageManager.getResourcesForApplication(
                    info.activityInfo.applicationInfo);

如果获取资源不成功就返回getFullResDefaultActivityIcon(),如果获取资源成功

        if (resources != null) {
    
    
            int iconId = info.activityInfo.getIconResource();
            if (iconId != 0) {
    
    
                return getFullResIcon(resources, iconId);
            }
        }


就得到相应应用图标的ID,然后返回getFullResIcon也就是

    public Drawable getFullResIcon(Resources resources, int iconId) {
    
    
        Drawable d;
        try {
    
    
            d = resources.getDrawableForDensity(iconId, mIconDpi);
        } catch (Resources.NotFoundException e) {
    
    
            d = null;
        }
 
        return (d != null) ? d : getFullResDefaultActivityIcon();
    }


通过图标的ID获取到图片Drawable返回,如果获取图片不成功同样返回getFullResDefaultActivityIcon(),也就是

    public Drawable getFullResDefaultActivityIcon() {
    
    
        return getFullResIcon(Resources.getSystem(),
                com.android.internal.R.mipmap.sym_def_app_icon);
    }


这个资源是我们framework下面的一张图片,也就是我们经常见的那个android小人人,可能不同代码这个图片有改动。如果想要把Launcher的图标改成我们想要的就可在这部分动手脚了,例如我们获取到一个我们准备好的图片,然后返回就可以了,这些不再多讲。现在回到cacheLocked中,还看

            entry.icon = Utilities.createIconBitmap(
                    getFullResIcon(info), mContext);


这句话,刚才我们分析了getFullResIcon(info)返回一个Drawable,现在我们看看Utilities.createIconBitmap,Utilities.createIconBitmap是一个重构函数,一个传进去Bitmap,一个传进去Drawable,我们看传进去Drawable的函数

    static Bitmap createIconBitmap(Drawable icon, Context context) {
    
    
        synchronized (sCanvas) {
    
     // we share the statics :-(
            if (sIconWidth == -1) {
    
    
                initStatics(context);
            }
 
            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());
                }
            }
            int sourceWidth = icon.getIntrinsicWidth();
            int sourceHeight = icon.getIntrinsicHeight();
 
            if (sourceWidth > 0 && sourceHeight > 0) {
    
    
                // There are intrinsic sizes.
                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
            int textureWidth = sIconTextureWidth;
            int textureHeight = sIconTextureHeight;
 
            final Bitmap bitmap = Bitmap.createBitmap(textureWidth, textureHeight,
                    Bitmap.Config.ARGB_8888);
            final Canvas canvas = sCanvas;
            canvas.setBitmap(bitmap);
 
            final int left = (textureWidth-width) / 2;
            final int top = (textureHeight-height) / 2;
 
            if (false) {
    
    
                // 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);
            }
 
            sOldBounds.set(icon.getBounds());
            icon.setBounds(left, top, left+width, top+height);
            icon.draw(canvas);
            icon.setBounds(sOldBounds);
            canvas.setBitmap(null);
 
            return bitmap;
        }
    }


很明显在这里对图片大小等做了修整吧,如果要控制Launcher图标显示就在这里做手脚吧,如可以添加一个背景图片什么的,或显示大小什么的,不再多说。然后我们回到getTitleAndIcon,接着看

            application.title = entry.title;
            application.iconBitmap = createBitmap(application.componentName, entry.icon,
                    application);


就是给我们的ApplicationInfo中的title和iconBitmap赋值了。这里有疑问了?我们的图标图片不是已经做好了,这里怎么又createBitmap呢?我们去看看

    public Bitmap createBitmap(ComponentName componentName, Bitmap bitmap,
            ApplicationInfo application) {
    
    
        if (!componentName.getPackageName().equals("com.android.mms")) {
    
    
            return bitmap;
        }
        //return the Bitmap with unRead Tip
        return MessageManager.getInstance(mContext).createMmsBitmap(bitmap, application);
    }


这下我们恍然大悟了吧,这是在做什么呢?是在把我们未读短信的个数显示在图标上,明白了吧。如果我们要把未接电话的个数显示在图片上就可以在这里动手脚了,至于怎么获取到未读短信,未接电话个数的,怎么把数字做到图片上的,这些都是学习android的必备知识,不在详细解说。不懂的可以顺这代码看看就知道了。到此我们一个应用的ApplicationInfo制作完成了。然后就是循环的问题了,我们回到LauncherModel中loadAllAppsByBatch继续看

                for (int j=0; i<N && j<batchSize; j++) {
    
    
                    // This builds the icon bitmaps.
                    mAllAppsList.add(new ApplicationInfo(packageManager, apps.get(i),
                            mIconCache, mLabelCache));
                    i++;
                }


刚才我们用了大量的篇幅讲解了制作一个ApplicationInfo的过程,希望大家都能明白。接着我们看看mAllAppsList.add也就是

    public void add(ApplicationInfo info) {
    
    
        if (findActivity(data, info.componentName)) {
    
    
            return;
        }
        data.add(info);
        added.add(info);
    }


把我们制作好的ApplicationInfo给了AllAppsList的data和added这两个我们前面说过,这里不再多说。继续看LauncherModel中loadAllAppsByBatch后面的内容

                final boolean first = i <= batchSize;
                final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                final ArrayList<ApplicationInfo> added = mAllAppsList.added;
                mAllAppsList.added = new ArrayList<ApplicationInfo>();
 
                mHandler.post(new Runnable() {
    
    
                    public void run() {
    
    
                        final long t = SystemClock.uptimeMillis();
                        if (callbacks != null) {
    
    
                        	isNeedSave = true;
                            if (first) {
    
    
                                callbacks.bindAllApplications(added);
                            } else {
    
    
                                callbacks.bindAppsAdded(added);
                            }
                            if (DEBUG_LOADERS) {
    
    
                                Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - t) + "ms");
                            }
                        } else {
    
    
                            Log.i(TAG, "not binding apps: no Launcher activity");
                        }
                    }
                });


把我们的mAllAppsList.added给了一个新申请的ArrayList<ApplicationInfo>也就是added,然后把我们的mAllAppsList.added重新申请,置空。最后又启动了一个线程来加载和显示我们的应用了,也就是

                            if (first) {
    
    
                                callbacks.bindAllApplications(added);
                            } else {
    
    
                                callbacks.bindAppsAdded(added);
                            }


这两个差不多,最终走向一样,我们只看一个bindAllApplications,bindAllApplications是在Launcher中实现的

    public void bindAllApplications(final ArrayList<ApplicationInfo> apps) {
    
    
        // Remove the progress bar entirely; we could also make it GONE
        // but better to remove it since we know it's not going to be used
        View progressBar = mAppsCustomizeTabHost.
            findViewById(R.id.apps_customize_progress_bar);
        if (progressBar != null) {
    
    
            ((ViewGroup)progressBar.getParent()).removeView(progressBar);
        }
        if (LOGD) Log.d(TAG, "bindAllApplications " + apps.toString());
        // We just post the call to setApps so the user sees the progress bar
        // disappear-- otherwise, it just looks like the progress bar froze
        // which doesn't look great
        mAppsCustomizeTabHost.post(new Runnable() {
    
    
            public void run() {
    
    
                if (mAppsCustomizeContent != null) {
    
    
                    mAppsCustomizeContent.setApps(apps);
                }
            }
        });
    }


这里出现两个布局mAppsCustomizeTabHost和mAppsCustomizeContent,这里插个小曲,来说一下Launcher布局,我们不从最底层说起,我们从DragLayout这层说起,顾名思义是拖动层了,这一层包含AppsCustomizeTabHost也就是主菜单,Hotseat最下面的那一行应用图标,Workspace就是待机界面了等等吧,其它不重要的就不说了。接着说说Workspace,Workspace包含很多CellLayout,CellLayout就是我们在待机左右滑动时的页,CellLayout又包含CellLayoutChildren,CellLayoutChildren包含许多LauncherAppWidgetHostView接下来就是我们的Widget了,CellLayoutChildren还包含BubbleTextView,就是我们的App图标了,这就是Workspace的构造。接着说说主菜单,也就是AppsCustomizeTabHost,AppsCustomizeTabHost之上有很多层,不再解释,直接到AppsCustomizePagedView层,AppsCustomizePagedView包含很多PagedViewCellLayout,PagedViewCellLayout就是我们在主菜单左右滑动出现的页了,PagedViewCellLayout之上是PagedViewCellLayoutChildren,PagedViewCellLayoutChildren包含很多PagedViewIcon也就是我们的应用图标。这样我们就清楚了Launcher的大致布局了。好了,我们接着说mAppsCustomizeTabHost和mAppsCustomizeContent,这两个也就是AppsCustomizeTabHost和AppsCustomizePagedView,通过上面的解释可以明白它们之间的关系了,我们这里要加载应用,就是去AppsCustomizePagedView中加载了,也就是mAppsCustomizeContent.setApps(apps)了

    public void setApps(ArrayList<ApplicationInfo> list) {
    
    
        mApps = list;
        Collections.sort(mApps, LauncherModel.APP_NAME_COMPARATOR);
        updatePageCounts();
 
        // The next layout pass will trigger data-ready if both widgets and apps are set, so
        // request a layout to do this test and invalidate the page data when ready.
        LauncherModel.cacheAllApp(mContext, mApps);
        if (testDataReady()) requestLayout();
        invalidatePageData();
    }


首先重新计算我们的page页个数

    private void updatePageCounts() {
    
    
        mNumWidgetPages = (int) Math.ceil(mWidgets.size() /
                (float) (mWidgetCountX * mWidgetCountY));
        mNumAppsPages = (int) Math.ceil((float) mApps.size() / (mCellCountX * mCellCountY));
    }


接着备份我们的应用信息到数据库LauncherModel.cacheAllApp(mContext, mApps),也就我们前面说的从数据库加载应用的过程,由于代码不同,有的代码没有这一部分,所以不做讲解,好处就是开机加载应用图标比较快。然后就是更新我们布局,就可以把我们的应用显示出来了。而invalidatePageData()是什么呢?就是Launcher页面都放满了图标,就新增一页,来放图标,最终还是通过requestLayout()来从新分布布局刷新显示了。至此我们的图标就显示出来了。

    接着我们说说当点击图标的时候怎样启动应用的。这个分为两个,一个是点击主菜单图标,一个点击待机图标。我么先说点击待机图标也就是Workspace图标,这个事件响应再Launcher中,也就是

    public void onClick(View v) {
    
    
        // Make sure that rogue clicks don't get through while allapps is launching, or after the
        // view has detached (it's possible for this to happen if the view is removed mid touch).
        if (v.getWindowToken() == null) {
    
    
            return;
        }
 
        if (mWorkspace.isSwitchingState()) {
    
    
            return;
        }
 
        Object tag = v.getTag();
        if (tag instanceof ShortcutInfo) {
    
    
            // Open shortcut
            final Intent intent = ((ShortcutInfo) tag).intent;
            int[] pos = new int[2];
            v.getLocationOnScreen(pos);
            intent.setSourceBounds(new Rect(pos[0], pos[1],
                    pos[0] + v.getWidth(), pos[1] + v.getHeight()));
            boolean success = startActivitySafely(intent, tag);
 
            if (success && v instanceof BubbleTextView) {
    
    
                mWaitingForResume = (BubbleTextView) v;
                mWaitingForResume.setStayPressed(true);
            }
        } else if (tag instanceof FolderInfo) {
    
    
            if (v instanceof FolderIcon) {
    
    
                FolderIcon fi = (FolderIcon) v;
                handleFolderClick(fi);
            }
        } else if (v == mAllAppsButton) {
    
    
            if (mState == State.APPS_CUSTOMIZE) {
    
    
                showWorkspace(true);
            } else {
    
    
                onClickAllAppsButton(v);
            }
        }
    }


先说一下ShortcutInfo,ShortcutInfo的信息是从ApplicationInfo信息中获取的,至于是怎么获取,这里就不再解释,童鞋们可以自己研究。所以这里的((ShortcutInfo) tag).intent信息大家就很明白了,是可以启动一个应用的,前面说过,不再解释。至于下面的FolderInfo就是点击文件夹时做了什么,这里不讲解了,自己研究。然后看看主菜单点击时是如何启动应用的,这部分在AppsCustomizePagedView中

    public void onClick(View v) {
    
    
        // When we have exited all apps or are in transition, disregard clicks
        if (!mLauncher.isAllAppsCustomizeOpen() ||
                mLauncher.getWorkspace().isSwitchingState()) return;
 
        if (v instanceof PagedViewIcon) {
    
    
            // Animate some feedback to the click
            final ApplicationInfo appInfo = (ApplicationInfo) v.getTag();
            // bug 211336 begin
            //if (OptConfig.LC_RAM_SUPPORT) {
    
    
                // remove the animation when click
                mLauncher.startActivitySafely(appInfo.intent, appInfo);
            //} else {
    
    
            //    animateClickFeedback(v, new Runnable() {
    
    
            //        @Override
            //        public void run() {
    
    
            //            mLauncher.startActivitySafely(appInfo.intent, appInfo);
            //        }
            //    });
            //}
            // bug 211336 end
        } else if (v instanceof PagedViewWidget) {
    
    
            // Let the user know that they have to long press to add a widget
            Toast.makeText(getContext(), R.string.long_press_widget_to_add,
                    Toast.LENGTH_SHORT).show();
 
            // Create a little animation to show that the widget can move
            float offsetY = getResources().getDimensionPixelSize(R.dimen.dragViewOffsetY);
            final ImageView p = (ImageView) v.findViewById(R.id.widget_preview);
            AnimatorSet bounce = new AnimatorSet();
            ValueAnimator tyuAnim = ObjectAnimator.ofFloat(p, "translationY", offsetY);
            tyuAnim.setDuration(125);
            ValueAnimator tydAnim = ObjectAnimator.ofFloat(p, "translationY", 0f);
            tydAnim.setDuration(100);
            bounce.play(tyuAnim).before(tydAnim);
            bounce.setInterpolator(new AccelerateInterpolator());
            bounce.start();
        }
    }


两部分,一部分是点击主菜单图标,一部分是点击Widget,也就是长按Widget添加到Workspace待机,这里只说点击主菜单图标也就是PagedViewIcon,单点击PagedViewIcon的时候就会获取到响应的ApplicationInfo信息,通过ApplicationInfo的intent来启动一个应用是完全可以的,这个我们前面已经强调过很多次了。

    点击图标启动应用就讲这么多,不再多讲,接下来我们说说拖动图标或者widget。当我们长按时就可以从待机移动图标或widget,还可一从主菜单把图标或widget移动到待机,这个过程是怎么一回事呢?这里做一下讲解。先说在workspace待机拖动图标,我们从长按事件说起,这个在Launcher中

    public boolean onLongClick(View v) {
    
    
        if (mState != State.WORKSPACE) {
    
    
            return false;
        }
 
        if (isWorkspaceLocked()) {
    
    
            return false;
        }
 
        if (!(v instanceof CellLayout)) {
    
    
            v = (View) v.getParent().getParent();
        }
 
        resetAddInfo();
        CellLayout.CellInfo longClickCellInfo = (CellLayout.CellInfo) v.getTag();
        // This happens when long clicking an item with the dpad/trackball
        if (longClickCellInfo == null) {
    
    
            return true;
        }
 
        // The hotseat touch handling does not go through Workspace, and we always allow long press
        // on hotseat items.
        final View itemUnderLongClick = longClickCellInfo.cell;
        boolean allowLongPress = isHotseatLayout(v) || mWorkspace.allowLongPress();
        if (allowLongPress && !mDragController.isDragging()) {
    
    
            if (itemUnderLongClick == null) {
    
    
                // User long pressed on empty space
                mWorkspace.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS,
                        HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
                startWallpaper();
            } else {
    
    
                if (!(itemUnderLongClick instanceof Folder)) {
    
    
                    // User long pressed on an item
                    mWorkspace.startDrag(longClickCellInfo);
                }
            }
        }
        return true;
    }


也就是mWorkspace.startDrag(longClickCellInfo)其它内容不做讲解,感兴趣可以看看。mWorkspace.startDrag(longClickCellInfo)也就是

    void startDrag(CellLayout.CellInfo cellInfo) {
    
    
        View child = cellInfo.cell;
 
        // Make sure the drag was started by a long press as opposed to a long click.
        if (!child.isInTouchMode()) {
    
    
            return;
        }
 
        mDragInfo = cellInfo;
        child.setVisibility(GONE);
 
        child.clearFocus();
        child.setPressed(false);
 
        final Canvas canvas = new Canvas();
 
        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
 
        // The outline is used to visualize where the item will land if dropped
        mDragOutline = createDragOutline(child, canvas, bitmapPadding);
        beginDragShared(child, this);
    }


创建一个Bitmap为mDragOutline这个mDragOutline保存的是原始的图片,等到UP的时候也就是松手的时候会用到。然后就是beginDragShared也就是

    public void beginDragShared(View child, DragSource source) {
    
    
        Resources r = getResources();
 
        // We need to add extra padding to the bitmap to make room for the glow effect
        final int bitmapPadding = HolographicOutlineHelper.MAX_OUTER_BLUR_RADIUS;
 
        // The drag bitmap follows the touch point around on the screen
        final Bitmap b = createDragBitmap(child, new Canvas(), bitmapPadding);
 
        final int bmpWidth = b.getWidth();
 
        mLauncher.getDragLayer().getLocationInDragLayer(child, mTempXY);
        final int dragLayerX = (int) mTempXY[0] + (child.getWidth() - bmpWidth) / 2;
        int dragLayerY = mTempXY[1] - bitmapPadding / 2;
 
        Point dragVisualizeOffset = null;
        Rect dragRect = null;
        if (child instanceof BubbleTextView || child instanceof PagedViewIcon) {
    
    
            int iconSize = r.getDimensionPixelSize(R.dimen.app_icon_size);
            int iconPaddingTop = r.getDimensionPixelSize(R.dimen.app_icon_padding_top);
            int top = child.getPaddingTop();
            int left = (bmpWidth - iconSize) / 2;
            int right = left + iconSize;
            int bottom = top + iconSize;
            dragLayerY += top;
            // Note: The drag region is used to calculate drag layer offsets, but the
            // dragVisualizeOffset in addition to the dragRect (the size) to position the outline.
            dragVisualizeOffset = new Point(-bitmapPadding / 2, iconPaddingTop - bitmapPadding / 2);
            dragRect = new Rect(left, top, right, bottom);
        } else if (child instanceof FolderIcon) {
    
    
            int previewSize = r.getDimensionPixelSize(R.dimen.folder_preview_size);
            dragRect = new Rect(0, 0, child.getWidth(), previewSize);
        }
 
        // Clear the pressed state if necessary
        if (child instanceof BubbleTextView) {
    
    
            BubbleTextView icon = (BubbleTextView) child;
            icon.clearPressedOrFocusedBackground();
        }
 
        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);
        b.recycle();
    }


创建一个拖动的Bitmap这个和刚才的mDragOutline不同,这个Bitmap透明度、大小等等有变化的。然后就是

        mDragController.startDrag(b, dragLayerX, dragLayerY, source, child.getTag(),
                DragController.DRAG_ACTION_MOVE, dragVisualizeOffset, dragRect);


也就是

    public void startDrag(Bitmap b, int dragLayerX, int dragLayerY,
            DragSource source, Object dragInfo, int dragAction, Point dragOffset, Rect dragRegion) {
    
    
        if (PROFILE_DRAWING_DURING_DRAG) {
    
    
            android.os.Debug.startMethodTracing("Launcher");
        }
 
        // Hide soft keyboard, if visible
        if (mInputMethodManager == null) {
    
    
            mInputMethodManager = (InputMethodManager)
                    mLauncher.getSystemService(Context.INPUT_METHOD_SERVICE);
        }
        mInputMethodManager.hideSoftInputFromWindow(mWindowToken, 0);
 
        for (DragListener listener : mListeners) {
    
    
            listener.onDragStart(source, dragInfo, dragAction);
        }
 
        final int registrationX = mMotionDownX - dragLayerX;
        final int registrationY = mMotionDownY - dragLayerY;
 
        final int dragRegionLeft = dragRegion == null ? 0 : dragRegion.left;
        final int dragRegionTop = dragRegion == null ? 0 : dragRegion.top;
 
        mDragging = true;
 
        mDragObject = new DropTarget.DragObject();
 
        mDragObject.dragComplete = false;
        mDragObject.xOffset = mMotionDownX - (dragLayerX + dragRegionLeft);
        mDragObject.yOffset = mMotionDownY - (dragLayerY + dragRegionTop);
        mDragObject.dragSource = source;
        mDragObject.dragInfo = dragInfo;
 
        mVibrator.vibrate(VIBRATE_DURATION);
 
        final DragView dragView = mDragObject.dragView = new DragView(mLauncher, b, registrationX,
                registrationY, 0, 0, b.getWidth(), b.getHeight());
 
        if (dragOffset != null) {
    
    
            dragView.setDragVisualizeOffset(new Point(dragOffset));
        }
        if (dragRegion != null) {
    
    
            dragView.setDragRegion(new Rect(dragRegion));
        }
 
        dragView.show(mMotionDownX, mMotionDownY);
        handleMoveEvent(mMotionDownX, mMotionDownY);
    }


创建一个DragView然后显示dragView.show,长按的时候会震动一下也就是在这里mVibrator.vibrate(VIBRATE_DURATION),最终handleMoveEvent(mMotionDownX, mMotionDownY)也就是

    private void handleMoveEvent(int x, int y) {
    
    
        mDragObject.dragView.move(x, y);
 
        // Drop on someone?
        final int[] coordinates = mCoordinatesTemp;
        DropTarget dropTarget = findDropTarget(x, y, coordinates);
        mDragObject.x = coordinates[0];
        mDragObject.y = coordinates[1];
        if (dropTarget != null) {
    
    
            DropTarget delegate = dropTarget.getDropTargetDelegate(mDragObject);
            if (delegate != null) {
    
    
                dropTarget = delegate;
            }
 
            if (mLastDropTarget != dropTarget) {
    
    
                if (mLastDropTarget != null) {
    
    
                    mLastDropTarget.onDragExit(mDragObject);
                }
                dropTarget.onDragEnter(mDragObject);
            }
            dropTarget.onDragOver(mDragObject);
        } else {
    
    
            if (mLastDropTarget != null) {
    
    
                mLastDropTarget.onDragExit(mDragObject);
            }
        }
        mLastDropTarget = dropTarget;
 
        // After a scroll, the touch point will still be in the scroll region.
        // Rather than scrolling immediately, require a bit of twiddling to scroll again
        final int slop = ViewConfiguration.get(mLauncher).getScaledWindowTouchSlop();
        mDistanceSinceScroll +=
            Math.sqrt(Math.pow(mLastTouch[0] - x, 2) + Math.pow(mLastTouch[1] - y, 2));
        mLastTouch[0] = x;
        mLastTouch[1] = y;
 
        if (x < mScrollZone) {
    
    
            if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
    
    
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, SCROLL_LEFT)) {
    
    
                    mScrollRunnable.setDirection(SCROLL_LEFT);
                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                }
            }
        } else if (x > mScrollView.getWidth() - mScrollZone) {
    
    
            if (mScrollState == SCROLL_OUTSIDE_ZONE && mDistanceSinceScroll > slop) {
    
    
                mScrollState = SCROLL_WAITING_IN_ZONE;
                if (mDragScroller.onEnterScrollArea(x, y, SCROLL_RIGHT)) {
    
    
                    mScrollRunnable.setDirection(SCROLL_RIGHT);
                    mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
                }
            }
        } else {
    
    
            if (mScrollState == SCROLL_WAITING_IN_ZONE) {
    
    
                mScrollState = SCROLL_OUTSIDE_ZONE;
                mScrollRunnable.setDirection(SCROLL_RIGHT);
                mHandler.removeCallbacks(mScrollRunnable);
                mDragScroller.onExitScrollArea();
            }
        }
    }


当开始拖动的时候也就开始分发ACTION_MOVE消息,也就是

    public boolean onTouchEvent(MotionEvent ev) {
    
    
        if (!mDragging) {
    
    
            return false;
        }
 
        final int action = ev.getAction();
        final int[] dragLayerPos = getClampedDragLayerPos(ev.getX(), ev.getY());
        final int dragLayerX = dragLayerPos[0];
        final int dragLayerY = dragLayerPos[1];
 
        switch (action) {
    
    
        case MotionEvent.ACTION_DOWN:
            // Remember where the motion event started
            mMotionDownX = dragLayerX;
            mMotionDownY = dragLayerY;
 
            if ((dragLayerX < mScrollZone) || (dragLayerX > mScrollView.getWidth() - mScrollZone)) {
    
    
                mScrollState = SCROLL_WAITING_IN_ZONE;
                mHandler.postDelayed(mScrollRunnable, SCROLL_DELAY);
            } else {
    
    
                mScrollState = SCROLL_OUTSIDE_ZONE;
            }
            break;
        case MotionEvent.ACTION_MOVE:
            handleMoveEvent(dragLayerX, dragLayerY);
            break;
        case MotionEvent.ACTION_UP:
            // Ensure that we've processed a move event at the current pointer location.
            handleMoveEvent(dragLayerX, dragLayerY);
 
            mHandler.removeCallbacks(mScrollRunnable);
            if (mDragging) {
    
    
                drop(dragLayerX, dragLayerY);
            }
            endDrag();
            break;
        case MotionEvent.ACTION_CANCEL:
            cancelDrag();
            break;
        }
 
        return true;
    }


这里的MotionEvent.ACTION_MOVE消息,一直重复handleMoveEvent,当松手的时候就是MotionEvent.ACTION_UP了。我们还先回到handleMoveEvent看看

mDragObject.dragView.move(x, y);


这个就是拖动的过程了,也就是

    void move(int touchX, int touchY) {
    
    
        DragLayer.LayoutParams lp = mLayoutParams;
        lp.x = touchX - mRegistrationX + (int) mOffsetX;
        lp.y = touchY - mRegistrationY + (int) mOffsetY;
        mDragLayer.requestLayout();
    }


一直更改坐标,然后更新。然后还回到handleMoveEvent下面的内容是什么呢?大致解释一下不再深入解析,就是当drop也就是UP松手时做的一下事情,和当移动到边缘时切换到下一页,这些不再讲解。然后我们回到MotionEvent.ACTION_UP消息,也就是

            handleMoveEvent(dragLayerX, dragLayerY);
 
            mHandler.removeCallbacks(mScrollRunnable);
            if (mDragging) {
    
    
                drop(dragLayerX, dragLayerY);
            }
            endDrag();

猜你喜欢

转载自blog.csdn.net/gqg_guan/article/details/130889782
今日推荐