catch react native IllegalViewOperationException

0x00 React Native Default Exception Handler

React Native官方支持在生成ReactInstanceManager时使用NativeModuleCallExceptionHandler接口来设置自己的异常处理器,防止js/react-native代码异常导致native端直接崩溃,样例代码如下:

  ReactInstanceManagerBuilder builder = ReactInstanceManager.builder()
                .setApplication(context.getApplicationContext())
                .addPackage(new MainReactPackage())
                .setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
                .setNativeModuleCallExceptionHandler(new NativeModuleCallExceptionHandler {

			        @Override
			        public void handleException(Exception e) {
			        	// 自己处理react-native异常.
			        }
			    });

0x01 com.facebook.react.uimanager.IllegalViewOperationException

app发布到线上之后,仍然出现了少部分未能捕获的异常,堆栈信息如下:

com.facebook.react.uimanager.IllegalViewOperationException
Trying to add unknown view tag: 500 detail: View tag:496 children(2): [ 497,498, ], viewsToAdd(1): [ [2,500], ],
1 com.facebook.react.uimanager.NativeViewHierarchyManager.manageChildren(NativeViewHierarchyManager.java:387)
2 com.facebook.react.uimanager.UIViewOperationQueue$ManageChildrenOperation.execute(UIViewOperationQueue.java:179)
3 com.facebook.react.uimanager.UIViewOperationQueue$2.run(UIViewOperationQueue.java:787)
4 com.facebook.react.uimanager.UIViewOperationQueue.flushPendingBatches(UIViewOperationQueue.java:843)
5 com.facebook.react.uimanager.UIViewOperationQueue.access$1600(UIViewOperationQueue.java:48)
6 com.facebook.react.uimanager.UIViewOperationQueue$DispatchUIFrameCallback.doFrameGuarded(UIViewOperationQueue.java:889)
7 com.facebook.react.uimanager.GuardedFrameCallback.doFrame(GuardedFrameCallback.java:31)
8 com.facebook.react.modules.core.ReactChoreographer$ReactChoreographerDispatcher.doFrame(ReactChoreographer.java:129)
9 com.facebook.react.modules.core.ChoreographerCompat$FrameCallback$1.doFrame(ChoreographerCompat.java:107)
10 android.view.Choreographer$CallbackRecord.run(Choreographer.java:798)
11 android.view.Choreographer.doCallbacks(Choreographer.java:603)
12 android.view.Choreographer.doFrame(Choreographer.java:571)
13 android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:786)
14 android.os.Handler.handleCallback(Handler.java:815)

从日志中可以看出,抛出的异常为IllegalViewOperationException,该异常的继承链如下,可以发现该异常继承自RuntimeException

IllegalViewOperationException -> JSApplicationCausedNativeException -> RuntimeException

对比GuardedFrameCallback的源代码,可以发现在GuardedFrameCallback.doFrame方法中捕获了全部的RuntimeException

package com.facebook.react.uimanager;

public abstract class GuardedFrameCallback extends ChoreographerCompat.FrameCallback {

  @Override
  public final void doFrame(long frameTimeNanos) {
    try {
      doFrameGuarded(frameTimeNanos);
    } catch (RuntimeException e) {
      mReactContext.handleException(e);
    }
  }
}

进一步往下追踪,ReactContexthandleException()方法的源代码如下:

public void handleException(RuntimeException e) {
	if (mCatalystInstance != null &&
	    !mCatalystInstance.isDestroyed() &&
	    mNativeModuleCallExceptionHandler != null) {
	  mNativeModuleCallExceptionHandler.handleException(e);
	} else {
	  throw e;
	}
}

由于我们先前已经设置了NativeModuleCallExceptionHandler处理器;同时异常并没有被捕获到;可知出现异常时mCatalystInstance要么为null,要么已经销毁了。

Catch IllegalViewOperationException

为了捕获这个异常,我们的做法大致如下:

@Aspect
public class ReactContextAspect {

    @Around("execution (* com.facebook.react.bridge.ReactContext.handleException(..))")
    public Object injectHandleException(ProceedingJoinPoint joinPoint) throws Throwable {
        try {
            joinPoint.proceed(joinPoint.getArgs());
		 } catch (Throwable e) {
            LOGGER.d("ReactContextAspect", "handleException", e);
			// 上报错误
     	 }
        return null;
    }

    private static class ReactContextHandleException extends Throwable {
        public ReactContextHandleException(Throwable cause) {
            super(cause);
        }
    }
}