1. 程式人生 > >墨香帶你學Launcher之(二)-資料載入流

墨香帶你學Launcher之(二)-資料載入流

上一篇墨香帶你學Launcher之-概述,我已經介紹了Launcher的佈局以及相關的介面跳轉,今天我們繼續學習,按照計劃,我們開始學習Launcher啟動之資料載入,主要是圖示、Widget和資料夾的載入.

1.基礎知識

在介紹載入之前我先介紹一點需要用的相關知識:

  • Launcher:繼承Activity,是桌面的主介面,因此可知,桌面其實就是一個activity,只是和平常的應用不同,他用來顯示圖示、Widget和資料夾等;

  • LauncherModel:繼承BroadcastReceiver,由此可知他是一個廣播接收器,用來接收廣播,另外,LauncherModel還主要載入資料;

  • LauncherProvider:繼承ContentProvider,主要是處理資料庫操作;

  • LauncherAppState:單例模式的全域性管理類,主要是初始化一些物件,註冊廣播等.

  • Compat:相容包,帶有這個字尾的都是做相容處理的類.

2.預設圖示配置

我們在買回新的手機或者第一次安裝新的Launcher後,會發現手機的第一頁已經有了一些應用的圖示和時鐘或者天氣外掛,那麼這個是怎麼實現的呢?其實,手機在出廠的時候或者Launcher發到市場的時候已經預設排布了一些應用,在第一啟動時就會載入並且判斷手機中是否有這些圖示,如果有則顯示到固定位置,這個位置其實是已經寫好的.下面我們看看這個位置到底在哪裡寫好的.

下面是Launcher的資原始檔,我們看這個比我們平時的多一個xml資料夾,裡面有很多xml檔案,那麼這些是做什麼用的,我來解釋一下,有三個檔案,分別為default_workspace_4x4.xml,default_workspace_5x5.xml和default_workspace_5x6.xml,這三個檔案就是我們預設的佈局檔案,後面的跟著的4x4、5x5和5x6表示桌面圖示的列數和行數,也就是4行4列,5行5列,5行6列,這個怎麼用我們後面再說.

01

我們先看一下default_workspace_4x4.xml這個檔案中的程式碼:

02

第20行是一個include的檔案,在xml資料夾中的名字dw_phone_hotseat檔案,我們後面在看,接著看上圖的下面的程式碼,下面是三個resolve檔案,裡面包含一些資訊,screen表示第幾屏,x表示橫向的位置,y表示縱向的位置,那麼這個位置怎定的呢,我來畫一個4x4的圖你就明白了:

03

先看上半部分,就是我們說的4x4部分,沒一格表示一格圖示,在我們繪製圖標的時候已經分好了格,每格的大小,只要知道知道他的位置即可繪製圖標到相應的位置,那麼程式碼中的x,y就是這個圖示的位置.上面resolve中還有兩個favorite,在第一個中最後面有個”APP_”,這個我們一看就知道是應用的屬性,其實這就表示我們配置了那個app在這個位置,我們再看一下上面介紹的hotseat那個xml檔案:

04

這個圖我只截圖了一部分,想看全部的可以下載我github上的原始碼檢視,其實只是重複,我介紹一個就知道了,上一章我介紹過hotseat這個概念,其實就是我們手機最下面的那個四個或者五個最常用的app圖示,這個就是在這裡面配置的,我以第一個為例來介紹這個Hotseat配置,我們先看第21行,這個比我們前面介紹的多個屬性就是這個container,之前的是沒有的,這個就表示容器,-101就是hotseat,也就是這個圖示放置到Hotseat中,Hotseat只有一行,所以只有x在變,而y不變.

到此基本的桌面預設圖示顯示配置就介紹完了,如果你需要預設顯示哪個只需要配置這個檔案即可.

3.Launcher啟動過程

下面我們開始介紹Launcher的啟動過程.分析Launcher的啟動過程要從原始碼開始分析.在原始碼中是通過startHomeActivityLocked這個方法呼叫的啟動Launcher,我們先看一下哪裡開始呼叫的這個函式,

