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

    从事Android rom一年多,一直在负责 Launcher 相关的工作。最近打算写些文章记录下自己对这个模块的理解和源码实现的一些解析。
这些文章将会基于 Android 7.0 的 Launcher3 源码进行分析。
    模块源码路径: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了。如有问题,欢迎讨论。
下一篇将会详细讲解 初始化过程中 图标和字体大小等显示相关参数是如何初始化和处理的

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值