基于Android7.0的Launcher3源码分析(1)——框架设计分析


    模块源码路径:packages/apps/Launcher3 。

    最开始先从架构设计入手大致介绍一下模块的基本构成。Launcher 模块基本上是按照改进版的MVC架构进行设计的。

    Model层主要负责数据的加载和处理,然后通过回调接口,把数据传递给Controller层,最后由Controller层通知View的显示与更新。其中,View层与Model层只有轻度的耦合,View层有些操作会直接通过Model层来更新数据。用户的交互事件主要在Controller层完成。
    作为Controller层的 Launcher  就是程序的主入口,继承自Activity。本文主要关注各个层之间是如何建立联系的。其他内容后面再仔细分析。onCreate里将各个层之间的联系建立起来。

    public class Launcher extends Activity
            implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
                       View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
        ......
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
     
            //建立Model层与Controller层的关联
            LauncherAppState app = LauncherAppState.getInstance();
            mModel = app.setLauncher(this);
     
            ......
     
            setContentView(R.layout.launcher);
     
            ......
     
            //建立View层与Controller层的关联
            setupViews();
            mDeviceProfile.layout(this); //根据默认配置参数对控件布局作调整
     
            ......
     
            //加载桌面用于显示的数据
            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());
                }
            }
     
            ......
        }
    }


一、Model层与Controller层建立关联
    首先获取了LauncherAppState类的单例对象。该对象初始化时对桌面的默认配置数据进行了初始化和计算。如:桌面图标和文字大小等。这个后面的文章再仔细分析。
    除此之外,这个单例中持有一些重要类的实例对象,其中包括LauncherModel的实例对象。通过app.setLauncher(this)将Controller层和Model层进行关联,并把LauncherModel的实例对象传递给Launcher的成员变量mModel,方便Launcher对Model层进行一些状态的更新。
    前面说过,这两个层级之间通过回调接口进行数据交互。
    Launcher实现了两个接口:1、LauncherModel.Callbacks;2、LauncherProviderChangeListener
    其中,LauncherModel.Callbacks用于和LauncherModel进行数据交互;LauncherProviderChangeListener用于和LauncherProvider进行交互。

    public class LauncherAppState {
        ......
     
        LauncherModel setLauncher(Launcher launcher) {
            getLauncherProvider().setLauncherProviderChangeListener(launcher);
            mModel.initialize(launcher);
            mAccessibilityDelegate = ((launcher != null) && Utilities.ATLEAST_LOLLIPOP) ?
                new LauncherAccessibilityDelegate(launcher) : null;
            return mModel;
        }
     
        ......
    }


1.1、Launcher与LauncherProvider通过接口类LauncherProviderChangeListener建立关联。
    将Launcher作为接口实现类传递给LauncherProvider 的成员对象mListener,需要时就可以通过该成员对象向Launcher传递数据和状态更新。

    public class LauncherProvider extends ContentProvider {
        ......
        @Thunk LauncherProviderChangeListener mListener;
     
        public void setLauncherProviderChangeListener(LauncherProviderChangeListener listener) {
            mListener = listener;
            mOpenHelper.mListener = mListener;
        }
     
        ...
    }
     
    public interface LauncherProviderChangeListener {
     
        public void onLauncherProviderChange();
     
        public void onSettingsChanged(String settings, boolean value);
     
        public void onAppWidgetHostReset();
    }


1.2、Launcher与LauncherModel通过接口Callbacks建立关联
    将Launcher的实例对象作为接口实现类传递给LauncherModel,这里采用弱引用的形式持有Launcher的对象。这样在需要的时候,LauncherModel就可以通过mCallbacks调用相应接口方法,把数据传递给Launcher。
    至此,Model层与Controller层关联完毕。

    public class LauncherModel extends BroadcastReceiver
            implements LauncherAppsCompat.OnAppsChangedCallbackCompat {
        ......
     
        public void initialize(Callbacks callbacks) {
            synchronized (mLock) {
                // Disconnect any of the callbacks and drawables associated with ItemInfos on the
                // workspace to prevent leaking Launcher activities on orientation change.
                unbindItemInfosAndClearQueuedBindRunnables();
                mCallbacks = new WeakReference<Callbacks>(callbacks);
            }
        }
     
        ......
     
        public interface Callbacks {
            public boolean setLoadOnResume();
            public int getCurrentWorkspaceScreen();
            public void startBinding();
            public void bindItems(ArrayList<ItemInfo> shortcuts, int start, int end,
                                  boolean forceAnimateIcons);
            public void bindScreens(ArrayList<Long> orderedScreenIds);
            public void bindAddScreens(ArrayList<Long> orderedScreenIds);
            public void bindFolders(LongArrayMap<FolderInfo> folders);
            public void finishBindingItems();
            public void bindAppWidget(LauncherAppWidgetInfo info);
            public void bindAllApplications(ArrayList<AppInfo> apps);
            public void bindAppsAdded(ArrayList<Long> newScreens,
                                      ArrayList<ItemInfo> addNotAnimated,
                                      ArrayList<ItemInfo> addAnimated,
                                      ArrayList<AppInfo> addedApps);
            public void bindAppsUpdated(ArrayList<AppInfo> apps);
            public void bindShortcutsChanged(ArrayList<ShortcutInfo> updated,
                    ArrayList<ShortcutInfo> removed, UserHandleCompat user);
            public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets);
            public void bindRestoreItemsChange(HashSet<ItemInfo> updates);
            public void bindWorkspaceComponentsRemoved(
                    HashSet<String> packageNames, HashSet<ComponentName> components,
                    UserHandleCompat user);
            public void bindAppInfosRemoved(ArrayList<AppInfo> appInfos);
            public void notifyWidgetProvidersChanged();
            public void bindWidgetsModel(WidgetsModel model);
            public void bindSearchProviderChanged();
            public boolean isAllAppsButtonRank(int rank);
            public void onPageBoundSynchronously(int page);
            public void dumpLogsToLocalData();
        }
     
        ......
    }