![05](https://img-blog.csdn.net/20170113164719174?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMzg5OTcwNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 從上面的呼叫圖可知有三個地方呼叫了啟動Launcher的方法,這三個方法中首次啟動應該是中間的那個systemReady方法,系統準備過程中呼叫啟動Launcher,我們看一下systemReady方法是哪裡呼叫的來驗證一下: ![06](https://img-blog.csdn.net/20170113164838705?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMzg5OTcwNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 從上程式碼靜態分析圖來看最開始是在System.main方法開始的,正好這個方法就是啟動系統的一個入口,也就是在這個過程中啟動了Launcher,找到呼叫的地方後,我們來看一下startHomeActivityLocked是怎麼啟動Launcher的,首先看一下原始碼: ![07](https://img-blog.csdn.net/20170113164907567?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMzg5OTcwNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 我們看上面的3473行,獲取Intent,再看3451行,如果不為空,則啟動HomeActivity,我們看一下這個Intent是什麼的Intent: ![08](https://img-blog.csdn.net/20170113165027304?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMzg5OTcwNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 上面的3424行,有個Intent.CATEGORY_HOME,我們在Intent中找到這個屬性的程式碼: ![09](https://img-blog.csdn.net/20170113165044503?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvdTAxMzg5OTcwNg==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast) 這個就是我們上一章講的設定app為launcher的屬性值. 通過上面這些分析可以看到系統是怎麼啟動launcher的.下面我們看是介紹Launcher內部是如何啟動的.

4.Launcher初始化

我們知道App的啟動是從Application開始的,但是我們最新的Launcher3中,谷歌工程師把這個類移除,再次之前的版本都是有這個類的,我在這提一下就是因為開發以前launcher的時候遇到一個問題,就是在Application和ContentProvider同時存在時,ContentProvider的onCreate方法要比Application的onCreate方法先啟動,下面我們通過原始碼分析來驗證這個問題.

啟動Application是從ActivityManagerService中的attachApplication方法開始的,程式碼:


 public final void attachApplication(IApplicationThread thread) {
        synchronized (this) {
            int callingPid = Binder.getCallingPid();
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid);
            Binder.restoreCallingIdentity(origId);
        }
    }

接著呼叫attachApplicationLocked方法,程式碼如下:


   private final boolean attachApplicationLocked(IApplicationThread thread, int pid) {
        app.makeActive(thread, mProcessStats);
        app.curAdj = app.setAdj = -100;
        app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT;
        app.forcingToForeground = null;
        updateProcessForegroundLocked(app, false, false);
        app.hasShownUi = false;
        app.debugging = false;
        app.cached = false;
        app.killedByAm = false;

        mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);

        boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
        List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null;

        if (!normalMode) {
            Slog.i(TAG, "Launching preboot mode app: " + app);
        }

        if (DEBUG_ALL) Slog.v(
            TAG, "New app record " + app
            + " thread=" + thread.asBinder() + " pid=" + pid);
        try {
           ...

            ProfilerInfo profilerInfo = profileFile == null ? null
                    : new ProfilerInfo(profileFile, profileFd, samplingInterval, profileAutoStop);
            thread.bindApplication(processName, appInfo, providers, app.instrumentationClass,
                    profilerInfo, app.instrumentationArguments, app.instrumentationWatcher,
                    app.instrumentationUiAutomationConnection, testMode, enableOpenGlTrace,
                    isRestrictedBackupMode || !normalMode, app.persistent,
                    new Configuration(mConfiguration), app.compat,
                    getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked());
            updateLruProcessLocked(app, false, null);
            app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis();
        } catch (Exception e) {

            app.resetPackageList(mProcessStats);
            app.unlinkDeathRecipient();
            startProcessLocked(app, "bind fail", processName);
            return false;
        }

        ...

        return true;
    }

上面程式碼中主要有一個thread.bindApplication方法來繫結application,接著看bindApplication程式碼:

 public final void bindApplication(String processName, ApplicationInfo appInfo,
                List<ProviderInfo> providers, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableOpenGlTrace, boolean isRestrictedBackupMode, boolean persistent,
                Configuration config, CompatibilityInfo compatInfo, Map<String, IBinder> services,
                Bundle coreSettings) {

            ...

            AppBindData data = new AppBindData();
            data.processName = processName;
            data.appInfo = appInfo;
            data.providers = providers;
            data.instrumentationName = instrumentationName;
            data.instrumentationArgs = instrumentationArgs;
            data.instrumentationWatcher = instrumentationWatcher;
            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
            data.debugMode = debugMode;
            data.enableOpenGlTrace = enableOpenGlTrace;
            data.restrictedBackupMode = isRestrictedBackupMode;
            data.persistent = persistent;
            data.config = config;
            data.compatInfo = compatInfo;
            data.initProfilerInfo = profilerInfo;
            sendMessage(H.BIND_APPLICATION, data);
        }

準備data資料,然後傳送訊息到Handler,Handler中處理訊息的程式碼如下:


public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {


                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break;

            }
            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
        }

根據訊息型別BIND_APPLICATION來判斷呼叫handleBindApplication方法,

 private void handleBindApplication(AppBindData data) {

        ...

        try {
            // If the app is being launched for full backup or restore, bring it up in
            // a restricted environment with the base application class.
            Application app = data.info.makeApplication(data.restrictedBackupMode, null);
            mInitialApplication = app;

            // don't bring up providers in restricted mode; they may depend on the
            // app's custom Application class
            if (!data.restrictedBackupMode) {
                List<ProviderInfo> providers = data.providers;
                if (providers != null) {

                    //安裝ContentProviders
                    installContentProviders(app, providers);

                    // For process that contains content providers, we want to
                    // ensure that the JIT is enabled "at some point".
                    mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
                }
            }

            ...

            try {
                //啟動Application的onCreate方法
                mInstrumentation.callApplicationOnCreate(app);
            } catch (Exception e) {
                if (!mInstrumentation.onException(app, e)) {
                    throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                        + ": " + e.toString(), e);
                }
            }
        } finally {
            StrictMode.setThreadPolicy(savedPolicy);
        }
    }

