Android开发

源码阅读#LayoutInflater的创建过程 & AsyncLayoutInflater原理

本文主要是介绍源码阅读#LayoutInflater的创建过程 & AsyncLayoutInflater原理,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

主要内容

补充上一篇文章关于LayoutInflater的一点内容,以及AsyncLayoutInflater的原理讲解,还有一点优化经验的分享。

LayoutInflater 的创建过程

提出疑问

上一篇文章中我讲到了PhoneWindow的构造方法中会获取一个LayoutInflater的实例:

public PhoneWindow(Context context) {
    super(context);
    mLayoutInflater = LayoutInflater.from(context);
}
复制代码

我们注意到,获取LayoutInflater实例的时候都需要传入Context,那么这个ContextLayoutInflater有很么关系?,接下来我们就带着这个问题来看看源码。

带着问题看源码

先看LayoutInflater#from方法:

public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}
复制代码

我们可以看到其中调用了Context#getSystemService,这里传入的是ActivityContext,而从ActivityThread#performLaunchActivity中我们知道,这里的Context引用的实际上就是ContextImpl对象,所以我们继续看ContextImpl中的getSystemService方法:

@Override
public Object getSystemService(String name) {
    return SystemServiceRegistry.getSystemService(this, name);
}
复制代码

这里调用了SystemServiceRegistry#getSystemService:

private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS = new HashMap<>();
            
public static Object getSystemService(ContextImpl ctx, String name) {
    ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
    return fetcher != null ? fetcher.getService(ctx) : null;
}
复制代码

敲黑板了哈🧐,记住上面方法中使用的fetcher.getService(ctx),还有SYSTEM_SERVICE_FETCHERS,下面要考。

下面简述下SystemServiceRegistry是如何是如何获取各个“Service”的:

1. 通过静态代码块注册各“Service”的ServiceFetcher

比如:

static {
    ......
    registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
            new CachedServiceFetcher<LayoutInflater>() {
        @Override
        public LayoutInflater createService(ContextImpl ctx) {
            return new PhoneLayoutInflater(ctx.getOuterContext());
        }});
    ......
}

