注册

【奇淫技巧】解锁X5内核WebView同层渲染能力

前言

WebView同层渲染,并不是一个新技术,国内一线互联网产品广泛应用,比如小程序的原生组件,电商H5嵌原生播放器等场景;

如果您了解其原理,会发现这玩意在Android端,需要修改浏览器内核才能搞定,所以上手难度高;以至于读完文章,心血澎湃直呼牛逼,但是回过头翻看原生WebView代码,并没有找到合适的API;

同层渲染简介

在Android平台,H5内容依托WebView视图渲染,它和原生的View是独立平等的关系, 从绘制层次上来看,WebView和原生必然存在相互覆盖遮挡的,更没法做到同步滚动;

在网页DOM树中,间杂一部分原生的组件,且保留原本的层次和样式,这就是同层渲染;

图片来源微信技术文章

同层渲染能解决什么问题?

使用web前端技术实现困难,或者稳定性、性能受限等情况

例如:视频播放器、地图、游戏引擎、直播推拉流、摄像头预览等场景;

领略X5内核浏览器同层渲染

准备工作:

准备一个占位标签

X5同层渲染的原理是用原生接管在H5页面里的特定标签,所以得准备一个H5页面,然后插入一个自定义的标签, 标签名可以随意定义,比如mytag,样式就按照标准的css来设置


占位的标签

强制开启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 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,而是给原生提供的该标签区域的绘制的入口,从onSurfaceCreatedonSurfaceDestroyed可以理解;

既然不是原生的View绘制,X5还是提供了类比View的属性,从API命名可以简单看出来其作用;

  • onSurfaceCreated 该标签可以绘制,请求原生API处理
  • onSurfaceDestroyed 该标签视图销毁,请求原生销毁
  • onTouchEvent 触摸事件分发
  • onRectChanged 该标签在WebView中坐标变化回调(例如滚动,改变宽高)
  • onVisibilityChanged 该标签显示隐藏
  • onDestroy 该标签被移除,或者display = none

演示个Demo

熟悉了X5的API,写个简单的Demo玩一下

Demo的设计思路是在一个垂直滚动的网页中,嵌入一个原生的相机,原生的相机要正常的采集显示,且前端代码可以控制相机标签的显示隐藏,同步滚动等基本操作;

网页




<span class="javascript">测试网页</span>






测试网页哈哈哈



1

2



相机占位标签

点击跳转哈哈哈

4

5

6






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();
}
}

snap.jpeg

前端样式改变和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;
}
原文地址:https://juejin.cn/post/7018037732412768269

2 个评论

大佬您好 请问下怎么在嵌入自己的组件呢 比如我想在自定义的那个html标签里嵌入android的EditText
不知道怎么利用那个onSurfaceCreated里面返回的surface

要回复文章请先登录注册