在上面函式中呼叫installContentProviders方法來安裝ContentProvider,程式碼如下:

 private void installContentProviders(
            Context context, List<ProviderInfo> providers) {
        final ArrayList<IActivityManager.ContentProviderHolder> results =
            new ArrayList<IActivityManager.ContentProviderHolder>();

        for (ProviderInfo cpi : providers) {
            if (DEBUG_PROVIDER) {
                StringBuilder buf = new StringBuilder(128);
                buf.append("Pub ");
                buf.append(cpi.authority);
                buf.append(": ");
                buf.append(cpi.name);
                Log.i(TAG, buf.toString());
            }
            IActivityManager.ContentProviderHolder cph = installProvider(context, null, cpi,

            ...

        }

      ...

    }

呼叫installProvider返回一個IActivityManager.ContentProviderHolder物件,我們看這個方法裡面做了哪些處理,

private IActivityManager.ContentProviderHolder installProvider(Context context,
            IActivityManager.ContentProviderHolder holder, ProviderInfo info,
            boolean noisy, boolean noReleaseNeeded, boolean stable) {
        ContentProvider localProvider = null;
        IContentProvider provider;         if (holder == null || holder.provider == null) {
            if (DEBUG_PROVIDER || noisy) {
                Slog.d(TAG, "Loading provider " + info.authority + ": "
                        + info.name);
            }
            Context c = null;
            ApplicationInfo ai = info.applicationInfo;
            if (context.getPackageName().equals(ai.packageName)) {
                c = context;
            } else if (mInitialApplication != null &&
                    mInitialApplication.getPackageName().equals(ai.packageName)) {
                c = mInitialApplication;
            } else {
                try {
                    c = context.createPackageContext(ai.packageName,
                            Context.CONTEXT_INCLUDE_CODE);
                } catch (PackageManager.NameNotFoundException e) {
                    // Ignore
                }
            }
            if (c == null) {
                Slog.w(TAG, "Unable to get context for package " +
                      ai.packageName +
                      " while loading content provider " +
                      info.name);
                return null;
            }
            try {
                final java.lang.ClassLoader cl = c.getClassLoader();
                localProvider = (ContentProvider)cl.
                    loadClass(info.name).newInstance();
                //獲取ContentProvider
                provider = localProvider.getIContentProvider();
                if (provider == null) {
                    Slog.e(TAG, "Failed to instantiate class " +
                          info.name + " from sourceDir " +
                          info.applicationInfo.sourceDir);
                    return null;
                }
                if (DEBUG_PROVIDER) Slog.v(
                    TAG, "Instantiating local provider " + info.name);

                // XXX Need to create the correct context for this provider.
                localProvider.attachInfo(c, info);

            } catch (java.lang.Exception e) {
                if (!mInstrumentation.onException(null, e)) {
                    throw new RuntimeException(
                            "Unable to get provider " + info.name
                            + ": " + e.toString(), e);
                }
                return null;
            }
        } else {
            provider = holder.provider;
            if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
                    + info.name);
        }    

        ...

        return retHolder;
    }

上面程式碼中有個關鍵方法:localProvider.attachInfo(c, info),這個方法就是新增Provider的,程式碼如下:

private void attachInfo(Context context, ProviderInfo info, boolean testing) {

        /*
         * Only allow it to be set once, so after the content service gives
         * this to us clients can't change it.
         */
        if (mContext == null) {
            mContext = context;
            if (context != null) {
                mTransport.mAppOpsManager = (AppOpsManager) context.getSystemService(
                        Context.APP_OPS_SERVICE);
            }
            mMyUid = Process.myUid();
            if (info != null) {
                setReadPermission(info.readPermission);
                setWritePermission(info.writePermission);
                setPathPermissions(info.pathPermissions);
                mExported = info.exported;
            }
            ContentProvider.this.onCreate();
        }
    }

我們看到在最後呼叫了ContentProvider.this.onCreate()這個方法,然後會返回到handleBindApplication方法中執行mInstrumentation.callApplicationOnCreate(app)方法,程式碼如下:


 public void callApplicationOnCreate(Application app) {
      app.onCreate();
 }

因此我們看到ContentProvider的onCreate方法比Application的onCreate方法呼叫早。這裡只是簡單介紹詳細過程去看原始碼。

我現在講解的是基於最新的Launcher3程式碼,因此我們這個Launcher中沒有Application,所以程式啟動最開始的是ContentProvider的onCreate方法,程式碼如下:

public boolean onCreate() {
        final Context context = getContext();
        StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
        mOpenHelper = new DatabaseHelper(context);
        StrictMode.setThreadPolicy(oldPolicy);
        LauncherAppState.setLauncherProvider(this);
        return true;
}

