【奇淫技巧】解锁X5内核WebView同层渲染能力
前言
WebView同层渲染,并不是一个新技术,国内一线互联网产品广泛应用,比如小程序的原生组件,电商H5嵌原生播放器等场景;
场景分析 | 技术博客 |
---|---|
微信小程序 | 小程序同层渲染原理剖析 |
百度小程序 | 【走进小程序原理】揭秘组件同层渲染 |
淘宝系播放器 | 618淘系前端技术分享 |
如果您了解其原理,会发现这玩意在Android端,需要修改浏览器内核才能搞定,所以上手难度高;以至于读完文章,心血澎湃直呼牛逼,但是回过头翻看原生WebView代码,并没有找到合适的API;
同层渲染简介
在Android平台,H5内容依托WebView视图渲染,它和原生的View是独立平等的关系, 从绘制层次上来看,WebView和原生必然存在相互覆盖遮挡的,更没法做到同步滚动;
在网页DOM树中,间杂一部分原生的组件,且保留原本的层次和样式,这就是同层渲染;
同层渲染能解决什么问题?
使用web前端技术实现困难,或者稳定性、性能受限等情况
例如:视频播放器、地图、游戏引擎、直播推拉流、摄像头预览等场景;
领略X5内核浏览器同层渲染
准备工作:
正确接入腾讯X5浏览器 x5.tencent.com/
保证X5内核加载成功 x5.tencent.com/docs/questi…
准备一个占位标签
X5同层渲染的原理是用原生接管在H5页面里的特定标签,所以得准备一个H5页面,然后插入一个自定义的标签, 标签名可以随意定义,比如mytag,样式就按照标准的css来设置
<mytag id = "mytag" src="https://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4" style="position:absolute;width:350px; height:500px;"> 占位的标签 </mytag>复制代码
强制开启X5WebView同层渲染
X5同层渲染能力默认是关闭的,通过云端开关控制,通过分析发现,可以强制修改本地SP属性,强制打开
if (mWebView.getX5WebViewExtension()!=null){ //强制设置EMBEDDED云控开关enable SharedPreferences tbs_public_settings = getSharedPreferences("tbs_public_settings", Context.MODE_PRIVATE); SharedPreferences.Editor edit = tbs_public_settings.edit(); edit.putInt("MTT_CORE_EMBEDDED_WIDGET_ENABLE",1); edit.apply(); }else { Log.d(TAG, "init: 非x5内核"); }复制代码
向浏览器注册目标占位标签的原生控件
使用registerEmbeddedWidget
方法,可以想浏览器内核注册,需要原生来接管的占位的标签,第一个参数
是需要接管的标签名
,第二个参数
是工厂创建对应原生标签对象的工厂接口
//注册dom树中占位标签,创建对应的原生组件 boolean result = mWebView.getX5WebViewExtension().registerEmbeddedWidget(new String[]{"mytag"}, new IEmbeddedWidgetClientFactory() { @Override public IEmbeddedWidgetClient createWidgetClient(String s, Map<String, String> map, IEmbeddedWidget iEmbeddedWidget) { Log.d(TAG, "init: createWidgetClient s"+s); Log.d(TAG, "init: createWidgetClient map"+map.toString()); return new VideoEmbeddedWidgetClient(BrowserActivity.this); } });复制代码
其中createWidgetClient
方法参数意义
s
标签名,大写map
该标签的属性,在html中指定的iEmbeddedWidget
提供的原生的该标签的代理接口
下面是map打印的内容
init: createWidgetClient map{src=https://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4, style=position:absolute;width:350px; height:500px;, id=mytag}复制代码
处理原生占位控件的实现IEmbeddedWidgetClient
原生如何接管替换占位标签,主要的实现类是IEmbeddedWidgetClient
public interface IEmbeddedWidgetClient { void onSurfaceCreated(Surface var1); void onSurfaceDestroyed(Surface var1); boolean onTouchEvent(MotionEvent var1); void onRectChanged(Rect var1); void onVisibilityChanged(boolean var1); void onDestroy(); void onActive(); void onDeactive(); void onRequestRedraw(); }复制代码
首先,IEmbeddedWidgetClient
并不是一个原生的View,而是给原生提供的该标签区域的绘制的入口,从onSurfaceCreated
和onSurfaceDestroyed
可以理解;
既然不是原生的View绘制,X5还是提供了类比View的属性,从API命名可以简单看出来其作用;
onSurfaceCreated
该标签可以绘制,请求原生API处理onSurfaceDestroyed
该标签视图销毁,请求原生销毁onTouchEvent
触摸事件分发onRectChanged
该标签在WebView中坐标变化回调(例如滚动,改变宽高)onVisibilityChanged
该标签显示隐藏onDestroy
该标签被移除,或者display = none
演示个Demo
熟悉了X5的API,写个简单的Demo玩一下
Demo的设计思路是在一个垂直滚动的网页中,嵌入一个原生的相机,原生的相机要正常的采集显示,且前端代码可以控制相机标签的显示隐藏,同步滚动等基本操作;
网页
<!DOCTYPE html> <html> <head> <title>测试网页</title> <meta charset="UTF-8"> <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, shrink-to-fit=no" name="viewport"> <script type="text/javascript"> window.onload = touchtouch; function touchtouch(){ var camera = document.getElementById('camera'); console.log("camera:"+camera) camera.addEventListener("touchStart",handlerTouch,false); camera.addEventListener("touchend",handlerTouch,false); camera.addEventListener("touchcancel",handlerTouch,false); camera.addEventListener("touchleave",handlerTouch,false); camera.addEventListener("touchmove",handlerTouch,false); } function handlerTouch(evt){ log(evt); } </script> </head> <body> <p>测试网页哈哈哈</p> <div> <div style="background-color: aqua; height: 100px;text-align: center;">1</div> <div style="background-color: gray; height: 100px;text-align: center;">2</div> <div style="background-color: rgb(80, 69, 69);height:500px;"> <camera cameraId=0 style="position:absolute;width:350px; height:500px;"> 相机占位标签 </camera> <a href="https://www.baidu.com" style="position:absolute;color:rgba(187, 255, 0, 0.482)"> 点击跳转哈哈哈</a> </div> <div style="background-color: gold; height: 100px;text-align: center;">4</div> <div style="background-color: red; height: 100px;text-align: center;">5</div> <div style="background-color: green; height: 100px;text-align: center;">6</div> </div> </body> </html>复制代码
Java实现
public class CameraEmbeddedWidgetClient implements IEmbeddedWidgetClient { private String TAG = "VideoEmbeddedWidgetClient"; private Rect rect; private CameraHelper cameraHelper; public CameraEmbeddedWidgetClient(Context c) { cameraHelper = new CameraHelper(c); } @Override public void onSurfaceCreated(Surface surface) { Log.d(TAG, "onSurfaceCreated: "); // Canvas canvas = surface.lockCanvas(rect); // canvas.drawColor(Color.parseColor("#7f000000")); // surface.unlockCanvasAndPost(canvas); cameraHelper.preview(surface); } @Override public void onSurfaceDestroyed(Surface surface) { Log.d(TAG, "onSurfaceDestroyed: "); cameraHelper.release(); } }复制代码
前端样式改变和Native事件触发
指定样式display:none
原生回调onVisibilityChanged(false)
和onDestroy
指定样式display:block
重新创建新的Client指定样式visibility:visible
原生回调onVisibilityChanged(true)
指定样式visibility:hidden
原生回调onVisibilityChanged(false)
移除当前dom
等效于display:none
触摸事件的验证
必须得在js中设置改标签的事件监听
camera.addEventListener("touchStart",handlerTouch,false); camera.addEventListener("touchend",handlerTouch,false); camera.addEventListener("touchcancel",handlerTouch,false); camera.addEventListener("touchleave",handlerTouch,false); camera.addEventListener("touchmove",handlerTouch,false);复制代码
原生接受事件处理
IEmbeddedWidgetClient实现类
@Override public boolean onTouchEvent(MotionEvent motionEvent) { Log.d(TAG, "onTouchEvent: "+motionEvent.toString()); float x = motionEvent.getX(); float y = motionEvent.getY(); int action = motionEvent.getAction(); switch (action){ case MotionEvent.ACTION_DOWN: initX = x; initY = y; intercepted = false; return false; case MotionEvent.ACTION_MOVE: float dx = x - initX; float dy = y - initY; if (!intercepted && Math.abs(dy)>Math.abs(dx) && Math.abs(dy)>16){ intercepted = true; } break; case MotionEvent.ACTION_UP: break; } return intercepted; }复制代码
总结
本文主要分享X5内核隐藏的同层渲染能力,用于学习交流,但是官方没有明确的文档说明,所以引入项目请谨慎;如果您对此该话题兴趣,不妨亲手折腾一下。
作者:HitenDev
链接:https://juejin.cn/post/7018037732412768269