最近在项目中发现一个现象,当给js thread处理大量数据的同时对APP界面进行交互:
1.原生控件:马上得到响应,没有延迟(android seekbar)
2.RN自定义组件:延迟约200毫秒后才进入触摸或手势事件
查询了一番资料发现:
React Native有三个重要的线程:
1.Javascript thread。JS代码的执行线程,即进行了JS代码的运算处理,也承担着所有JS和原生代码的交互,可谓是亚历山大。这也是最值得进行优化线程内操作的一个环节。
2.UI thread (也称Main thread)。负责rn所有的ui绘制和调用原生的功能例如蓝牙。
3.Shadow thread。布局引擎(Yoga)转换布局使用。主要通过创建Shadow Tree模拟react的结构树。将我们js代码中flexbox布局通过yoga引擎转换为原生布局。
目前得出的结论是:如果你的页面出现了操作延迟变慢,可以排查优化你在JS线程中的耗时运算,另一方面减少不必要的页面重绘。
具体原理还在进一步探索,这里先埋了一个坑,我的理解暂时还不够充分,待整理好再来编辑填坑。
更新:
翻看了一些源码后发现
在ReactNativeRenderer-dev.js文件中,
function receiveTouches(eventTopLevelType, touches, changedIndices) {
console.log('receiveTouches'+' test '+touches)
var changedTouches =
eventTopLevelType === "topTouchEnd" ||
eventTopLevelType === "topTouchCancel"
? removeTouchesAtIndices(touches, changedIndices)
: touchSubsequence(touches, changedIndices);
for (var jj = 0; jj < changedTouches.length; jj++) {
var touch = changedTouches[jj];
// Touch objects can fulfill the role of `DOM` `Event` objects if we set
// the `changedTouches`/`touches`. This saves allocations.
touch.changedTouches = changedTouches;
touch.touches = touches;
var nativeEvent = touch;
var rootNodeID = null;
var target = nativeEvent.target;
if (target !== null && target !== undefined) {
if (target < 1) {
{
warningWithoutStack$1(
false,
"A view is reporting that a touch occurred on tag zero."
);
}
} else {
rootNodeID = target;
}
}
// $FlowFixMe Shouldn't we *not* call it if rootNodeID is null?
_receiveRootNodeIDEvent(rootNodeID, eventTopLevelType, nativeEvent);
}
}
receiveTouches方法收到的时间会受到js线程阻塞的影响推迟。
进一步的分析就到了js中的event loop这一步,
macro-task 宏任务:代码script,setTimeout,setInterval
micro-task 微任务:Promise
还是JS单线程的局限性,所以异步的操作实际上是按照不断的循环将事务放进线程中去执行,当前执行的代码阻塞会延后下一段本该执行的代码块。
那么是否有什么曲线救国的方案呢?我想到一个,在android的mainactivity中复写dispatchtouchevent方法
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
Log.d(TAG, "test dispatchTouchEvent action:"+ev.getAction());
XXXApi.dispatchTouchEvent=ev.getAction();
return super.dispatchTouchEvent(ev);
}
我们将中间层的一个变量赋值
public static int dispatchTouchEvent=1;// 0按下 2移动 1抬起(无事件)
@ReactMethod(isBlockingSynchronousMethod = true)
public int getTouchStatus() {
Log.d(TAG+"dispatch", dispatchTouchEvent+"");
return dispatchTouchEvent;
}
再通过JS处理大量代码之前判断原生端的触摸状态,可以规避一些线程阻塞。
setTimeout(function () {
console.log('test for 5000 inside2 start');
for (var i = 0; i < 1000000000; i++) {
let status=XXXApi.getTouchStatus()
// let status=2
if (status!=1){
console.log('test for 5000 inside2 当前用户有操作 break'+status);
break
}
if (i%100000000==0){
console.log('test for 5000 inside2 now index:' ,i)
}
// console.log(TAG + "test run: 1",i)
}
console.log('test for 5000 inside2 end');
}, 50);