程式碼中處理的事情不多,主要是啟動嚴苛模式和建立資料庫,關於嚴苛模式的具體資訊看官方文件或者部落格,都有很詳細的講解,然後將ContentProvider放置到整個Launcher的管理類LauncherAppState中,以方便獲取。

接下來就是啟動Launcher,我麼看一下Launcher中的onCreate方法中的程式碼:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        if (DEBUG_STRICT_MODE) {
            StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectNetwork()   // or .detectAll() for all detectable problems
                    .penaltyLog()
                    .build());
            StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
                    .detectLeakedSqlLiteObjects()
                    .detectLeakedClosableObjects()
                    .penaltyLog()
                    .penaltyDeath()
                    .build());
        }

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.preOnCreate();
        }

        super.onCreate(savedInstanceState);

        LauncherAppState.setApplicationContext(getApplicationContext());
        LauncherAppState app = LauncherAppState.getInstance();

        // Load configuration-specific DeviceProfile
        mDeviceProfile = getResources().getConfiguration().orientation
                == Configuration.ORIENTATION_LANDSCAPE ?
                app.getInvariantDeviceProfile().landscapeProfile
                : app.getInvariantDeviceProfile().portraitProfile;

        mSharedPrefs = getSharedPreferences(LauncherAppState.getSharedPreferencesKey(),
                Context.MODE_PRIVATE);
        mIsSafeModeEnabled = getPackageManager().isSafeMode();
        mModel = app.setLauncher(this);
        mIconCache = app.getIconCache();

        mDragController = new DragController(this);
        mInflater = getLayoutInflater();
        mStateTransitionAnimation = new LauncherStateTransitionAnimation(this);

        mStats = new Stats(this);

        mAppWidgetManager = AppWidgetManagerCompat.getInstance(this);

        mAppWidgetHost = new LauncherAppWidgetHost(this, APPWIDGET_HOST_ID);
        mAppWidgetHost.startListening();

        // If we are getting an onCreate, we can actually preempt onResume and unset mPaused here,
        // this also ensures that any synchronous binding below doesn't re-trigger another
        // LauncherModel load.
        mPaused = false;

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

        setContentView(R.layout.launcher);

        registerHomeKey();
        setupViews();

        //動態設定各佈局的引數
        mDeviceProfile.layout(this);

        mSavedState = savedInstanceState;
        restoreState(mSavedState);

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

        if (!mRestoring) {
            if (DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE) {
                // If the user leaves launcher, then we should just load items asynchronously when
                // they return.
                mModel.startLoader(PagedView.INVALID_RESTORE_PAGE);
            } else {
                // We only load the page synchronously if the user rotates (or triggers a
                // configuration change) while launcher is in the foreground
                mModel.startLoader(mWorkspace.getRestorePage());
            }
        }

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

        IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        registerReceiver(mCloseSystemDialogsReceiver, filter);

        mRotationEnabled = Utilities.isRotationAllowedForDevice(getApplicationContext());
        // In case we are on a device with locked rotation, we should look at preferences to check
        // if the user has specifically allowed rotation.
        if (!mRotationEnabled) {
            mRotationEnabled = Utilities.isAllowRotationPrefEnabled(getApplicationContext(), false);
        }

        // On large interfaces, or on devices that a user has specifically enabled screen rotation,
        // we want the screen to auto-rotate based on the current orientation
        setOrientation();

        if (mLauncherCallbacks != null) {
            mLauncherCallbacks.onCreate(savedInstanceState);
            if (mLauncherCallbacks.hasLauncherOverlay()) {
                ViewStub stub = (ViewStub) findViewById(R.id.launcher_overlay_stub);
                mLauncherOverlayContainer = (InsettableFrameLayout) stub.inflate();
                mLauncherOverlay = mLauncherCallbacks.setLauncherOverlayView(
                        mLauncherOverlayContainer, mLauncherOverlayCallbacks);
                mWorkspace.setLauncherOverlay(mLauncherOverlay);
            }
        }

        if (shouldShowIntroScreen()) {
            showIntroScreen();
        } else {
            showFirstRunActivity();
            showFirstRunClings();
        }
    }

程式碼比較多我們看一下執行過程圖:


11

首先是啟動嚴苛模式,準備回撥介面,初始化LauncherAppState:

    private LauncherAppState() {
        if (sContext == null) {
            throw new IllegalStateException("LauncherAppState inited before app context set");
        }

        if (sContext.getResources().getBoolean(R.bool.debug_memory_enabled)) {
            MemoryTracker.startTrackingMe(sContext, "L");
        }

        //初始化固定的裝置配置
        mInvariantDeviceProfile = new InvariantDeviceProfile(sContext);

        //初始化圖示管理工具
        mIconCache = new IconCache(sContext, mInvariantDeviceProfile);

        //初始化Widget載入混存工具
        mWidgetCache = new WidgetPreviewLoader(sContext, mIconCache);

        mAppFilter = AppFilter.loadByName(sContext.getString(R.string.app_filter_class));
        mBuildInfo = BuildInfo.loadByName(sContext.getString(R.string.build_info_class));

        //初始化廣播
        mModel = new LauncherModel(this, mIconCache, mAppFilter);

        LauncherAppsCompat.getInstance(sContext).addOnAppsChangedCallback(mModel);

        // Register intent receivers
        IntentFilter filter = new IntentFilter();
        filter.addAction(Intent.ACTION_LOCALE_CHANGED);
        filter.addAction(SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED);
        // For handling managed profiles
        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED);
        filter.addAction(LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED);

        //註冊廣播
        sContext.registerReceiver(mModel, filter);
        UserManagerCompat.getInstance(sContext).enableAndResetCache();
    }

