android M Launcher之LauncherModel

Posted _solary

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android M Launcher之LauncherModel相关的知识,希望对你有一定的参考价值。

众所周知 LauncherModel在Launcher中所占的位置,它相当于Launcher的数据中心,Launcher的桌面以及应用程序菜单中所需的数据像 桌面小部件的信息、快捷方式信息、文件信息、以及一些比较特殊的桌面页的信息等都是由它提供,因此我们这里来分析下LauncherModel的工作流程。

 一、LauncherModel的创建

要了解LauncherModel,我们要从LauncherModel的实例化开始。
LauncherModel是在Launcher应用程序被创建的时候,由LauncherApplication通过调用LauncherAppState的初始化方法实现的,即由LauncherAppState的getInstance方法调用LauncherAppState
的构造方法来实现,因此我们需要去看下LauncherAppState的构造方法

1、创建LauncherModel的准备工作
LauncherModel作为中心枢纽,不可避免的要与大量的图片打交道。这些图片包括Launcher所管理的医用程序图标,以及一些桌面小部件的预览图标,Launcher为他们准备了一个图片缓存区(IconCache),因此LauncherModel首先需要持有IconCache的实例,另外,如果Launcher并不希望那么多的应用程序展示在其中,所以它还需要一个应用程序的筛选器实例(AppFilter)。于是,LauncherModel的准备工作实际上就是IconCache和AppFilter的创建过程。

 mIconCache = new IconCache(sContext, mInvariantDeviceProfile);

...

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

中间的代码都省略一下只看和Launchermodel有关的。

2、创建LauncherModel的实例
在完成了以上准备工作后,就可以进行LauncherModel的实例创建了

mModel = new LauncherModel(this, mIconCache, mAppFilter);

3、LauncherModel的一些设置

LauncherModel本身是一个广播接收器的子类,LauncherAppState在创建的时候会为它设置一些广播,以便让LauncherModel的实例能够处理自己的广播:

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

从上面代码可以看出,LauncherModel本身需要关注设备的语种,设备配置变化以及搜索相关的变化,这是因为Launcher需要根据设备配置信息的改变来改变加载在桌面的图标、标题的信息,例如语言改变后 Launcher会马上生效。

到这里 LauncherModel的创建过程就完成了,接下来看下LauncherModel的构造函数。

二 、LauncherModel的构造函数

上图展示了LauncherModel的创建过程,可以看出LauncherModel的运行需要依赖一些条件。
如下

1、有些应用程序会通过其配置把自己安装到外部存储器上,因此Launcher在管理应用程序的时候就需要额外关注这个存储器是否是一个可以被移除的设备

mAppsCanBeOnRemoveableStorage = Environment.isExternalStorageRemovable();

isExternalStorageRemovable这个方法将返回这个外部存储器的性质。

2、Launcher3是在android4.4以后才提供的全新桌面应用程序,因此我们需要考虑从Launcher2的升级,其中一个重要的部分就是将Launcher2的数据库数据迁移并升级到Launcher3中,并适配Launcher3的数据库结构。在Launcher3中通过字符串的配置将Launcher2数据库的URL保持起来后,LauncherModel在创建的时候通过此字符串的配置获取访问Launcher2数据库的URL,如下

 String oldProvider = context.getString(R.string.old_launcher_provider_uri);

3、有了访问Launcher2数据库的URL,接下来要做的是判断Launcher2的数据库是否还存在。

 // This may be the same as MIGRATE_AUTHORITY, or it may be replaced by a different
        // resource string.
        //获取Launcher2数据库的Author信息
        String redirectAuthority = Uri.parse(oldProvider).getAuthority();
        //通过特定的Author获取特定的数据库组件信息
        ProviderInfo providerInfo =
                context.getPackageManager().resolveContentProvider(MIGRATE_AUTHORITY, 0);
        ProviderInfo redirectProvider =
                context.getPackageManager().resolveContentProvider(redirectAuthority, 0);

        Log.d(TAG, "Old launcher provider: " + oldProvider);
        //判断Launcher2的数据库组件是否存在
        mOldContentProviderExists = (providerInfo != null) && (redirectProvider != null);

这里主要的目的是通过mOldContentProviderExists 保持当前是否存在Launcher2数据库组件,因为只有这些组件存在,才可以保证Launcher2数据库存在。

