Android中ICS4.0源碼Launcher啓動流程分析【android源碼Launcher系列一】

       最近研究ICS4.0的Launcher,發現4.0和2.3有稍微點區別,但是區別不是特別大,所以我就先整理一下Launcher啓動的大致流程。Launcher其實是貫徹於手機的整個系統的,時時刻刻都在運行,要是Launcher不運行了,手機就得黑屏了。Launcher的LauncherMode=singletask,所以說不管Launcher啓動了哪個應用,總有個Launcher的實例在堆棧中,並且位於棧底。點擊Home鍵進入到Launcher,上篇Android的全局鍵(home鍵/長按耳機鍵)詳解【android源碼解析八】 中有詳細的介紹。大致思路其實就是啓動launcher的時候,新啓動一個task。大致先說這麼多,先看截圖:

大明原創,轉載請標明出處:http://blog.csdn.net/wdaming1986/article/details/7585649 

 

                                    

                                                                           圖(1)

      上圖是4.0的Launcher界面,下面我們分步來解析一下Launcher的啓動過程。

 

     Step 0:首先要給大家介紹一下Launcher的數據庫,這個數據庫中存放着待機界面的圖標,主屏底部的應用程序圖標和桌面folder中各應用程序的圖標,ICS4.0的folder中只能放應用程序的快捷方式,shortcut不能放到這個folder中,先看截圖: 

                                                                        圖(2)

       說說各字段的含義:

                title:表示桌面應用程序的名字,有的title爲空,表示是widget的快捷方式;

             intent:表示啓動這個圖標的intent放到數據庫中,當click的時候就會調用這個字段,啓動相應的應用程序;

       container:表示應用程序的容器,folder的容器爲整數,-100:表示在桌面的程序,-101:表示是主屏底部的程序;

           screen:表示在第幾個屏,folder的screen都是0, container=-101的爲0,1,3,4;2爲allapp的按鈕;

               cellX:表示在屏幕X軸的位置,(0,1,2,3),左上角爲0點,往右依次增加;

               cellY:表示在屏幕Y軸的位置,(0,1,2,3),左上角爲0點,往下依次增加;

              spallX:表示佔X軸幾個格;

              spallY:表示佔Y軸幾個格;

         itemType:應用程序用0表示,shortcut用1表示,folder用2表示,widget用4表示;

    appWidgetId:-1表示不是widget,數字大於0表示纔是widget;

       isShortCut:值爲0表示不是應用程序的ShortCut,值爲1表示是應用程序的ShortCut;

         iconType:值爲0表示圖標的名字被定義爲包名的資源id,值爲1表示圖標用bitmap保存;

                 icon:表示應用程序的圖標,二進制的;顯示爲一張圖片;

       說明:folder中的應用快捷方式綁定folder---->是用container的值綁定folder的id的;

        詳細的講解請參考LauncherSettings.java這個類,有數據庫字段的詳細講解;

         手機是在第一次燒機完成後,數據庫的值還沒有,這時候launcher解析default_workspace.xml把這個值存到數據庫中;所以說想定製什麼樣的開機桌面就在default_workspace.xml中做相應的配置,具體參照我前面的博客:

