android7.x Launcher3源码解析(3)---workspace和allapps加载流程

Launcher系列目录:

一、android7.x Launcher3源码解析(1)—启动流程

二、android7.x Launcher3源码解析(2)—框架结构

三、android7.x Launcher3源码解析(3)—workspace和allapps加载流程

前两篇博客分别对Lancher的启动和Launcher的框架结构进行了一些分析,这一篇,将着重开始分析界面的加载流程。

1、整体流程

先上一张整体的流程图吧。(图片看不清可以下载下来看或者右击新开个页面查看图片)

先从Launcher.java的onCreate方法开始,

protected void onCreate(Bundle savedInstanceState) {
    ......
    //建立LauncherAppState对象
    LauncherAppState.setApplicationContext(getApplicationContext());
    LauncherAppState app = LauncherAppState.getInstance();

    ......
    //建立LauncherModel对象
    mModel = app.setLauncher(this);

    //一些其他对象初始化
    ......
    setContentView(R.layout.launcher);
    setupViews();
    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());
            }
    }
    ......
}

重点调用了LauncherModel的startLoader的方法,startLoader里面,最重要的就是启动了LoaderTask,mLoaderTask = new LoaderTask(mApp.getContext(), synchronousBindPage);

我们接着分析LoaderTask的run方法。

public void run() {
            ......
            keep_running: {
                if (DEBUG_LOADERS) Log.d(TAG, "step 1: loading workspace");
                loadAndBindWorkspace();

                if (mStopped) {
                    break keep_running;
                }

                waitForIdle();

                // second step
                if (DEBUG_LOADERS) Log.d(TAG, "step 2: loading all apps");
                loadAndBindAllApps();

                waitForIdle();

                // third step
                if (DEBUG_LOADERS) Log.d(TAG, "step 3: loading deep shortcuts");
                loadAndBindDeepShortcuts();
            }

            ......
        }

这里一共就几步,loadAndBindWorkspace–>waitForIdle()—>loadAndBindAllApps()—>waitForIdle()—>loadAndBindDeepShortcuts()

3步加载流程里面都穿插了waitForIdle,这个方法是干嘛的呢?

private void waitForIdle() {
          ......
            synchronized (LoaderTask.this) {
                final long workspaceWaitTime = DEBUG_LOADERS ? SystemClock.uptimeMillis() : 0;

                mHandler.postIdle(new Runnable() {
                        public void run() {
                            synchronized (LoaderTask.this) {
                                mLoadAndBindStepFinished = true;
                                if (DEBUG_LOADERS) {
                                    Log.d(TAG, "done with previous binding step");
                                }
                                LoaderTask.this.notify();
                            }
                        }
                    });

                while (!mStopped && !mLoadAndBindStepFinished) {
                    try {
                        // Just in case mFlushingWorkerThread changes but we aren‘t woken up,
                        // wait no longer than 1sec at a time
                        this.wait(1000);
                    } catch (InterruptedException ex) {
                        // Ignore
                    }
                }
               ......
            }
        }

load数据时我们是去UI线程中处理的,UI线程正常情况下是不能阻塞的,否则有可能产生ANR,这将严重影响用户体验。所有这里LoaderTask在将结果发送给UI线程之后,为了保证界面绑定任务可以高效的完成,往往会将自己的任务暂停下来,等待UI线程处理完成。

分析下这个方法:

首先,创建一个UI线程闲时执行的任务,这个任务负责设置某些关键的控制标志,并将其通过PostIdle方法加入处理器的消息队列中。一旦任务得到执行,就会将mLoadAndBindStepFinished 置为true,以控制即将来临的有条件的无限等待。 最后 设置一个有条件的无限等待,等待来自UI线程的指示。

2、workspace的加载流程

从总流程图上可以看到,workspace的加载流程主要分为loadWorkspace();bindWorkspace(mPageToBindFirst);

a、loadWorkspace()

