Launcher加载APP图标流程

QuibblerQuibbler 2020-07-06 约 23 分钟 2674 次阅读

Launcher加载APP图标流程

前段时间看了桌面Launcher的启动流程《安卓桌面Launcher的启动》,但是还没完,桌面也是一个安卓应用且涉及到的面很广,光是桌面这个模块就需要一个团队维护开发。

AOSP Launcher源码:Launcher3

大牛维护的Launcher代码:fookwood/Launcher3

1、Launcher.onCreate()

接着Launcher的启动,通过Intent启动桌面之后,逻辑和正常的应用一样。执行主界面Activity的onCreate()方法,把这一百多行的代码分解一点一点看:

①Debug启用严格模式,系统应用调试非常苛刻。

protected void onCreate(Bundle savedInstanceState) {
        RaceConditionTracker.onEvent(ON_CREATE_EVT, ENTER);
        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());
        }
        ...

②接着调用super.onCreate(),Trance开始,并且中间记录了一次

TraceHelper.beginSection("Launcher-onCreate");
        super.onCreate(savedInstanceState);
        TraceHelper.partitionSection("Launcher-onCreate", "super call");

③接着获取LauncherAppState实例,并将当前Launcher设置给LauncherAppState

LauncherAppState app = LauncherAppState.getInstance(this);
        mOldConfig = new Configuration(getResources().getConfiguration());
        mModel = app.setLauncher(this);
        ...

④初始化一些成员变量,加载布局、setupViews()

...
        mAppWidgetHost = new LauncherAppWidgetHost(this);
        mAppWidgetHost.startListening();
        mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
        setupViews();
        ...

调用了setupViews()就初始化了AllAppsContainerView布局(后面用到)

/**
     * Finds all the views we need and configure them properly.
     */
    private void setupViews() {
        ...
        mAllAppsController.setupViews(mAppsView);
    }

⑤调用LauncherModel的startLoader()方法,这里的方法经过重构与之前源码略有不同。

...
        if (!mModel.startLoader(currentScreen)) {
            if (!internalStateHandled) {
                // If we are not binding synchronously, show a fade in animation when
                // the first page bind completes.
                mDragLayer.getAlphaProperty(ALPHA_INDEX_LAUNCHER_LOAD).setValue(0);
            }
        } else {
            // Pages bound synchronously.
            mWorkspace.setCurrentPage(currentScreen);
            setWorkspaceLoading(true);
        }
        ...

⑥到这里才给桌面界面setContentView(),并且注册监听手机息屏广播:Intent.ACTION_SCREEN_OFF。

....
        setContentView(mLauncherView);
        getRootView().dispatchInsets();
        
        // Listen for broadcasts
        registerReceiver(mScreenOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
        ...

大体流程分解完成,现在来看每一个详细执行的到底有哪些?

2、详细分析

在第③步中,获取LauncherAppState实例,并调用setLauncher(Launcher launcher)方法

LauncherModel setLauncher(Launcher launcher) {
        getLocalProvider(mContext).setLauncherProviderChangeListener(launcher);
        mModel.initialize(launcher);
        return mModel;
    }

mModel是LauncherModel类型的成员变量,调用initialize()方法将传入的launcher设置为当前加载程序的Launcher活动对象,封装为一个弱引用。

/**
     * Set this as the current Launcher activity object for the loader.
     */
    public void initialize(Callbacks callbacks) {
        synchronized (mLock) {
            Preconditions.assertUIThread();
            mCallbacks = new WeakReference<>(callbacks);
        }
    }

在第⑤步中,调用了LauncherModel的startLoader()方法,这部分方法注释简单翻译了一下方便查看,方法步长吗,但是每一步都很关键,需要细看:

/**
     * Starts the loader. Tries to bind {@params synchronousBindPage} synchronously if possible.
     * @return true if the page could be bound synchronously.
     */
    public boolean startLoader(int synchronousBindPage) {
        // 在启动加载程序之前启用队列。 它将在Launcher#finishBindingItems中禁用
        InstallShortcutReceiver.enableInstallQueue(InstallShortcutReceiver.FLAG_LOADER_RUNNING);
        synchronized (mLock) {
            // 如果我们知道它不会做任何事情,请不要费心启动线程
            if (mCallbacks != null && mCallbacks.get() != null) {
                final Callbacks oldCallbacks = mCallbacks.get();
                // Clear any pending bind-runnables from the synchronized load process.
                mUiExecutor.execute(oldCallbacks::clearPendingBinds);
                // 如果已经在运行,请告诉它停止。
                stopLoader();
                LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
                        mBgAllAppsList, synchronousBindPage, mCallbacks);
                if (mModelLoaded && !mIsLoaderTaskRunning) {
                    // 将已加载项的集合划分为我们要同步绑定的项,以及要正常(异步)绑定的所有其他项。
                    loaderResults.bindWorkspace();
                    // 现在,继续发布AllApp的绑定,因为这还会引起其他问题。
                    loaderResults.bindAllApps();
                    loaderResults.bindDeepShortcuts();
                    loaderResults.bindWidgets();
                    return true;
                } else {
                    startLoaderForResults(loaderResults);
                }
            }
        }
        return false;
    }

