持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情
历史文章
可改变base context的ContextWrapper
本身常见的Activity就是间接继承于ContextWrapper(继承了Context接口),并且其内部会持有一个mBase对象:
public class ContextWrapper extends Context {
@UnsupportedAppUsage
Context mBase;
public ContextWrapper(Context base) {
mBase = base;
}
}
这个mBase对象真正的实现类为ContextImpl,而我们常见的getResource()、getAssets()、getTheme()方法能力的真正提供者就是来自于这个mBase对象:
而这个mBase对象是无法通过外部进行设置的,但是我们想要就想要通过外部传入怎么办,MutableContextWrapper它就来了:
public class MutableContextWrapper extends ContextWrapper {
public MutableContextWrapper(Context base) {
super(base);
}
public void setBaseContext(Context base) {
mBase = base;
}
}
本身就是个装饰器模式的应用,我们可以外部调用setBaseContext()方法来改变mBase对象。
讲了这么多,这个外部设置mBase的能力有什么用呢?
一般做
WebView渲染优化的时候,都会有个预加载WebView的过程,即提前创建WebView组件,但是WebView需要传入一个Context,我们预创建肯定不是在使用WebView的Activity时才进行预创建,比如在Application中创建。
所以需要手动构造一个
Context对象进行传入,等到WebView使用的时候再将这个Context替换成当前界面Activity,MutableContextWrapper就是要给很好的选择。
我们简单写下实现代码:
object CacheWebView {
private var mCacheWebView = ArrayList<WebView>()
fun putCache(application: MainApplication) {
mCacheWebView.add(WebView(MutableContextWrapper(application)))
}
fun getCache(activity2: MainActivity2): WebView {
return mCacheWebView.getOrNull(0)?.let {
(it.context as MutableContextWrapper).baseContext = activity2
it
} ?: WebView(MutableContextWrapper(activity2)).also {
mCacheWebView.add(it)
}
}
}
比如在Application中调用CacheWebView.putCache(this)实现预创建WebView,这个时候MutableContextWrapper中传入的context是Application类型;
等到真正使用WebView的界面获取时,从mCacheWebView拿到WebView,并将MutableContextWrapper中context替换为Activity。
请注意,上面只是简单的模拟了下WebView的预加载,可不是能够直接拿来使用的。
View.post()和Handler.post()区别
看下View.post()源码:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
当attachInfo != null时,效果和Handler.post()相同;当attachInfo == null,就大为不同了,会先加入到一个集合中。
那就得看下mAttachInfo的赋值位置了:
继续往上找,最终在ViewRootImpl的performTraversals()调用了dispatchAttachedToWindow()并传入了这个mAttachInfo对象。
这个performTraversals()什么时候调用的,这里直接说结论(源码太复杂了哈哈):
当界面走到
onResume()时,会通过RootViewImpl对象调用Choreographer注册垂直脉冲信号监听,当信号到来时,最终就会调用到performTraversals()方法,触发界面绘制。
也就是说等到界面第一帧绘制完成之后,才会触发View.post()发送的Message添加到消息队列中,开始分发执行。
这里对View.post()和Handler.post()做个小结:
View.post()等到界面第一帧开始绘制之后,才会和Handler.post()一样,添加到消息队列中,开始消息的分发执行。
这个有什么用呢,我们可以用来获取View子类组件的测量宽高,而不是直接在onCreate()中获取宽高发现等于0(哈哈)。