4、最后LauncherModel还需要保持一些对于自身工作有着深远影响的信息,如图片缓存区、用户管理器等信息

 //保存Launcher的运行状态
        mApp = app;
        //支持后台加载数据的列表
        mBgAllAppsList = new AllAppsList(iconCache, appFilter);
        //图片缓存
        mIconCache = iconCache;

        //应用程序兼容管理器
        mLauncherApps = LauncherAppsCompat.getInstance(context);
        //保存当前用户管理器
        mUserManager = UserManagerCompat.getInstance(context);

到这里LauncherModel就实例化完成了,当LauncherAppState实例化流程完成后,LauncherModel就可以投入运行,等待来自其他组件的调度。

三、与LauncherModel的沟通方式

LauncherModel作为Launcher的中心枢纽,它给Launcher的各个组件提供了三类借口,以便更新Launcher的界面等信息,这些借口包括LauncherModel操作的回调借口,广播接口和应用程序内部接口。

1、LauncherModel操作的回调接口

LauncherModel操作的回调接口主要使用在一下场景中。

  • Launcher在启动时,需要桌面的数据,这些数据的形成需要由LauncherModel完成,在数据不断被完善的过程中,LauncherModel需要通过这些接口输送已经完成的数据

  • 在Launcher使用的过程中,我们可能会改变桌面上的元素配置,比如手动往桌面上添加快捷方式,每一次操作也无一例外的需要通过LauncherModel来完成。操作完成后LauncherModel也需要通过这些接口通知界面更新。

  • 第三方应用程序通过Launcher提供的接口往桌面上添加或者从桌面上删除摸个快捷方式,这个操作由Launcher提供的广播接收器将请求转发到LauncherModel来处理,LauncherModel完成相关数据处理后,将会通过这些接口通知界面更新。

以上就是这些回调接口的作用以及使用场景,需要注意的是,这些接口并非由LauncherModel实现,而是由Launcher的组件实现并注册到LauncherModel中,从而实现这样的沟通机制。

下面分析Launcher怎么注册到LauncherModel中

① 注册回调接口

在Launcher中,很多组件都需要与LauncherModel建立联系,而这些联系多数是通过Launcher的主Activity(Launcher.java)来实现的,因此Launcher需要实现LauncherModel的回调接口,LauncherModel在执行某个操作的时候,会将执行的过程和结果通过回调的方式告诉Launcher,下面看下Launcher是如何将接口注册到LauncherModel中。

Launcher的入口Activity在创建的时候,通过LauncherAppState的setLauncher方法开始注册接口的过程,如下

mModel = app.setLauncher(this);

LauncherAppState的setLauncher方法除了完成注册以外,还将LauncherModel实例的引用返回到Launcher。

在LauncherAppState的setLauncher方法中调用LauncherModel的initialize方法,将接口实现的引用注册到LauncherModel中,并返回已经完成初始化的LauncherModel的引用。比较绕口 看代码就明白

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

在LauncherModel的initialize方法中,实际上只是使用Launcher的接口实现部分,并将接口实现的引用放在一个弱引用中,从而完成初始化过程
如下

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

这样Launcher和LauncherModel中的回调方法就建立起了联系,LauncherModel的所有操作结果都会通过callbacks中定义的各个回调接口中的方法通知给Launcher,并由Launcher分发给不同的桌面组件或者Launcher自身。

② 回调接口的定义
上面了解了接口的注册过程,接下来我们看下接口的具体内容。
在LauncherModel中定义了一个名叫callbacks的接口,它体现了Launchermodel在操作的过程中可能产生的中间过程以及动作,方便LauncherModel获取实现方的运行状态以决定其操作的过程。每个以接口的含义请参考这篇博客

LauncherModel.Callbacks接口

2、广播接口

系统也可以通过发送广播来驱动LauncherModel工作,当LauncherModel接收到一些广播的时候,会启动界面刷新。LauncherModel本身就是一个广播接收器,根据Android的管理,LauncherModel通过自身实现的OnReceive方法来接收来自不同地方的广播

 public void onReceive(Context context, Intent intent) 
        if (DEBUG_RECEIVER) Log.d(TAG, "onReceive intent=" + intent);

        final String action = intent.getAction();
        if (Intent.ACTION_LOCALE_CHANGED.equals(action)) 
            // If we have changed locale we need to clear out the labels in all apps/workspace.
            forceReload();
         else if (SearchManager.INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED.equals(action)) 
            Callbacks callbacks = getCallback();
            if (callbacks != null) 
                callbacks.bindSearchProviderChanged();
            
         else if (LauncherAppsCompat.ACTION_MANAGED_PROFILE_ADDED.equals(action)
                || LauncherAppsCompat.ACTION_MANAGED_PROFILE_REMOVED.equals(action)) 
            UserManagerCompat.getInstance(context).enableAndResetCache();
            forceReload();
        
    

