补充上一篇文章关于LayoutInflater
的一点内容,以及AsyncLayoutInflater
的原理讲解,还有一点优化经验的分享。
上一篇文章中我讲到了PhoneWindow
的构造方法中会获取一个LayoutInflater
的实例:
public PhoneWindow(Context context) { super(context); mLayoutInflater = LayoutInflater.from(context); } 复制代码
我们注意到,获取LayoutInflater
实例的时候都需要传入Context
,那么这个Context
和LayoutInflater
有很么关系?,接下来我们就带着这个问题来看看源码。
先看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
,这里传入的是Activity
的Context
,而从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”的:
比如:
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”还有很多,包括ActivityManager
、BluetoothManager
、BatteryManager
等等。
每创建一个CachedServiceFetcher
,SystemServiceRegistry
中的一个静态变量都会+1:
private static int sServiceCacheSize; 复制代码
CachedServiceFetcher
继承了Fetcher
,实现了Fetcher#getService
,对“Service”进行了懒创建,即首次调用才创建,又声明了createService
抽象方法,该方法用于在在静态代码块中实现特定的创建功能,参考第1点中的代码。因为是静态代码块中创建的,所以一种“Service”对应一个具体的CachedServiceFetcher
对象,并且CachedServiceFetcher
会记录当前值作为下标,这个下标有什么用呢,看下一点。
在调用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
的时候,如果当前Context
的mServiceCache
没有对应的“Service”,就创建一个,放到CachedServiceFetcher
事先记录好的下标下。
所以,一个ContextImpl
对象会持有一套“Service”,保存在mServiceCache
中。当然,如果没有“get”过这个“Service”,它就不会被创建(所以我从来都是只用Application
的Context
来“getService”,这样各种“Service”就只有一个实例,减少对象数目😂)。总结一下:
Context
和LayoutInflater
有很么关系?他们的关系就是:一个Context
对象会持有最多1个LayoutInflater
实例,不同的Context
会持有各自的LayoutInflater
实例。
当一个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
的时候及时响应用户并且能播放动画。
回调接口,在inflate
方法中传入对象,异步执行完成后,通过Handler
回调主线程执行该对象中的回调方法。
从上面的文章我们知道ContextImpl
中的LayoutInflater
其实是PhoneLayoutInflater
,这里的BasicInflater
与PhoneLayoutInflater
一模一样,它也是AsyncLayoutInflater
中的内部类。
InflateRequest
代表一次inflate
请求,它包含了一次inflate
的所有关键信息,它是AsyncLayoutInflater
中的内部类。
private static class InflateRequest { AsyncLayoutInflater inflater; // AsyncLayoutInflater本身 ViewGroup parent; // inflate方法传入参数,父布局 int resid; // inflate方法传入参数,注入布局ID View view; // 根据布局创建的View OnInflateFinishedListener callback; // 回调对象 InflateRequest() { } } 复制代码
用来执行异步任务的线程,他其中有一个阻塞队列+一个对象池。对象池是用来复用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
时异步线程启动,然后被阻塞队列take
方法阻塞。inflate
方法传入构造View
的信息与回调对象,然后从对象池中取出InflateRequest
对象进行信息更新,然后放入阻塞队列中。take
获取到元素,异步线程开始执行View
的创建,并在执行完成后发送消息给Handler
。Handler
回调主线程,执行OnInflateFinishedListener
回调对象,然后回收InflateRequest
对象。过程大致如下:
之前我们的App做了一次首页的首帧优化,优化的内容是什么呢,就是刚跳转首页的时候会有一小段的白屏时间,这个是因为整个测量、布局、绘制时间太长的原因,从systrace
中看到的就是performTraversals
时间太长,我们的View
层级很复杂,不易重构,所以我们就想到了AsyncLayoutInflater
,但是这还是不能解决我们的问题,因为这只是异步,界面还是要等到异步执行完成才显示出来,后来我们使用了一种方法——ViewStub
,让标题、导航栏等元素首先加载,其余元素使用ViewStub
延迟300
-500
毫秒之后注入,整个界面看上去就是瞬间显示,只是有些元素会有淡入的效果。
以上是一点经验的记录。