React 本机自定义 webview 在开发时防止 SSL 错误

React native custom webview prevent SSL errors while developing

问题
尝试在 Android.
上使用 react-native-webview 连接到我的本地开发服务器 您可以使用 http://10.0.0.2 作为默认网关来连接到模拟器上的本地计算机。或者,我 运行 在我的 phone 上构建并连接到我机器的 IPV4 地址。
我需要 Crypto API 并且只能在 HTTPS 上使用,这意味着我需要连接到 https://10.0.0.2.
我没有使用自签名证书。

快速而肮脏的解决方案
com.reactnativecommunity.webview包的onReceivedSslError方法的第一行调用handler.proceed()
虽然这可能有效,但它不是首选方式,因为它不受源代码控制,每次制作发布版本或全新安装项目时都需要删除。

解决方案
查看下面的答案
在开发版本上创建自定义 webview 以规避 SSL 错误(或任何其他 webview 方法)。

我的解决方案

1. 仅创建调试 class CustomWebviewManager.java
我们不希望我们的自定义 webview 管理器在发布版本中结束。

package nl.myapp;

import android.webkit.WebView;
import android.webkit.SslErrorHandler;
import android.net.http.SslError;

import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.reactnativecommunity.webview.RNCWebViewManager;

@ReactModule(name = CustomWebViewManager.REACT_CLASS)
public class CustomWebViewManager extends RNCWebViewManager {
    protected static final String REACT_CLASS = "RNCCustomWebView"; // React native import name

    protected static class CustomWebviewClient extends RNCWebViewClient {
        @Override
        public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error){
            // Prevent SSL errors
            handler.proceed();
        }
    }

    @Override
    public String getName() {
        return REACT_CLASS;
    }

    @Override
    protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
        // Set our custom client as webview client
        view.setWebViewClient(new CustomWebviewClient());
    }
}

2.新建class注册React PackagesMyappAppPackage.java
我们应该只在调试模式下注册调试 class 并作为 ViewManager.
下一步注册我们的包后React Native会自动调用createViewManagers
使用 BuildConfig.DEBUG 仅在调试版本中执行。
我们不能直接实例化我们的 class,因为它会导致产品构建错误。这就是为什么我们使用 Class.forName

package nl.myapp;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class MyappAppPackage implements ReactPackage {

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        List<ViewManager> viewManagers = new ArrayList<>();

        if (BuildConfig.DEBUG) {
            // Add custom webview manager to circumvent SSL errors
            try {
                Class<ViewManager> c = (Class<ViewManager>) Class.forName("nl.myapp.CustomWebViewManager");
                viewManagers.add(c.newInstance());
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            }
        }

        return viewManagers;
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}

3.注册我们的包

package nl.myapp;

import android.app.Application;

import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import java.util.List;

public class MainApplication extends Application implements ReactApplication {
    private final ReactNativeHost mReactNativeHost =
        new ReactNativeHost(this) {
          @Override
          public boolean getUseDeveloperSupport() {
              return BuildConfig.DEBUG;
          }

          @Override
          protected List<ReactPackage> getPackages() {
              @SuppressWarnings("UnnecessaryLocalVariable")
              List<ReactPackage> packages = new PackageList(this).getPackages();

              packages.add(new MyappAppPackage());

              return packages;
          }

          @Override
          protected String getJSMainModuleName() {
              return "index";
          }
        };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    ...
}

4. 最后在开发时使用我们自定义的webview
将它放在单独的文件中,否则热重载将不起作用,因为 requireNativeComponent 会尝试再次注册 ViewManager
src/components/native/RNCCustomWebView.js

import {requireNativeComponent} from 'react-native';

module.exports = requireNativeComponent('RNCCustomWebView');

src/components/webview.js

import React from 'react';
import { WebView } from 'react-native-webview';
import RNCCustomWebView from './native/RNCCustomWebView';

// ...

const renderWebView = () => {
  // ...

  const nativeConfig = {};
  if (__DEV__) { // __DEV__ is set by RN on debug builds
    // Set custom component to circumvent SSL errors
    nativeConfig.component = RNCCustomWebView;
  }

  return (
    <WebView
      nativeConfig={nativeConfig}
      source="https://10.0.0.1"
    />
  );
};