对于LauncherModel来说,系统的语言配置属性变化或者设备中的mcc码配置发生了改变,需要对桌面上的组件产生影响,例如,当语言配置属性发生改变的时候,设备上的显示信息需要更新成相应的语言,因此我们需要对Launcher管理的应用程序的快捷方式等桌面组件进行一次整体更新。

3、LauncherModel的应用程序级别接口

LauncherModel作为Launcher的枢纽,绝大多数时间是在处理Launcher应用程序通过调用LauncherModel的接口发来的请求,比如当第三方应用程序安装通过接口往桌面上添加快捷方式的时候,当用户安装或者卸载某个应用程序的时候,以及当Launcher启动初期时,都需要LauncherModel为Launcher应用程序提供必要的接口以及正确的数据。

下面按接口来看下他们的实现

① 启动加载任务startLoader
这是一个用于指示LauncherModel进行桌面以及应用程序菜单数据加载的接口。Launcher在启动或者被恢复的时候,Launcher都会通过这个接口来进行数据加载。他的声明如下

public void startLoader(int synchronousBindPage, int loadFlags) ;

它没有返回值,因为在加载的过程中,会通过Launcher注册的回调接口得到数据处理的中间过程
输入参数的含义

  • synchronousBindPage 这是一个整型的输入参数,调用方通过定制这个参数的值来指示LauncherModel加载对应的桌面页数据,当输入-1001时(在pageview中定义)将会对当前Launcher上维护的所有桌面页上的数据进行加载。
  • loadFlags 这是一个整型的输入参数,调用者通过这个参数来指示LauncherModel的加载任务应该执行的动作,LauncherModel提供了3中不同的选项

    1. LOADER_FLAG_CLEAR_WORKSPACE 用于指示LauncherModel对synchronousBindPage 参数指定的桌面数据进行清理
    2. LOADER_FLAG_MIGRATE_SHORTCUTS 用于指示LauncherModel对Launcher2的快捷方式数据进行迁移。
    3. LOADER_FLAG_NONE 用于指示LauncherModel对Launcher3的桌面数据进行一次加载或者刷新。
   public void startLoader(int synchronousBindPage, int loadFlags) 
        // Enable queue before starting loader. It will get disabled in Launcher#finishBindingItems
        InstallShortcutReceiver.enableInstallQueue();
        synchronized (mLock) 
            // Clear any deferred bind-runnables from the synchronized load process
            // We must do this before any loading/binding is scheduled below.
            synchronized (mDeferredBindRunnables) 
                mDeferredBindRunnables.clear();
            

            // 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.
                stopLoaderLocked();
                mLoaderTask = new LoaderTask(mApp.getContext(), loadFlags);
                if (synchronousBindPage != PagedView.INVALID_RESTORE_PAGE
                        && mAllAppsLoaded && mWorkspaceLoaded && !mIsLoaderTaskRunning) 
                    mLoaderTask.runBindSynchronousPage(synchronousBindPage);
                 else 
                    sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                    sWorker.post(mLoaderTask);
                
            
        
    

具体实现如上。
在Launcher内部,所有的桌面数据的操作请求都通过这个方法来处理,因此startLoader需要保证在多个请求并发的情况。正是基于这个原因LauncherModel的startLoader方法的方法体都被包含在一个同步块中。

Launcher和LauncherModel之间的交互式是以类似消息驱动的方式进行的。LauncherModel产生的消息通过Launcher注册到LauncherModel中的回调接口,并得到通知。而当LauncherModel完成一次加载的时候,通过finishBindingItems接口通知Launcher加载已经完成,如果当前只是对桌面的某一个页面进行数据刷新,那么这个接口的调用会被封装成为一个任务加载到一个消息队列中,等待后续所有的任务完成后才统一执行,如果这个任务还没有得到执行,而新的刷新页面的请求已经到来,那么LauncherModel在启动加载之前会将消息队列清空,以确保所在任务都执行完成后。Launcher才会得到通知具体实现如下:

 synchronized (mDeferredBindRunnables) 
                mDeferredBindRunnables.clear();
            

mDeferredBindRunnables是一个Runable的列表,当LauncherModel的加载任务完成后,这里将会保存发往Launcher的通知,封装在一个Runable中加入该列表。

