Android—->Allapps載入流程詳解【AndroidICS4.0——>Launcher系列五】

來源:互聯網
上載者:User

 工作需要總結,這樣就能保證地基牢固,就能爬得更高;                              

                                                                          ----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於北京

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.