Android中源碼Launcher主屏幕程序排列詳解【安卓Launcher進化一】中有詳細的介紹:

       i f (!convertDatabase(db)) {
                 // Populate favorites table with initial favorites
                loadFavorites(db, R.xml.default_workspace);
        }

      Step 1:開機後先啓動LauncherApplication.java這個類的onCreate()方法,下面看代碼:   

   

   @Override
    public void onCreate() {
        super.onCreate();

        // set sIsScreenXLarge and sScreenDensity *before* creating icon cache
        // 在創建圖標緩存之前先設置sIsScreenXLarge和屏幕設備的分辨率
        final int screenSize = getResources().getConfiguration().screenLayout &
                Configuration.SCREENLAYOUT_SIZE_MASK;
        sIsScreenLarge = screenSize == Configuration.SCREENLAYOUT_SIZE_LARGE ||
            screenSize == Configuration.SCREENLAYOUT_SIZE_XLARGE;
        sScreenDensity = getResources().getDisplayMetrics().density;

        // 實例化圖標緩存區的對象
        mIconCache = new IconCache(this);
        // 實例化一個LauncherModel對象,這個類是保存Launcher的內存啓動狀態,更新Launcher的數據庫的作用
        mModel = new LauncherModel(this, mIconCache);

        // Register intent receivers
        // 註冊監聽,應用package增加,刪除,改變的監聽。
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addDataScheme("package");
        registerReceiver(mModel, filter);
        filter = new IntentFilter();
        // 註冊application是否可用,語言改變,方向改變的監聽。4.0支持橫豎屏
        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
        filter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
        filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
        registerReceiver(mModel, filter);
        filter = new IntentFilter();
        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
        registerReceiver(mModel, filter);
        filter = new IntentFilter();
        filter.addAction(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED);
        registerReceiver(mModel, filter);

        // Register for changes to the favorites
        // 註冊favorites應用程序數據庫改變的監聽
        ContentResolver resolver = getContentResolver();
        resolver.registerContentObserver(LauncherSettings.Favorites.CONTENT_URI, true,
                mFavoritesObserver);
    }

 


 

     

       Step 2:在LauncherApplication.java中onTerminate()的方法,解除監聽的綁定;

 

 /**
     * There's no guarantee that this function is ever called.
     */
    @Override
    public void onTerminate() {
        super.onTerminate();

        unregisterReceiver(mModel);

        ContentResolver resolver = getContentResolver();
        resolver.unregisterContentObserver(mFavoritesObserver);
    }

 

     Step 3:Step1中的數據庫mFavoritesObserver監聽內部類如下:

 

   /**
     * Receives notifications whenever the user favorites have changed.
     */
    private final ContentObserver mFavoritesObserver = new ContentObserver(new Handler()) {
        @Override
        public void onChange(boolean selfChange) {
            mModel.startLoader(LauncherApplication.this, false);
        }
    };


 

      說明:mModel.startLoader(。。,。。)是開啓一個線程,設置線程的優先級NORM_PRIORITY,開始load桌面圖標對應的數據庫,這個過程是和Launcher.onCreate()同時進行的;

 

 

    Step 4: 接着我們來看看mModel.startLoader(LauncherApplication.this, false)的方法:

 

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

 
 

   Step 5:接着我們來看看LoaderTask.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;
                }
            }
        }

        public void stopLocked() {
            synchronized (LoaderTask.this) {
                mStopped = true;
                this.notify();
            }
        }

