工作需要总结,这样就能保证地基牢固,就能爬得更高;
----2013-01-07题记
转载请标明出处:http://blog.csdn.net/wdaming1986/article/details/8478533
前段时间研究了Launcher的AllApps的加载流程,对这个进行了一点修改,呵呵,其实也不算太难,只要把Launcher的代码都能看个80%,基本就是想怎么改就怎么改!AllApps是什么,就是在Android的IDEL界面(主界面)点击MainMenu键进入后的界面,也就是所有应用程序界面;
先来看看它是怎么被手机加载上来的?
Step1:手机第一次开机,首先加载LauncherApplication,注册一些监听,共享数据,比如:LauncherModel对象,通过((LauncherApplication)getApplication());可以获取到LauncherApplication的对象;然后再加载Launcher.java这个类,先走onCreate()方法;里面调用如下方法:
if (!mRestoring) {
mModel.startLoader(this, true);
}
Step2:在Step1中这个方法调到了LauncheModel.java的类里面了,在这个方法里面主要的工作就是启动一个线程,下面我们来看看在线程的run()方法做了哪些操作;
public void run() { // Optimize for end-user experience: if the Launcher is up and // running with the // All Apps interface in the foreground, load All Apps first. Otherwise, load the // workspace first (default). final Callbacks cbk = mCallbacks.get(); final boolean loadWorkspaceFirst = cbk != null ? (!cbk.isAllAppsVisible()) : true; 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); } } // Update the saved icons if necessary if (DEBUG_LOADERS) Log.d(TAG, "Comparing loaded icons to database icons"); for (Object key : sDbIconCache.keySet()) { updateSavedIcon(mContext, (ShortcutInfo) key, sDbIconCache.get(key)); } sDbIconCache.clear(); // Clear out this reference, otherwise we end up holding it until all of the // callback runnables are done. mContext = null; synchronized (mLock) { // If we are still the last one to be scheduled, remove ourselves. if (mLoaderTask == this) { mLoaderTask = null; } } }
其实主要的操作就是加载workspace和AllApps;loadAndBindAllApps()这个方法就是加载AllApps的;
Step3:在这个loadAndBindAllApps()里面,会调用loadAllAppsByBatch(),批量加载AllApps;
先根据:
final Intent mainIntent = new Intent(Intent.ACTION_MAIN, null); mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
创建一个带有CATEGORY_LAUNCHER这种类型的mainIntent,然后再通过
List<ResolveInfo> apps=packageManager.queryIntentActivities(mainIntent, 0);
过滤出所有的apps,通过sort对apps进行排序:
Collections.sort(apps,new LauncherModel.ShortcutNameComparator(packageManager, mLabelCache));
排序完成后,然后把这些apps逐个添加到ArrayList中去:代码如下:
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++; }
调到AllAppsList.java中的add方法:
public void add(ApplicationInfo info) { if (findActivity(data, info.componentName)) { return; } data.add(info); added.add(info); }
这个added的定义就是:
public ArrayList<ApplicationInfo> added = new ArrayList<ApplicationInfo>(DEFAULT_APPLICATIONS_NUMBER);
然后通过开启线程callback回调到Launcher.java的bindAllApplications()方法中:
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) { 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"); } } });
Step4:在Launcher.java中bindAllApplications()方法中做的事:如果有对话框存在,就remove对话框,主要是
mAppsCustomizeContent.setApps(apps);
Step5:在AppsCustomizePagedView.java中的setApps()中主要做的事就是,赋值给mApps,再次对apps进行排序,计算apps的页数和widget占用的页数;代码如下:
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. if (testDataReady()) requestLayout(); }
updatePageCounts()就是计算apps的页数和widget的页数;
Step6:而进入这个allapps的时候,就是进入到AppsCustomizePagedView.java这个类的时候会调用
onMeasure()这个方法;在这个里面首先会对allapps和widgets进行校验,
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); int height = MeasureSpec.getSize(heightMeasureSpec); if (!isDataReady()) { if (testDataReady()) { setDataIsReady(); setMeasuredDimension(width, height); onDataReady(width, height); } } super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
通过testDataReady()这个方法来校验是否他们为空!如果为空就不加载他们;代码如下:
/** * This differs from isDataReady as this is the test done if isDataReady is not set. */ private boolean testDataReady() { // We only do this test once, and we default to the Applications page, so we only really // have to wait for there to be apps. // TODO: What if one of them is validly empty return !mApps.isEmpty() && !mWidgets.isEmpty(); }
当allapps和widgets的数据都准备好了的时候,给这个view设置宽和高setMeasuredDimension(width, height);
然后调用onDataReady(width, height);在这个方法中会计算占用的页数,内容的宽度,细胞的数量,强制措施,以更新重新计算差距,存储页面,刷新数据显示上来通过invalidatePageData(Math.max(0, page), hostIsTransitioning);
这个调用到了PageView.java这个类(Launcher的主要精华类,写得相当有水准,看了好几遍,每次看都有收获)在这个方法里面主要做的是
(1)先加载apps和widgets的view,通过方法:
// Update all the pages syncPages();
public void syncPages() { removeAllViews(); cancelAllTasks(); Context context = getContext(); for (int j = 0; j < mNumWidgetPages; ++j) { PagedViewGridLayout layout = new PagedViewGridLayout(context, mWidgetCountX, mWidgetCountY); setupPage(layout); addView(layout, new PagedViewGridLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); } for (int i = 0; i < mNumAppsPages; ++i) { PagedViewCellLayout layout = new PagedViewCellLayout(context); setupPage(layout); addView(layout); } }
(2)再刷新数据到每页的view界面中,通过方法:
// Load any pages that are necessary for the current window of views loadAssociatedPages(mCurrentPage, immediateAndOnly); requestLayout();
在PageView.java中的loadAssociatedPages()方法中里面调用的主要的方法syncPageItems(i, (i == page) && immediateAndOnly);这个通过接口调到了AppsCustomizePagedView.java中的syncPageItems()方法中去了:
@Override public void syncPageItems(int page, boolean immediate) { if (page < mNumAppsPages) { syncAppsPageItems(page, immediate); } else { syncWidgetPageItems(page - mNumAppsPages, immediate); } }
里面就是刷新apps或者是widget的每一页;
再来看看syncAppsPageItems()这个方法:
public void syncAppsPageItems(int page, boolean immediate) { // ensure that we have the right number of items on the pages int numCells = mCellCountX * mCellCountY; int startIndex = page * numCells; int endIndex = Math.min(startIndex + numCells, mApps.size()); PagedViewCellLayout layout = (PagedViewCellLayout) getPageAt(page); layout.removeAllViewsOnPage(); ArrayList<Object> items = new ArrayList<Object>(); ArrayList<Bitmap> images = new ArrayList<Bitmap>(); for (int i = startIndex; i < endIndex; ++i) { ApplicationInfo info = mApps.get(i); PagedViewIcon icon = (PagedViewIcon) mLayoutInflater.inflate( R.layout.apps_customize_application, layout, false); icon.applyFromApplicationInfo(info, true, mHolographicOutlineHelper); icon.setOnClickListener(this); icon.setOnLongClickListener(this); icon.setOnTouchListener(this); icon.setOnKeyListener(this); int index = i - startIndex; int x = index % mCellCountX; int y = index / mCellCountX; layout.addViewToCellLayout(icon, -1, i, new PagedViewCellLayout.LayoutParams(x,y, 1,1)); items.add(info); images.add(info.iconBitmap); } layout.createHardwareLayers(); /* TEMPORARILY DISABLE HOLOGRAPHIC ICONS if (mFadeInAdjacentScreens) { prepareGenerateHoloOutlinesTask(page, items, images); } */ }
当你看到这个addViewToCellLayout()方法的时候,我相信你就会有“山穷水复疑无路,柳暗花明又一村”的感觉了!这就是加载每个icon到view的那个位置;
syncWidgetPageItems()这个也是同理,代码我相信大家自己都能看明白了吧!
Step7:而这个widgets的数据是怎么加载上来的呢???这个是在Launcher.java中的onCreate()方法中一步一步加载的:
(1)在Launcher.java中的onCreate()方法中:
// Update customization drawer _after_ restoring the states if (mAppsCustomizeContent != null) { mAppsCustomizeContent.onPackagesUpdated(); }
(2)调用到AppsCustomizePagedView.java中的onPackagesUpdated()的方法,这个里面主要做的是启动一个延迟的线程来加载widgets
public void onPackagesUpdated() { // TODO: this isn't ideal, but we actually need to delay here. This call is triggered // by a broadcast receiver, and in order for it to work correctly, we need to know that // the AppWidgetService has already received and processed the same broadcast. Since there // is no guarantee about ordering of broadcast receipt, we just delay here. Ideally, // we should have a more precise way of ensuring the AppWidgetService is up to date. postDelayed(new Runnable() { public void run() { updatePackages(); } }, 500); }
(3)通过updatePackages()这个方法来实现的加载widgets的下面来看看代码:
public void updatePackages() { // Get the list of widgets and shortcuts boolean wasEmpty = mWidgets.isEmpty(); mWidgets.clear(); List<AppWidgetProviderInfo> widgets = AppWidgetManager.getInstance(mLauncher).getInstalledProviders(); Intent shortcutsIntent = new Intent(Intent.ACTION_CREATE_SHORTCUT); List<ResolveInfo> shortcuts = mPackageManager.queryIntentActivities(shortcutsIntent, 0); for (AppWidgetProviderInfo widget : widgets) { if (widget.minWidth > 0 && widget.minHeight > 0) { mWidgets.add(widget); } else { Log.e(LOG_TAG, "Widget " + widget.provider + " has invalid dimensions (" + widget.minWidth + ", " + widget.minHeight + ")"); } } mWidgets.addAll(shortcuts); Collections.sort(mWidgets, new LauncherModel.WidgetAndShortcutNameComparator(mPackageManager)); updatePageCounts(); if (wasEmpty) { // 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. if (testDataReady()) requestLayout(); } else { cancelAllTasks(); invalidatePageData(); } }
相信大家看到这里,根据上面的分析,就应该明白了mWidgets数据的加载过程了吧!
Step8:置于里面的click事件就查看onClick()方法;
长按是调用到父类的PagedViewWithDraggableItems.java的onLongClick()事件:
@Override public boolean onLongClick(View v) { // Return early if this is not initiated from a touch if (!v.isInTouchMode()) return false; // Return early if we are still animating the pages if (mNextPage != INVALID_PAGE) return false; // When we have exited all apps or are in transition, disregard long clicks if (!mLauncher.isAllAppsCustomizeOpen() || mLauncher.getWorkspace().isSwitchingState()) return false; return beginDragging(v); }
然后回调子类的AppsCustomizePagedView.java的beginDragging()方法的:
private void beginDraggingApplication(View v) { mLauncher.getWorkspace().onDragStartedWithItem(v); mLauncher.getWorkspace().beginDragShared(v, this); }
以后的流程大家可以自己跟跟,就明白拖拽事件的传递了,其实和Folder的拖拽是类似的原理;
今天就总结到这里吧!
2013年1月7日22:35于北京