二、View层与Controller层建立关联
    MVC框架中建议View通过XML语言来描述,Launcher 的主布局通过 launcher.xml 来描述。setContentView(R.layout.launcher) ,将xml布局进行解析和加载。
    然后通过 setupViews() 进行一系列的 findViewById 操作,将控件们进行实例化,并给控件注册事件监听。Launcher 持有这些控件的实例对象的引用,用于通知View的显示与刷新。
    mDeviceProfile.layout(this) 根据参数对控件进行一些位置的调整。

    public class Launcher extends Activity
            implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
                       View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
        ......
     
        private void setupViews() {
            final DragController dragController = mDragController;
     
            mLauncherView = findViewById(R.id.launcher);
            mFocusHandler = (FocusIndicatorView) findViewById(R.id.focus_indicator);
            mDragLayer = (DragLayer) findViewById(R.id.drag_layer);
            mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace);
            mWorkspace.setPageSwitchListener(this);
            mPageIndicators = mDragLayer.findViewById(R.id.page_indicator);
     
            mLauncherView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            mWorkspaceBackgroundDrawable = getResources().getDrawable(R.drawable.workspace_bg);
     
            // Setup the drag layer
            mDragLayer.setup(this, dragController);
     
            // Setup the hotseat
            mHotseat = (Hotseat) findViewById(R.id.hotseat);
            if (mHotseat != null) {
                mHotseat.setOnLongClickListener(this);
            }
     
            // Setup the overview panel
            setupOverviewPanel();
     
            // Setup the workspace
            mWorkspace.setHapticFeedbackEnabled(false);
            mWorkspace.setOnLongClickListener(this);
            mWorkspace.setup(dragController);
            dragController.addDragListener(mWorkspace);
     
            // Get the search/delete bar
            mSearchDropTargetBar = (SearchDropTargetBar)
                    mDragLayer.findViewById(R.id.search_drop_target_bar);
     
            // Setup Apps and Widgets
            mAppsView = (AllAppsContainerView) findViewById(R.id.apps_view);
            mWidgetsView = (WidgetsContainerView) findViewById(R.id.widgets_view);
            if (mLauncherCallbacks != null && mLauncherCallbacks.getAllAppsSearchBarController() != null) {
                mAppsView.setSearchBarController(mLauncherCallbacks.getAllAppsSearchBarController());
            } else {
                mAppsView.setSearchBarController(new DefaultAppSearchController());
            }
     
            // Setup the drag controller (drop targets have to be added in reverse order in priority)
            dragController.setDragScoller(mWorkspace);
            dragController.setScrollView(mDragLayer);
            dragController.setMoveTarget(mWorkspace);
            dragController.addDropTarget(mWorkspace);
            if (mSearchDropTargetBar != null) {
                mSearchDropTargetBar.setup(this, dragController);
                mSearchDropTargetBar.setQsbSearchBar(getOrCreateQsbBar());
            }
     
            if (TestingUtils.MEMORY_DUMP_ENABLED) {
                TestingUtils.addWeightWatcher(this);
            }
        }
    }


三、Launcher通知LauncherModel 开始加载数据

    首先判断一个变量mRestoring,该值用于确认Launcher启动时,是正常启动还是系统资源紧张导致Launcher被kill之后的重启。如果mRestoring为true,表示是被kill之后的重启,那么通过onSaveInstanceState保存下来的数据,恢复Launcher被kill之前的状态,不走这段重新加载数据的代码。如果mRestoring为false,则表示正常启动,那么进入数据加载的流程。
    这里的DISABLE_SYNCHRONOUS_BINDING_CURRENT_PAGE是常量开关,默认是false,这里不用管它,数据加载走的代码是 mModel.startLoader(mWorkspace.getRestorePage())。
    数据加载完毕后,通过 LauncherModel.Callbacks 接口的回调方法把数据传递给Launcher,Launcher再把数据跟View绑定进行显示,这个过程比较复杂。后面会专门写一篇文章。

    public class Launcher extends Activity
            implements View.OnClickListener, OnLongClickListener, LauncherModel.Callbacks,
                       View.OnTouchListener, PageSwitchListener, LauncherProviderChangeListener {
        ......
     
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            ......
     
            //加载桌面用于显示的数据
            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());
                }
            }
     
            ......
        }
    }



至此,Launcher的框架及联系就over了。如有问题,欢迎讨论。
下一篇将会详细讲解 初始化过程中 图标和字体大小等显示相关参数是如何初始化和处理的

posted @   最好不过如今  阅读(46)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
点击右上角即可分享
微信分享提示