loadWorkspace()的代码实在是太多了,这里就不全部贴出来了,主要功能就是负责从数据库表中读取数据并转译为Launcher桌面项的数据结构。

以下为步骤:

1、进行一些预处理

2、加载默认值

LauncherAppState.getLauncherProvider().loadDefaultFavoritesIfNecessary()

之前Launcher2loadDefaultFavoritesIfNecessary这个方法,是这样加载默认布局的:

workspaceResId = sp.getInt(DEFAULT_WORKSPACE_RESOURCE_ID, R.xml.default_workspace);

但是Launcher3中,是这样加载的:

int workspaceResId = partnerRes.getIdentifier(Partner.RES_DEFAULT_LAYOUT,
                            "xml", partner.getPackageName());

这个地方,我暂时还没理解,到底默认布局是哪个?

3、初始化数据

清空之前的内存数据

/** Clears all the sBg data structures */
        private void clearSBgDataStructures() {
            synchronized (sBgLock) {
                sBgWorkspaceItems.clear();
                sBgAppWidgets.clear();
                sBgFolders.clear();
                sBgItemsIdMap.clear();
                sBgWorkspaceScreens.clear();
            }
        }

4、查询ContentProvider,返回favorites表的结果集

final HashMap<String, Integer> installingPkgs = PackageInstallerCompat
                        .getInstance(mContext).updateAndGetActiveSessionCache();

                final ArrayList<Long> itemsToRemove = new ArrayList<Long>();
                final ArrayList<Long> restoredRows = new ArrayList<Long>();
                final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI;
                if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri);
                final Cursor c = contentResolver.query(contentUri, null, null, null, null);

5、根据不同的类型,将数据保存到对应的arrayList中。

类型包含下面这几种:

ITEM_TYPE_APPLICATION
ITEM_TYPE_SHORTCUT

ITEM_TYPE_FOLDER

ITEM_TYPE_APPWIDGET
ITEM_TYPE_CUSTOM_APPWIDGET

根据以上类型,将数据保存到一下全局变量里面

sBgItemsIdMap,sBgWorkspaceItemsm,sBgAppWidgets,sBgFolders

另外,如果有空的文件夹、空的屏幕,也会delete掉,最终,把所有屏幕加载进全局变量sBgWorkspaceScreens中。

......
           // Remove any empty folder
                    for (long folderId : LauncherAppState.getLauncherProvider()
                            .deleteEmptyFolders()) {
                        sBgWorkspaceItems.remove(sBgFolders.get(folderId));
                        sBgFolders.remove(folderId);
                        sBgItemsIdMap.remove(folderId);
                    }

                    ......

 sBgWorkspaceScreens.addAll(loadWorkspaceScreensDb(mContext));

                // Remove any empty screens
                ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgWorkspaceScreens);
                for (ItemInfo item: sBgItemsIdMap) {
                    long screenId = item.screenId;
                    if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP &&
                            unusedScreens.contains(screenId)) {
                        unusedScreens.remove(screenId);
                    }
                }

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

b、bindWorkspace

bindWorkspace的功能是将上面获取到的数据由Launcher显示出来。

步骤:

1、首先复制数据:

            synchronized (sBgLock) {
                workspaceItems.addAll(sBgWorkspaceItems);
                appWidgets.addAll(sBgAppWidgets);
                orderedScreenIds.addAll(sBgWorkspaceScreens);

                folders = sBgFolders.clone();
                itemsIdMap = sBgItemsIdMap.clone();
            }

2、装载数据并排序

            //装载桌面项数据
            filterCurrentWorkspaceItems(currentScreenId, workspaceItems, currentWorkspaceItems,
                    otherWorkspaceItems);
            //装载widget
            filterCurrentAppWidgets(currentScreenId, appWidgets, currentAppWidgets,
                    otherAppWidgets);
            //装载文件夹
            filterCurrentFolders(currentScreenId, itemsIdMap, folders, currentFolders,
                    otherFolders);
            //排序
            sortWorkspaceItemsSpatially(currentWorkspaceItems);
            sortWorkspaceItemsSpatially(otherWorkspaceItems);

