DevSupport

性能优化之Web View预加载

2019-10-03  本文已影响0人  野火友烧不尽

用过WebView的都知道,在首次WebView时启动一些系统服务,而这一过程是非常的耗时,经过测试根据不同机型耗时也不一样,性能好的机型上可能200毫秒以上,而性能稍微差点的500毫秒以上,这是不可接受的,我最近也在看这块的东西,发现很多文章基本都是在讲使用复用池对WebView做优化,我个人觉得这也是一个优化点,但是这并不能从根本上解决问题,复用池也就是仅仅是对WebView的渲染着了优化,所以并不能从根本解决这个性能问题,而且这种方式不好控制,容易造成内存泄漏,为了解决问题而引入其他问题,这是得不偿失的,最近几天经过大量阅读网上的文章和阅读WbeView的源码,发现WebView首次加载慢的原因就是,在启动过过程中,要启动chrome相关的服务,而这些过程是相当耗时的,所以我们的优化点就是,是否可以在应用启动时,提前启动chrome的服务?答案是可以的,在这之后我看了滴滴的Booster的源码,他们优化方案也是通过提前启动chrome的服务,下面来看看是怎么实现的?

怎么绕过系统服务Hide API?

相比大家应该很想清楚Android P已经限制了很多非公开接口,就是通过反射或者JNI访问非公开接口时会触发警告/异常等:然后借助系统类去调用反射
首先,我们通过反射 API 拿到 getDeclaredMethod A方法,不存在问题;
然后,我们通过刚刚反射拿到的A方法去反射调用 getDeclardMethod。这里我们就实现了以系统身份去反射的目的——反射相关的 API 都是系统类,因此我们反射的A方法也是被系统类加载的方法;所以我们的反射方法调用的 getDeclardMethod 会被认为是系统调用的,可以反射任意的方法,这里就提这些,进入正题。

首先来看看原来怎么实现:

object WebViewHelper {
private const val WEB_VIEW_FACTORY_CLASS_NAME = "android.webkit.WebViewFactory"
private const val FACTORY_METHOD_NAME_GET_PROVIDER = "getProvider"

fun preloadWebView(application: Application) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        //将在系统闲置的时候执行
        application.mainLooper.queue.addIdleHandler {
            startChromiumEngine()
            false//返回false将会移除这个IdleHandler
        }
    }
}

@SuppressLint("PrivateApi")
private fun startChromiumEngine() {
    try {
        MeasureLaunchTime.startRecord()
        val webViewFactoryClass = Class.forName(WEB_VIEW_FACTORY_CLASS_NAME)
        ReflectHelper.invokeStaticMethod<Any>(webViewFactoryClass, FACTORY_METHOD_NAME_GET_PROVIDER)
            ?.let {
                ReflectHelper.invokeMethod<Any>(
                    it,
                    "startYourEngines",
                    arrayOf(Boolean::class.java),
                    arrayOf(true)
                )
            }
        MeasureLaunchTime.endRecord("Start chromium engine complete: ")
    } catch (t: Throwable) {
        Log.e("tag", "Start chromium engine error", t)
    }
}

}

注释写得很清楚就不多介绍,唯一要说的是IdleHandler这个类,addIdleHandler是等到系统闲置了才执行,而返回值为false,就是执行完成会讲这个addIdleHandler移除,当然你可以手动移除,如果你不信请看下面Handler的源码:

 Message next() {

 ............略..........

    for (;;) {
        synchronized (this) {
            if (mPendingIdleHandlers == null) {
                mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // 循环遍历所有的 IdleHandler 回调
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null;
            // 执行IdleHandler 的 queueIdle 方法,如果queueIdle 返回false,就会移除这个IdleHandler
            boolean keep = false;
            ............略.............

            keep = idler.queueIdle();
             ...........略.............
            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }
        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

最后在贴反射帮助类:

object ReflectHelper {

@JvmStatic
fun <T> invokeStaticMethod(
    kClass: Class<*>,
    name: String
): T? = invokeStaticMethod<T>(kClass, name, arrayOf(), arrayOf())

@JvmStatic
fun <T> invokeStaticMethod(
    kClass: Class<*>,
    name: String,//方法名
    paramTypes: Array<Class<*>>,//参数类型
    paramValue: Array<Any>//参数值
): T? {
    if (paramTypes.size != paramValue.size) return null
    return getMethod(kClass, name, paramTypes)?.run {
        isAccessible = true
        invoke(kClass, *paramValue) as? T
    }
}


/**
 * 调用普通方法
 */
@JvmStatic
fun <T> invokeMethod(
    obj: Any,
    name: String,//方法名
    paramTypes: Array<Class<*>>,//参数类型
    paramValue: Array<Any>//参数值
): T? {
    if (paramTypes.size != paramValue.size) return null
    return getMethod(obj.javaClass, name, paramTypes)?.run {
        isAccessible = true
        invoke(obj, *paramValue) as? T
    }
}


@JvmStatic
private fun getMethod(
    kClass: Class<*>,
    name: String,//方法名
    paramTypes: Array<Class<*>>//参数类型
): Method? {
    return try {
        //注意getDeclaredMethod和getMethod方法的区别
        kClass.getDeclaredMethod(name, *paramTypes)
    } catch (e: NoSuchMethodException) {
        //没有父类,说明这个类没有定义这个方法
        val parent = kClass.superclass ?: return null
        //如果还有父类,就从父类去找这个方法
        getMethod(parent, name, paramTypes)
    }
}
}

最后看看启动耗时的时间是:

Start chromium engine complete: 262 ms

文章到此WebView结束了。

上一篇 下一篇

猜你喜欢

热点阅读