加載桌面圖標對應的數據庫的值,這些值能把這些圖標顯示在屏幕上

 

 

    Step 6:LauncherApplication.onCreate()方法啓動完成後,接着開始調用Launcher.java的onCreate()方法。代碼如下:

 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 得到LauncherApplication的對象app
        LauncherApplication app = ((LauncherApplication)getApplication());
        // 得到LauncherModel對象mModel,設置一個mCallbacks = new WeakReference<Callbacks>(callbacks)的
        // 回調callbacks
        mModel = app.setLauncher(this);
        // 得到圖標緩存的對象mIconCache
        mIconCache = app.getIconCache();
        // 得到拖拽控制類DragController的對象
        mDragController = new DragController(this);
        // 得到一個LayoutInflater佈局的對象
        mInflater = getLayoutInflater();

        // 得到一個AppWidgetManager的對象
        mAppWidgetManager = AppWidgetManager.getInstance(this);
        // 得到LauncherAppWidgetHost的一個對象
        mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
        // Start receiving onAppWidgetChanged calls for your AppWidgets.
        mAppWidgetHost.startListening();

        if (PROFILE_STARTUP) {
            android.os.Debug.startMethodTracing(
                    Environment.getExternalStorageDirectory() + "/launcher");
        }

        // 檢查Locale的語言級別,mcc, mnc的改變
        checkForLocaleChange();
        // 加載Launcher.xml佈局文件
        setContentView(R.layout.launcher);
        // Launcher的佈局的初始化
        setupViews();
        // 第一次啓動Android的展示設置嚮導,
        // 這個SharedPreferences中存在
        // <boolean name="cling.workspace.dismissed" value="true" />
        // 如果值爲true,則不顯示設置嚮導,爲false,則顯示設置嚮導。
        showFirstRunWorkspaceCling();

        // 註冊數據庫觀察者
        registerContentObservers();

        lockAllApps();

        mSavedState = savedInstanceState;
        restoreState(mSavedState);

        // Update customization drawer _after_ restoring the states
        if (mAppsCustomizeContent != null) {
            mAppsCustomizeContent.onPackagesUpdated();
        }

        if (PROFILE_STARTUP) {
            android.os.Debug.stopMethodTracing();
        }

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

        if (!mModel.isAllAppsLoaded()) {
            ViewGroup appsCustomizeContentParent = (ViewGroup) mAppsCustomizeContent.getParent();
            mInflater.inflate(R.layout.apps_customize_progressbar, appsCustomizeContentParent);
        }

        // For handling default keys
        mDefaultKeySsb = new SpannableStringBuilder();
        Selection.setSelection(mDefaultKeySsb, 0);

        // 註冊系統對話框消失的監聽
        IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        registerReceiver(mCloseSystemDialogsReceiver, filter);

        boolean searchVisible = false;
        boolean voiceVisible = false;
        // If we have a saved version of these external icons, we load them up immediately
        // 如果我們已經保存了外部圖標的版本,我們立即加載它們
        int coi = getCurrentOrientationIndexForGlobalIcons();
        if (sGlobalSearchIcon[coi] == null || sVoiceSearchIcon[coi] == null ||
                sAppMarketIcon[coi] == null) {
            updateAppMarketIcon();
            searchVisible = updateGlobalSearchIcon();
            voiceVisible = updateVoiceSearchIcon(searchVisible);
        }
        if (sGlobalSearchIcon[coi] != null) {
             updateGlobalSearchIcon(sGlobalSearchIcon[coi]);
             searchVisible = true;
        }
        if (sVoiceSearchIcon[coi] != null) {
            updateVoiceSearchIcon(sVoiceSearchIcon[coi]);
            voiceVisible = true;
        }
        if (sAppMarketIcon[coi] != null) {
            updateAppMarketIcon(sAppMarketIcon[coi]);
        }
        mSearchDropTargetBar.onSearchPackagesChanged(searchVisible, voiceVisible);

        // On large interfaces, we want the screen to auto-rotate based on the current orientation
        if (LauncherApplication.isScreenLarge() || Build.TYPE.contentEquals("eng")) {
            setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
        }
    }

 

    Step 7:其中LauncherModel這個類中有個回調接口,具體定義如下:

 

 public interface Callbacks {
        public boolean setLoadOnResume();
        public int getCurrentWorkspaceScreen();
        public void startBinding();
        public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end);
        public void bindFolders(HashMap<Long,FolderInfo> folders);
        public void finishBindingItems();
        public void bindAppWidget(LauncherAppWidgetInfo info);
        public void bindAllApplications(ArrayList<ApplicationInfo> apps);
        public void bindAppsAdded(ArrayList<ApplicationInfo> apps);
        public void bindAppsUpdated(ArrayList<ApplicationInfo> apps);
        public void bindAppsRemoved(ArrayList<ApplicationInfo> apps, boolean permanent);
        public void bindPackagesUpdated();
        public boolean isAllAppsVisible();
        public void bindSearchablesChanged();
    }


 

對LauncherModel進行初始化的時候mModel = app.setLauncher(this);---->mModel.initialize(launcher);----->

              public void initialize(Callbacks callbacks) {
                      synchronized (mLock) {
                              mCallbacks = new WeakReference<Callbacks>(callbacks);
                       }
               }

這個callbacks就是定義的接口回調,具體實現是在Launcher.java中定義的,啓動Launcher的過程中,這些實現是異步來實現的。還有Launcher.java的onResume()方法沒有講解,到這兒基本上Android的Launcher已經啓動起來了,這個onResume()我研究後再更新。

      歡迎各界同僚留言指正錯誤和拍磚!歡迎留言!

發佈了88 篇原創文章 · 獲贊 25 · 訪問量 124萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章