然後初始化手機韌體資訊物件DeviceProfile,初始化拖拽管理器DragController,然後初始化小部件管理器,載入佈局,初始化桌面各個控制元件,並且設定各個控制元件的位置:

public void layout(Launcher launcher) {
        FrameLayout.LayoutParams lp;
        boolean hasVerticalBarLayout = isVerticalBarLayout();
        final boolean isLayoutRtl = Utilities.isRtl(launcher.getResources());

        // Layout the search bar space
        View searchBar = launcher.getSearchDropTargetBar();
        lp = (FrameLayout.LayoutParams) searchBar.getLayoutParams();
        if (hasVerticalBarLayout) {
            // Vertical search bar space -- The search bar is fixed in the layout to be on the left
            //                              of the screen regardless of RTL
            lp.gravity = Gravity.LEFT;
            lp.width = searchBarSpaceHeightPx;

            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
            targets.setOrientation(LinearLayout.VERTICAL);
            FrameLayout.LayoutParams targetsLp = (FrameLayout.LayoutParams) targets.getLayoutParams();
            targetsLp.gravity = Gravity.TOP;
            targetsLp.height = LayoutParams.WRAP_CONTENT;

        } else {
            // Horizontal search bar space
            lp.gravity = Gravity.TOP;
            lp.height = searchBarSpaceHeightPx;

            LinearLayout targets = (LinearLayout) searchBar.findViewById(R.id.drag_target_bar);
            targets.getLayoutParams().width = searchBarSpaceWidthPx;
        }
        searchBar.setLayoutParams(lp);

        //其他省略
        ...

    }

這裡就是動態設定桌面各個控制元件的位置及寬高等屬性。當所有資訊初始化完成後,就開始呼叫mModel.startLoader方法來載入應用資料。下面我們詳細來講資料載入流程。

5.Launcher資料載入

資料載入主要是從LauncherModel中的startLoader方法開始,先看一下這個方法做的事情:

12

這裡的事情不多,主要是呼叫LoaderTask這個任務,LoaderTask實現了Runnable這個介面,因此首先執行潤run方法,我麼看一下這個run方法裡面做了哪些事情,

 public void run() {

            ...

            keep_running:
            {

                loadAndBindWorkspace();

                if (mStopped) {
                    break keep_running;
                }

                waitForIdle();

                ...

                loadAndBindAllApps();
            }

          ...

        }

在這個方法中主要是三件事,我們用時序圖表一下:

13

首先是執行loadAndBindWorkspace方法:

 private void loadAndBindWorkspace() {

            ...

            //判斷workspace是否已經載入
            if (!mWorkspaceLoaded) {
                loadWorkspace();
                synchronized (LoaderTask.this) {
                    if (mStopped) {
                        return;
                    }
                    mWorkspaceLoaded = true;
                }
            }

            // Bind the workspace
            bindWorkspace(-1);
        }

這裡面主要是執行loadWorkspace和bindWorkspace,也就是載入workspace的應用並且進行繫結。先看loadWorkspace方法,程式碼很多,我們只貼關鍵部分:

