你可能需要了解下的Android开发技巧(三)

1,447 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第22天,点击查看活动详情

历史文章

你可能需要了解下的Android开发技巧(一)

你可能需要了解下的Android开发技巧(二)

可改变base contextContextWrapper

本身常见的Activity就是间接继承于ContextWrapper(继承了Context接口),并且其内部会持有一个mBase对象:

public class ContextWrapper extends Context {
    @UnsupportedAppUsage
    Context mBase;

    public ContextWrapper(Context base) {
        mBase = base;
    }
}

这个mBase对象真正的实现类为ContextImpl,而我们常见的getResource()getAssets()getTheme()方法能力的真正提供者就是来自于这个mBase对象:

image.png

而这个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,我们预创建肯定不是在使用WebViewActivity时才进行预创建,比如在Application中创建。

所以需要手动构造一个Context对象进行传入,等到WebView使用的时候再将这个Context替换成当前界面ActivityMutableContextWrapper就是要给很好的选择。

我们简单写下实现代码:

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中传入的contextApplication类型;

等到真正使用WebView的界面获取时,从mCacheWebView拿到WebView,并将MutableContextWrappercontext替换为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的赋值位置了:

image.png

继续往上找,最终在ViewRootImplperformTraversals()调用了dispatchAttachedToWindow()并传入了这个mAttachInfo对象。

这个performTraversals()什么时候调用的,这里直接说结论(源码太复杂了哈哈):

当界面走到onResume()时,会通过RootViewImpl对象调用Choreographer注册垂直脉冲信号监听,当信号到来时,最终就会调用到performTraversals()方法,触发界面绘制。

也就是说等到界面第一帧绘制完成之后,才会触发View.post()发送的Message添加到消息队列中,开始分发执行。

这里对View.post()Handler.post()做个小结:

View.post()等到界面第一帧开始绘制之后,才会和Handler.post()一样,添加到消息队列中,开始消息的分发执行。

这个有什么用呢,我们可以用来获取View子类组件的测量宽高,而不是直接在onCreate()中获取宽高发现等于0(哈哈)。