private static <T> void registerService(String serviceName, Class<T> serviceClass,
        ServiceFetcher<T> serviceFetcher) {
    ......
    SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
复制代码

可以看到Fecther被加到了SYSTEM_SERVICE_FETCHERS中。这里的“Service”还有很多,包括ActivityManagerBluetoothManagerBatteryManager等等。

2. 创建CachedServiceFetcher并记录数目

每创建一个CachedServiceFetcherSystemServiceRegistry中的一个静态变量都会+1:

private static int sServiceCacheSize;
复制代码

CachedServiceFetcher继承了Fetcher,实现了Fetcher#getService,对“Service”进行了懒创建,即首次调用才创建,又声明了createService抽象方法,该方法用于在在静态代码块中实现特定的创建功能,参考第1点中的代码。因为是静态代码块中创建的,所以一种“Service”对应一个具体的CachedServiceFetcher对象,并且CachedServiceFetcher会记录当前值作为下标,这个下标有什么用呢,看下一点。

3. 调用fetcher.getService(ctx)获取“Service”

在调用fetcher.getService(ctx)的时候,CachedServiceFetcher会通过ContextImp中的一个非静态成员来获取“Service”的对象:

// The system service cache for the system services that are cached per-ContextImpl.
final Object[] mServiceCache = SystemServiceRegistry.createServiceCache();
复制代码

SystemServiceRegistry#createServiceCache是利用了SystemServiceRegistry的静态成员sServiceCacheSize进行初始化(就是上面那个会+1的变量):

/**
 * Creates an array which is used to cache per-Context service instances.
 */
public static Object[] createServiceCache() {
    return new Object[sServiceCacheSize];
}
复制代码

这下明白了:静态代码块创建了多个“Service”,与此同时静态的sServiceCacheSize记录了“Service”的数量,CachedServiceFetcher记录了“Service”的下标,在每个ContextImpl被创建的时候,非静态的mServiceCache就根据sServiceCacheSize进行初始化,在我们调用Context#getSystemService的时候,如果当前ContextmServiceCache没有对应的“Service”,就创建一个,放到CachedServiceFetcher事先记录好的下标下。

所以,一个ContextImpl对象会持有一套“Service”,保存在mServiceCache中。当然,如果没有“get”过这个“Service”,它就不会被创建(所以我从来都是只用ApplicationContext来“getService”,这样各种“Service”就只有一个实例,减少对象数目😂)。总结一下:

结论

ContextLayoutInflater有很么关系?他们的关系就是:一个Context对象会持有最多1个LayoutInflater实例,不同的Context会持有各自的LayoutInflater实例


AsyncLayoutInflater

当一个Activity需要加载的页面过于复杂的时候,而我们又希望主线程可以“减负”,以免造成卡顿,此时我们就可以考虑使用AsyncLayoutInflater来异步完成。源码部分注释如下:

This is intended for parts of the UI that are created lazily or in response to user interactions. This allows the UI thread to continue to be responsive & animate while the relatively heavy inflate is being performed.

这个类的作用就是异步创建View,使界面能在大量加载View的时候及时响应用户并且能播放动画。

AsyncLayoutInflater 的重要组成元素

OnInflateFinishedListener

回调接口,在inflate方法中传入对象,异步执行完成后,通过Handler回调主线程执行该对象中的回调方法。

BasicInflater

从上面的文章我们知道ContextImpl中的LayoutInflater其实是PhoneLayoutInflater,这里的BasicInflaterPhoneLayoutInflater一模一样,它也是AsyncLayoutInflater中的内部类。

InflateRequest

InflateRequest代表一次inflate请求,它包含了一次inflate的所有关键信息,它是AsyncLayoutInflater中的内部类。

private static class InflateRequest {
    AsyncLayoutInflater inflater;       // AsyncLayoutInflater本身
    ViewGroup parent;                   // inflate方法传入参数,父布局
    int resid;                          // inflate方法传入参数,注入布局ID
    View view;                          // 根据布局创建的View
    OnInflateFinishedListener callback; // 回调对象
    
    InflateRequest() {
    }
}
复制代码

InflateThread

用来执行异步任务的线程,他其中有一个阻塞队列+一个对象池。对象池是用来复用InflateRequest对象的;线程的run方法中是一个无限循环,他会从阻塞队列中take元素。

这里面有个很有意思的地方:

private static class InflateThread extends Thread {
    private static final InflateThread sInstance;
    static {
        sInstance = new InflateThread();
        sInstance.start();
    }

    public static InflateThread getInstance() {
        return sInstance;
    }
    ......
}
复制代码

类似于单例模式的实现方式,这里的静态代码块其实是一种懒加载,在第一次调用getInstance的时候才会被执行,再看看AsyncLayoutInflater的构造方法就知道,InflateThread启动的时候,也就是我们创建AsyncLayoutInflater的时候:

public AsyncLayoutInflater(@NonNull Context context) {
    mInflater = new BasicInflater(context);
    mHandler = new Handler(mHandlerCallback);
    mInflateThread = InflateThread.getInstance();
}
复制代码

并且InflateThread也是单例模式的。

AsyncLayoutInflater 运行原理

其实AsyncLayoutInflater代码不多,原理也很简单:

  1. 创建AsyncLayoutInflater时异步线程启动,然后被阻塞队列take方法阻塞。
  2. 通过inflate方法传入构造View的信息与回调对象,然后从对象池中取出InflateRequest对象进行信息更新,然后放入阻塞队列中。
  3. 阻塞队列take获取到元素,异步线程开始执行View的创建,并在执行完成后发送消息给Handler
  4. Handler回调主线程,执行OnInflateFinishedListener回调对象,然后回收InflateRequest对象。

过程大致如下:

关于首帧优化

之前我们的App做了一次首页的首帧优化,优化的内容是什么呢,就是刚跳转首页的时候会有一小段的白屏时间,这个是因为整个测量、布局、绘制时间太长的原因,从systrace中看到的就是performTraversals时间太长,我们的View层级很复杂,不易重构,所以我们就想到了AsyncLayoutInflater,但是这还是不能解决我们的问题,因为这只是异步,界面还是要等到异步执行完成才显示出来,后来我们使用了一种方法——ViewStub,让标题、导航栏等元素首先加载,其余元素使用ViewStub延迟300-500毫秒之后注入,整个界面看上去就是瞬间显示,只是有些元素会有淡入的效果。

以上是一点经验的记录。

这篇关于源码阅读#LayoutInflater的创建过程 & AsyncLayoutInflater原理的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!