private void loadWorkspace() {
            //初始化一些值

            ...


            if ((mFlags & LOADER_FLAG_MIGRATE_SHORTCUTS) != 0) {
                // append the user's Launcher2 shortcuts
                Launcher.addDumpLog(TAG, "loadWorkspace: migrating from launcher2", true);
                LauncherAppState.getLauncherProvider().migrateLauncher2Shortcuts();
            } else {
                // Make sure the default workspace is loaded
                Launcher.addDumpLog(TAG, "loadWorkspace: loading default favorites", false);
                LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary();
            }

            synchronized (sBgLock) {
                //初始化一些值
                ...

                try {
                   //從資料庫查詢解析出來的所有應用資訊
                   ...

                    while (!mStopped && c.moveToNext()) {
                        try {
                            int itemType = c.getInt(itemTypeIndex);
                            boolean restored = 0 != c.getInt(restoredIndex);
                            boolean allowMissingTarget = false;
                            container = c.getInt(containerIndex);

                            switch (itemType) {
                                case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
                                case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:

                                    ...

                                    try {
                                        intent = Intent.parseUri(intentDescription, 0);
                                        ComponentName cn = intent.getComponent();
                                        if (cn != null && cn.getPackageName() != null) {
                                            //檢測資料庫(從xml檔案解析出來存入資料庫的)中取出來的app包是否存在
                                            boolean validPkg = launcherApps.isPackageEnabledForProfile(
                                                    cn.getPackageName(), user);
                                            //檢測資料庫(從xml檔案解析出來存入資料庫的)中取出來的app元件是否存在
                                            boolean validComponent = validPkg &&
                                                    launcherApps.isActivityEnabledForProfile(cn, user);

                                            if (validComponent) {

                                                ...

                                            } else if (validPkg) {

                                               ...

                                            } else if (restored) {

                                                ...

                                            } else if (launcherApps.isAppEnabled(
                                                    manager, cn.getPackageName(),
                                                    PackageManager.GET_UNINSTALLED_PACKAGES)) {

                                               ...

                                            } else if (!isSdCardReady) {

                                                ...

                                            } else {

                                                ...

                                            }
                                        } else if (cn == null) {
                                            // For shortcuts with no component, keep them as they are
                                            restoredRows.add(id);
                                            restored = false;
                                        }
                                    } catch (URISyntaxException e) {
                                        Launcher.addDumpLog(TAG,
                                                "Invalid uri: " + intentDescription, true);
                                        itemsToRemove.add(id);
                                        continue;
                                    }

                                    ...

                                    if (info != null) {
                                        info.id = id;
                                        info.intent = intent;
                                        info.container = container;
                                        info.screenId = c.getInt(screenIndex);
                                        info.cellX = c.getInt(cellXIndex);
                                        info.cellY = c.getInt(cellYIndex);
                                        info.rank = c.getInt(rankIndex);
                                        info.spanX = 1;
                                        info.spanY = 1;
                                        info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber);
                                        ...

                                        switch (container) {
                                            case LauncherSettings.Favorites.CONTAINER_DESKTOP:
                                            case LauncherSettings.Favorites.CONTAINER_HOTSEAT:
                                                sBgWorkspaceItems.add(info);
                                                break;
                                            default:
                                                // Item is in a user folder
                                                FolderInfo folderInfo =
                                                        findOrMakeFolder(sBgFolders, container);
                                                folderInfo.add(info);
                                                break;
                                        }
                                        sBgItemsIdMap.put(info.id, info);
                                    } else {
                                        throw new RuntimeException("Unexpected null ShortcutInfo");
                                    }
                                    break;

                                case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:

                                    ...

                                    break;

                                case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET:
                                case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET:

                                    ...
                                                                    break;
                            }
                        } catch (Exception e) {
                            Launcher.addDumpLog(TAG, "Desktop items loading interrupted", e, true);
                        }
                    }
                } finally {
                    ...
                }

                ...

                // Sort all the folder items and make sure the first 3 items are high resolution.
                for (FolderInfo folder : sBgFolders) {
                    Collections.sort(folder.contents, Folder.ITEM_POS_COMPARATOR);
                    int pos = 0;
                    for (ShortcutInfo info : folder.contents) {
                        if (info.usingLowResIcon) {
                            info.updateIcon(mIconCache, false);
                        }
                        pos++;
                        if (pos >= FolderIcon.NUM_ITEMS_IN_PREVIEW) {
                            break;
                        }
                    }
                }

                if (restoredRows.size() > 0) {
                    // Update restored items that no longer require special handling
                    ContentValues values = new ContentValues();
                    values.put(LauncherSettings.Favorites.RESTORED, 0);
                    contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values,
                            Utilities.createDbSelectionQuery(
                                    LauncherSettings.Favorites._ID, restoredRows), null);
                }

                if (!isSdCardReady && !sPendingPackages.isEmpty()) {
                    context.registerReceiver(new AppsAvailabilityCheck(),
                            new IntentFilter(StartupReceiver.SYSTEM_READY),
                            null, sWorker);
                }

                // Remove any empty screens
                ...

                // If there are any empty screens remove them, and update.
                if (unusedScreens.size() != 0) {
                    sBgWorkspaceScreens.removeAll(unusedScreens);
                    updateWorkspaceScreenOrder(context, sBgWorkspaceScreens);
                }

               ...
            }
        }

首先是呼叫loadDefaultFavoritesIfNecessary這個方法,來解析我們上面講的配置預設的桌面圖示的xml檔案,流程就是:初始化AutoInstallsLayout,然後呼叫LauncherProvider中的loadFavorites方法,在這個方法中呼叫AutoInstallsLayout中的loadLayout方法來解析配置的xml檔案,在AutoInstallsLayout中通過對小部件,圖示,資料夾等分類進行分辨解析,解析過程中如果有include標籤,則對相應的xml檔案進行解析,解析過程相對簡單,不在做詳細講解,解析過程中將解析的各種資訊儲存到資料庫中,以方便後面使用,當xml檔案解析完成後,開始讀取解析xml配置檔案儲存到資料庫的資料,讀取出來後,根據相應的型別(圖示,小部件,資料夾等)進行判斷,判斷系統中這個應用是否存在,是否可用,如果可用則生成相應物件並存儲到想定的map中,如果不存在則刪除資料庫中的資料,這樣整個判斷完成後資料庫中的資料就只剩下系統中存在的配置應用過了。