3、开始绑定

就上个流程图吧。

3、应用程序apps的加载

加载apps的主要流程,最上面那张流程图已经给出了,如果所有app没有加载,则loadAllApps();,不然直接onlyBindAllApps();

1、loadAllApps()

根据代码,画了下流程图,但是我不明白userProfile是个什么鬼?

2、onlyBindAllApps()

这里的函数比绑定workspace简单多了,直接通知Launcher绑定

            Runnable r = new Runnable() {
                public void run() {
                    final long t = SystemClock.uptimeMillis();
                    final Callbacks callbacks = tryGetCallbacks(oldCallbacks);
                    if (callbacks != null) {
                        callbacks.bindAllApplications(list);
                        callbacks.bindAllPackages(widgetList);
                    }
                    if (DEBUG_LOADERS) {
                        Log.d(TAG, "bound all " + list.size() + " apps from cache in "
                                + (SystemClock.uptimeMillis()-t) + "ms");
                    }
                }
            };

看一下Launcher的bindAllApplications函数,当然这个函数在loadAllApps函数里面也有,就是如何绑定数据来显示呢?

Launcher.java的bindAllApplications函数里面会给AllAppsContainerView设置数据

if (mAppsView != null) {
            mAppsView.setApps(apps);
        }

再跟代码到AllAppsContainerView,

public void setApps(List<AppInfo> apps) {
        mApps.setApps(apps);
    }

这个apps在哪里用到呢?比较明显的就是onFinishInflate()函数,

.....
        // Load the all apps recycler view
        mAppsRecyclerView = (AllAppsRecyclerView) findViewById(R.id.apps_list_view);
        mAppsRecyclerView.setApps(mApps);
        mAppsRecyclerView.setLayoutManager(mLayoutManager);
        mAppsRecyclerView.setAdapter(mAdapter);
        mAppsRecyclerView.setHasFixedSize(true);
.....

设置给了recyclerView,很显然,Launcher的应用程序界面就是一个自定义的RecyclerView,给这个recyclerview绑定的adpter是AllAppsGridAdapter,看一下这个adpter的onCreateViewHolder函数,很明显,这里根据不同的类型加载了不同的布局(应用程序界面有app和文件夹,头上还有个搜索框,用recyclerview的这个功能是最容易实现的),关于Recyclerview如何可以根据不同的类型加载不同的布局,可以参考我很久之前写的博客 RecyclerView的不同position加载不同View实现



好了,Launcher3的workspace和应用程序apps的加载流程就讲到这,后面还会对Launcher里面的内容做具体的分析。

时间: 2024-10-05 23:04:21

android7.x Launcher3源码解析(3)---workspace和allapps加载流程的相关文章

Tomcat源码分析——server.xml文件的加载与解析