接下来LauncherModel将决策加载任务(LoaderTask)的执行方式是即时执行环视线程调度执行。
如果LauncherModel接收到的是对指定页面的刷新任务,LauncherModel还需要对应用程序菜单及桌面数据的加载完成情况做判断,如果他们的数据都已经加载完成则当前加载任务将会即时执行。

 mLoaderTask.runBindSynchronousPage(synchronousBindPage);

如果当前选择的是对所有桌面页数据进行加载这类大量数据的请求,或者桌面以及应用程序菜单的数据的加载工作并未完成,那么这个任务将会被放置在lauModel创建的线程的线程队列中等待执行

 sWorkerThread.setPriority(Thread.NORM_PRIORITY);
                    sWorker.post(mLoaderTask);

② 停止加载

LauncherModel在需要停止加载任务的时候通过调用stopLoaderLocked方法来完成。

  private void stopLoaderLocked() 
        LoaderTask oldTask = mLoaderTask;
        if (oldTask != null) 
            oldTask.stopLocked();
        
    

它是LauncherModel的一个工具不对外公开,不需要任何参数,只负责将正在运行的任务停掉。

③ LauncherModel的工作线程

在LauncherModel中定义了一个静态线程变量sWorkerThread,所有需要等待加载的任务都被抛到该线程的队列中等待处理

  @Thunk static final HandlerThread sWorkerThread = new HandlerThread("launcher-loader");
    static 
        sWorkerThread.start();
    

这样设计能确保sWorkerThread 在LauncherModel类被加载的时候就已经存在,并处于运行状态。
另外HandlerThread是Thread的一个子类,这种类型的线程能确保消息队列中的每个请求按照先进先出的顺序执行。
在启动LauncherModel的工作线程后,紧接着LauncherModel还接管了工作线程的消息队列 如下:

@Thunk static final Handler sWorker = new Handler(sWorkerThread.getLooper());

当完成以上一系列动作后,LauncherModel就完成了对工作线程的初始化,这个线程将会一直处于消息等待状态,等待每一个刷新请求,支持LauncherModel的工作线程创建并且启动完成。

④ LauncherModel的主线程处理器

LauncherModel的sWorkerThread是主线程下的一个子线程,它负责接收来自Launcher应用程序组件的刷新请求,对于sWorkerThread而言,在处理任务的同时也需要与Launcher有所交互,比如sWorkerThread需要将处理进程消息通过Launcher注册到LauncherModel中的接口通知Launcher,以便Launcher做出必要的响应。

那么现在的问题就是Launcher和LauncherModel运行在Launcher这个应用程序的主线程中(这个线程由Android框架维护) sWorkerThread只是Launcher应用程序主线程下的一个子线程,对于线程和线程之间的消息交互,一个比较好的方案是将任务抛到目标线程的处理器中,为此,LauncherModel为sWorkerThread在主线程中创建了一个处理器,以实现sWorkerThread和Launcher所在进程之间的信息交互。代码如下

DeferredHandler mHandler = new DeferredHandler();

这里引入了一个定义在LauncherModel外面而在LauncherModel中使用的线程处理器工具DeferredHandler。 接下来看下它的实现原理

DeferredHandler被当做主线程的处理器实例化在LauncherModel中,作为处理器,它需要满足通用处理器的一些定制化特性,因此它定义了一些嵌套类来满足这些特性。

  • Impl类

    它首先是被定义在DeferredHandler的一个嵌套类。

class Impl extends Handler implements MessageQueue.IdleHandler 

作为handler的子类,它主要被用于处理发往主线程的消息。

  • IdleRunnable类

    IdleRunnable是DeferredHandler内部的另外一个嵌套类,它是一个实现了Run那边了接口的类

  private class IdleRunnable implements Runnable 
        Runnable mRunnable;

        IdleRunnable(Runnable r) 
            mRunnable = r;
        

        public void run() 
            mRunnable.run();
        
    

由它的构造函数以及实现的run接口可知,它实际上只是Runnable的简单实现。
这些类的具体使用我们后续继续分析。

好了 今天主要看了LauncherModel的创建,实例化(构造函数)和与LauncherModel的通信方式。

终于可以歇口气了。

以上是关于android M Launcher之LauncherModel的主要内容,如果未能解决你的问题,请参考以下文章

android M Launcher之数据库实现

android M Launcher之LauncherModel

CMake + ccache:RULE_LAUNCH_COMPILE 或 CMAKE_<LANG>_COMPILER_LAUNCHER

Android笔记 - Android启动之Launcher启动

launcher矩形图标

android launcher2开发之 有抽屉改成无抽屉