載入完配置應用圖示後,開始執行bindWorkspace方法繫結應用圖示到桌面,程式碼略過,我們看一下UML圖:

14

通過上面的時序圖,我們看到,首先執行過濾工作,比如這個圖示是在workspace中還是在Hotseat中,不同的位置放置不同的分類,然後進行排序處理,然後執行bindWorkspaceScreens方法來繫結手機有幾個螢幕,接著呼叫bindWorkspaceItems方法綁定當前螢幕的圖示、資料夾和小外掛資訊,最後呼叫繫結其他螢幕的應用圖示、資料夾和小外掛,關於繫結我們下一章再講。

接著執行LoadTask中的waitForIdle方法,改方法主要是等待載入資料結束。

最後執行loadAndBindAllApps方法來載入第二層的多有圖示資訊,看程式碼:

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

主要是如果已經載入了所有應用這只是執行繫結應用,如果沒有載入則執行載入操作。下面看載入操作:

private void loadAllApps() {

            ...

            final List<UserHandleCompat> profiles = mUserManager.getUserProfiles();

            // Clear the list of apps
            mBgAllAppsList.clear();
            for (UserHandleCompat user : profiles) {

                ...

               final List<LauncherActivityInfoCompat> apps = mLauncherApps.getActivityList(null, user);

                // Fail if we don't have any apps
                // TODO: Fix this. Only fail for the current user.
                if (apps == null || apps.isEmpty()) {
                    return;
                }

                // Create the ApplicationInfos
                for (int i = 0; i < apps.size(); i++) {
                    LauncherActivityInfoCompat app = apps.get(i);
                    // This builds the icon bitmaps.
                    mBgAllAppsList.add(new AppInfo(mContext, app, user, mIconCache));
                }

            ...

            // Huh? Shouldn't this be inside the Runnable below?
            final ArrayList<AppInfo> added = mBgAllAppsList.added;
            mBgAllAppsList.added = new ArrayList<AppInfo>();

            // Post callback on main thread
            mHandler.post(new Runnable() {
                public void run() {

                    final long bindTime = SystemClock.uptimeMillis();
                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.bindAllApplications(added);
                        if (DEBUG_LOADERS) {
                            Log.d(TAG, "bound " + added.size() + " apps in "
                                    + (SystemClock.uptimeMillis() - bindTime) + "ms");
                        }
                    } else {
                        Log.i(TAG, "not binding apps: no Launcher activity");
                    }
                }
            });
            ...

            loadAndBindWidgetsAndShortcuts(tryGetCallbacks(oldCallbacks), true /* refresh */);
            ...
        }

上面程式碼中通過mLauncherApps.getActivityList方法獲取所有應用啟動介面的一個物件列表,然後根據LauncherActivityInfoCompat來初始化對應的app物件,這樣就可以獲取手機中所有的應用列表。獲取完成後就執行繫結操作,最後呼叫loadAndBindWidgetsAndShortcuts方法載入繫結小部件和快捷方式到小部件介面。

public void 
            
           

相關推薦

墨香Launcher-資料載入

上一篇墨香帶你學Launcher之-概述,我已經介紹了Launcher的佈局以及相關的介面跳轉,今天我們繼續學習,按照計劃,我們開始學習Launcher啟動之資料載入,主要是圖示、Widget和資料夾的載入. 1.基礎知識 在介紹載入之前我先

墨香Launcher-繫結螢幕、圖示、資料夾和Widget

上一章我們講了Launcher的資料載入,包括:預設配置應用、資料夾以及widget的載入,所有應用的載入以及所有Widget的載入,資料載入完成後開始分批進行繪製到桌面上,包含預設配置bind,所有應用bind,所有小部件bind。下面我就從這幾個方面進

蘇蘇醬陪動態規劃——合唱團

1、問題重述      有 n 個學生站成一排,每個學生有一個能力值,牛牛想從這 n 個學生中按照順序選取 k 名學生,要求相鄰兩個學生的位置編號的差不超過 d,使得這 k 個學生的能力值的乘積最大,你能返回最大的乘積嗎? 2、題目分析        題目要求n各學生中

清風-H5+CSS3使用less維護rem佈局專案

M-web 掌握less的安裝和編譯 掌握less的基本語法 掌握在專案中使用less 掌握rem適配的原理 掌握rem+媒體查詢適配 掌握rem+flexible適配 課程內容 less 什麼是less 作為

LeiQ手把手搭部落格——VPS環境配置

I WANT MY OWN BLOG! 終於步入正題了!這次會帶大家把伺服器內部環境搭好。 在部落格入住前,把這間小屋先裝潢好!LNMP和AMH可以任選其一,LNMP純命令列,AMH帶了一套後臺面板與mysql管理工具,注意!×××不要兩個同時安裝,可能出