前言 作为Java程序员,对于tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载和解析进行分析. 加载过程分析 Bootstrap的load方法用于加载tomcat的server.xml,实际是通过反射调用Catalina的load方法,代码如下: /** * Load daemon. */ private void load(String[] arguments) throws Exception { // Call

6.Sentinel源码分析—Sentinel是如何动态加载配置限流的?

Sentinel源码解析系列: 1.Sentinel源码分析-FlowRuleManager加载规则做了什么? 2. Sentinel源码分析-Sentinel是如何进行流量统计的? 3. Sentinel源码分析- QPS流量控制是如何实现的? 4.Sentinel源码分析- Sentinel是如何做到降级的? 5.Sentinel源码分析-Sentinel如何实现自适应限流? 有时候我们做限流的时候并不想直接写死在代码里面,然后每次要改规则,或者增加规则的时候只能去重启应用来解决.而是希望能

Redis源码解析:15Resis主从复制之从节点流程

Redis的主从复制功能,可以实现Redis实例的高可用,避免单个Redis 服务器的单点故障,并且可以实现负载均衡. 一:主从复制过程 Redis的复制功能分为同步(sync)和命令传播(commandpropagate)两个操作: 同步操作用于将从节点的数据库状态更新至主节点当前所处的数据库状态: 命令传播操作则用于在主节点的数据库状态被修改,导致主从节点的数据库状态不一致时,让主从节点的数据库重新回到一致状态: 1:同步 当客户端向从节点发送SLAYEOF命令,或者从节点的配置文件中配置了

android源码解析之(十)--&gt;Launcher启动流程

Launcher程序就是我们平时看到的桌面程序,它其实也是一个android应用程序,只不过这个应用程序是系统默认第一个启动的应用程序,这里我们就简单的分析一下Launcher应用的启动流程. 不同的手机厂商定制android操作系统的时候都会更改Launcher的源代码,我们这里以android23的源码为例大致的分析一下Launcher的启动流程. 通过上一篇文章,我们知道SystemServer进程主要用于启动系统的各种服务,二者其中就包含了负责启动Launcher的服务,Launcher

android源码解析(二十七)--&gt;HOME事件流程

上一篇文章中我们介绍了android系统的截屏事件,由于截屏事件是一种系统全局处理事件,所以事件的处理逻辑不是在App中执行,而是在PhoneWindowManager中执行.而本文我们现在主要讲解android系统中HOME按键的事件处理,和截屏事件类似,这里的HOME按键也是系统级别的按键事件监听,所以其处理事件的逻辑也应该和截屏事件处理流程类似,从上一篇文章的分析过冲中我们不难发现,系统级别的按键处理逻辑其实都是在PhoneWindowManager中,所以HOME按键的处理逻辑也是在Ph

thinkphp5源码剖析系列1-类的自动加载机制

前言 tp5想必大家都不陌生,但是大部分人都停留在应用的层面,我将开启系列随笔,深入剖析tp5源码,以供大家顺利进阶.本章将从类的自动加载讲起,自动加载是tp框架的灵魂所在,也是成熟php框架的必备功能 入口 // [ 应用入口文件 ] namespace think; // 加载基础文件 require __DIR__ . '/../thinkphp/base.php'; base.php <?php // +------------------------------------------

Tomcat源码分析——server.xml文件的加载

前言 作为Java程序员,对于tomcat的server.xml想必都不陌生.本文基于Tomcat7.0的Java源码,对server.xml文件是如何加载的进行分析. 源码分析 Bootstrap的load方法是加载tomcat的server.xml的入口,load方法实际通过反射调用了Catalina的load方法,见代码清单1. 代码清单1 /** * Load daemon. */ private void load(String[] arguments) throws Exceptio

0002 - Spring MVC 拦截器源码简析:拦截器加载与执行

1.概述 Spring MVC中的拦截器(Interceptor)类似于Servlet中的过滤器(Filter),它主要用于拦截用户请求并作相应的处理.例如通过拦截器可以进行权限验证.记录请求信息的日志.判断用户是否登录等. 2.简单示例 2.1.继承 HandlerInterceptorAdapter 抽象类实现一个拦截器.代码如下: public class DemoInterceptor extends HandlerInterceptorAdapter { @Override    pu

android源码解析(二十三)--&gt;Android异常处理流程

前面的几篇文章都是讲解的android中的窗口显示机制,包括Activity窗口加载绘制流程,Dialog窗口加载绘制流程,PopupWindow窗口加载绘制流程,Toast窗口加载绘制流程等等.整个Android的界面显示的原理都是类似的,都是通过Window对象控制View组件,实现加载与绘制流程. 这篇文章休息一下,不在讲解Android的窗口绘制机制,穿插的讲解一下Android系统的异常处理流程.O(∩_∩)O哈哈~ 开发过android项目的童鞋对android系统中错误弹窗,for