其中一步是调用stopLoader()方法,如果已经有加载程序任务在运行,就停止。

public void stopLoader() {
        synchronized (mLock) {
            LoaderTask oldTask = mLoaderTask;
            mLoaderTask = null;
            if (oldTask != null) {
                oldTask.stopLocked();
            }
        }
    }

这个这几很巧妙,和前面的startLoader()方法用了同一个对象mLock加锁互斥:        

@Thunk final Object mLock = new Object();

继续startLoader()方法,API 30 的系统源码明显经过很大的重构,现在将加载图标、桌面Widget、快捷方式的任务都封装在LoaderResults对象中。

这里我们只看其中一个,就是APP图标的加载,也就是LoaderResults的bindAllApps()方法

public void bindAllApps() {
        // shallow copy
        @SuppressWarnings("unchecked")
        ArrayList<AppInfo> list = (ArrayList<AppInfo>) mBgAllAppsList.data.clone();
        executeCallbacksTask(c -> c.bindAllApplications(list), mUiExecutor);
    }

又调用了LoaderResults中的executeCallbacksTask(CallbackTask ...)方法。

protected void executeCallbacksTask(CallbackTask task, Executor executor) {
        executor.execute(() -> {
            if (mMyBindingId != mBgDataModel.lastBindId) {
                Log.d(TAG, "Too many consecutive reloads, skipping obsolete data-bind");
                return;
            }
            Callbacks callbacks = mCallbacks.get();
            if (callbacks != null) {
                task.execute(callbacks);
            }
        });
    }

方法中的参数类型CallbackTask类型似曾相识,往前面回忆,没错!就是定义在LauncherModel中的接口

/**
     * A task to be executed on the current callbacks on the UI thread.
     * If there is no current callbacks, the task is ignored.
     */
    public interface CallbackTask {
        void execute(Callbacks callbacks);
    }

它在binderAllApps()方法中是通过Lambda表达式初始化的,传入的参数正是Callbacks,同样是定义在LauncherModel中的接口:

public interface Callbacks {
        public void rebindModel();
        public int getCurrentWorkspaceScreen();
        public void clearPendingBinds();
        public void startBinding();
        public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons);
        public void bindScreens(IntArray orderedScreenIds);
        public void finishFirstPageBind(ViewOnDrawExecutor executor);
        public void finishBindingItems(int pageBoundFirst);
        public void bindAllApplications(ArrayList<AppInfo> apps);
        public void bindAppsAddedOrUpdated(ArrayList<AppInfo> apps);
        ...
    }

那么LoaderResults中的Callbacks是怎么来的呢?还记的最开始调用startLoader()方法,其中一行就是用当前的mCallbacks作为参数传入:

...
        LoaderResults loaderResults = new LoaderResults(mApp, sBgDataModel,
                mBgAllAppsList, synchronousBindPage, mCallbacks);
        ...

注意这个就是Launcher作为Callbacks的弱引用,原来饶了一圈最后又回到了Launcher,Launcher实现了Callbacks中的所有接口方法,其中bindAllApplications(ArrayList<AppInfo> apps):

/**
     * Add the icons for all apps.
     *
     * Implementation of the method from LauncherModel.Callbacks.
     */
    public void bindAllApplications(ArrayList<AppInfo> apps) {
        mAppsView.getAppsStore().setApps(apps);
    }

mAppsView是AllAppsContainerView类型,调用getAppsStore()方法

public AllAppsStore getAppsStore() {
        return mAllAppsStore;
    }

返回AllAppsStore对象,调用setApps()方法

/**
     * Sets the current set of apps.
     */
    public void setApps(List<AppInfo> apps) {
        mComponentToAppMap.clear();
        addOrUpdateApps(apps);
    }

其实就是一个AllAppsContainerView的Adapter,更新数据通知View去刷新界面:

/**
     * Adds or updates existing apps in the list
     */
    public void addOrUpdateApps(List<AppInfo> apps) {
        for (AppInfo app : apps) {
            mComponentToAppMap.put(app.toComponentKey(), app);
        }
        notifyUpdate();
    }

提一下,回到最开始的onCreate()方法中调用了setupViews()就初始化了AllAppsContainerView等布局:

/**
     * Finds all the views we need and configure them properly.
     */
    private void setupViews() {
        ...
        mAllAppsController.setupViews(mAppsView);
    }

至此,随着桌面的onCreate(),图标加载完成。有点复杂,但是逻辑比较清晰。

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w