通過原始碼,手把手屬性動畫

主要內容:上篇側重介紹了ofFloat()方法,以及與動畫相關的方法、監聽,本節將繼續介紹剩下的 ofObject() 和 ofPropertyValuesHolder() 方法,以及相關的 TypeEvaluator 和 PropertyValue

通過原始碼,手把手屬性動畫

在 Android 3.0(API level 11) 之後,Google 為 Android添加了屬性動畫(Property Animation),該動畫系統是一個強大的框架,允許開發者對幾乎任何物件進行動畫。 由此可知,屬性動畫致力於為開發者提供更

微專案:一步一步使用SpringBoot入門

今天我們來使用JPA做分頁專案並且做講解 如果是新來的朋友請回上一篇 上一篇:微專案(一) maven整合 在pom檔案的dependencies依賴中匯入以下依賴 <dependency> <groupId>org.springframewor

寫Http框架——三個樣例深入理解AsyncTask

func implement oncreate 其它 層疊 worker dcl 例如 人員 這個標題大家不要奇怪,扯Http框架怎麽扯到AsyncTask去了,有兩個原因:首先是Http框架除了核心http理論外。其技術實現核心也是線程池 + 模板 +

CocosCreatorKUOKUO做隨機數遊戲1

開坑開坑啦,KUOKUO帶你入門CocosCreator,承諾不TJ,頂多慢一點{的console.log(滑稽)}本次引擎2.0.5 在我入門學習的時候是按照官方文件一條一條學習的,的確是有效。 但是我認為任務驅動的學習效率更高,例如,我開的坑。希望可以幫助更多的人入門CocosCreat

棟哥JavaJDBC

JDBC 什麼是JDBC? JDBC(Java Data Base Connectivity,java資料庫連線)是一種用於執行SQL語句 的JavaAPI,可以為多種關係資料庫提供統一訪問,它由一組用Java語言編寫的類和介面組成.JDBC提供了一種基準

解讀Spring Batch入手Spring Batch

width sync launch 3.0 edi 抽象 override ride 批次 前言   說得多不如show code。上一章簡單介紹了一下Spring Batch。本章將從頭到尾搭建一套基於Spring Batch(2.1.9)、Spring(3.0.5)、m

老侯Tensorflow19篇

    學習一門新技術,一定要有技巧,找到學習的捷徑就能事半功倍。現如今人工智慧產業發展迅

老司機玩轉面試3:Redis 高可用主從模式

![](https://cdn.geekdigging.com/Interview/mianshi_header_1.jpg) ## 前文回顧 建議前面文章沒看過的同學先看下前面的文章: [「老司機帶你玩轉面試(1):快取中介軟體 Redis 基礎知識以及資料持久化」](https://www.geek

老司機玩轉面試4:Redis 高可用哨兵模式

![](https://cdn.geekdigging.com/Interview/mianshi_header_1.jpg) ## 前文回顧 建議前面文章沒看過的同學先看下前面的文章: [「老司機帶你玩轉面試(1):快取中介軟體 Redis 基礎知識以及資料持久化」](https://www.geek

網絡遠程教育實施方案交流——網絡教育平臺項目的建設

商城 免費 數據 及其 技術 充值 互聯網產品 遠程教育 導出 網絡教育平臺項目的建設的方案能夠自建也能夠採購。但項目是否成功,並終於能夠落地發展,還須要業主方認真的調研和分析,最有效的方法就是利用項目管理的方法,從前期的需求分析、調研、可行性分析,立項

Google發布機器學習平臺Tensorflow遊樂場~玩神經網絡轉載

ima pdo androi 真的 技術 font 螺旋數據 本科 玩耍 Google發布機器學習平臺Tensorflow遊樂場~帶你玩神經網絡 原文地址:http://f.dataguru.cn/article-9324-1.html> 摘要:

Deep Learning論文筆記Sparse Filtering稀疏濾波

structure 分布 的確 tlab bolt 期望 有一個 尋找 mean Deep Learning論文筆記之(二)Sparse Filtering稀疏濾波 自己平時看了一些論文,但老感覺看完過後就會慢慢的淡忘,某一天重新拾起來的時候又好像沒有

負載均衡系列nginx

永遠 實例 表示 特性 反向代理服務器 依據 forward 訪問 子郵件 Nginx是一款輕量級的Web 服務器/反向代理服務器及電子郵件(IMAP/POP3)代理服務器,並在一個BSD-like 協議下發行 其特點是占有內存少,並發能力強,事實上nginx的並發能力

MongoDB學習java連接

代碼 core UC mongo bte 就是 ava jar包 lan 上一章完了下mongodb的安裝和IDE工具,現在開始使用java進行連接。 第一步:使用jar包, 這裏需要三個包,具體為啥我也不清楚,反正因為報錯,我就按照官方文檔一個個的都下載了。 鏈接:htt