上一篇文章讲了ios的这里接着讲RN-android-自签名https的配置,首先android比较复杂,首先用AS打开android工程
android有4个地方得改
- fetch (.jar包里的代码不能直接改)
- webview (.jar包里的代码不能直接改)
- Image (.jar包里的代码不能直接改--)
- react-native-fetch-blob(可以直接改源码-RNFetchBlobReq
RNFetchBlob 这两个类) - 如果使用了react-native-navigation时,在我们copy出MainReactPackage使用时会报错,注释掉react-native-navigation报错的地方就行
注意:RN的不同版本,会有不同的依赖问题,得根据报错一个一个改,很麻烦
fetch
-
修改路径和原理
安卓三方的依赖库在这可以看到
NetworkingModule.java文件就是我们android最底层的网络请求工具类,每次创建NetworkingModule对象的时候,我们会传入一个okhttpclient的对象:
public NetworkingModule(ReactApplicationContext context) {
this(context, (String)null, OkHttpClientProvider.createClient(), (List)null);
}
public NetworkingModule(ReactApplicationContext context, List<NetworkInterceptorCreator> networkInterceptorCreators) {
this(context, (String)null, OkHttpClientProvider.createClient(), networkInterceptorCreators);
}
public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) {
this(context, defaultUserAgent, OkHttpClientProvider.createClient(), (List)null);
}
可以看到,每次都是通过:
OkHttpClientProvider.createClient()
的方式创建了一个okhttpclient对象,最后发送请求为:
@ReactMethod
public void sendRequest(ExecutorToken executorToken, String method, String url, final int requestId, ReadableArray headers, ReadableMap data, final String responseType, final boolean useIncrementalUpdates, int timeout) {
okhttp3.Request.Builder requestBuilder = (new okhttp3.Request.Builder()).url(url);
if(requestId != 0) {
requestBuilder.tag(Integer.valueOf(requestId));
}
final RCTDeviceEventEmitter eventEmitter = this.getEventEmitter(executorToken);
Builder clientBuilder = this.mClient.newBuilder();
....
所以我们只需要替换掉默认的httpclient,然后为其添加上证书认证就可以了,我们打开 OkHttpClientProvider.createClient()方法:
public static OkHttpClient createClient() {
Builder client = (new Builder()).connectTimeout(0L, TimeUnit.MILLISECONDS).readTimeout(0L, TimeUnit.MILLISECONDS).writeTimeout(0L, TimeUnit.MILLISECONDS).cookieJar(new ReactCookieJarContainer());
return enableTls12OnPreLollipop(client).build();
}
OkHttpClientProvider这个工具类就是为我们提供了一个修改默认okhttpclient的方法,我们看到其中有一个方法:
public static void replaceOkHttpClient(OkHttpClient client) {
sClient = client;
}
这个方法就是替换掉rn中网络请求默认的okhttpclient方法。
所以我们在程序初始化的时候改掉默认的okhttpclient.
- 首先找到你项目的application文件,在oncreate方法中提供其方法:
@Override
public void onCreate() {
...
//RN OKHTTP添加https证书
OkHttpClientProvider.replaceOkHttpClient(initCustomOkHttpClient());
...
}
//自定义初始化client方法-
public OkHttpClient initCustomOkHttpClient() {
OkHttpClient.Builder client = new OkHttpClient.Builder()
.connectTimeout(30 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(30 * 1000, TimeUnit.MILLISECONDS)
.writeTimeout(30 * 1000, TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer());
client.addNetworkInterceptor(new StethoInterceptor());
client.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
try {
//你的证书文件,放在android的assets文件夹下
setCertificates(client, getAssets().open("CA.crt"));
client.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} catch (IOException e) {
e.printStackTrace();
}
OkHttpClient.Builder builder = OkHttpClientProvider.enableTls12OnPreLollipop(client);
return builder.build();
}
//处理证书
public void setCertificates(OkHttpClient.Builder client, InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
try {
if (certificate != null)
certificate.close();
} catch (IOException e) {
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
sslContext.init(
null,
trustManagerFactory.getTrustManagers(),
new SecureRandom()
);
client.sslSocketFactory(sslContext.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
}
- 修改NetworkingModule.java文件的以下方法,把默认创建okhttpclient的方式:OkHttpClientProvider.createClient(),全部改为:OkHttpClientProvider.getOkHttpClient()
public NetworkingModule(ReactApplicationContext context) {
this(context, (String)null, OkHttpClientProvider.createClient(), (List)null);
}
public NetworkingModule(ReactApplicationContext context, List<NetworkInterceptorCreator> networkInterceptorCreators) {
this(context, (String)null, OkHttpClientProvider.createClient(), networkInterceptorCreators);
}
public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) {
this(context, defaultUserAgent, OkHttpClientProvider.createClient(), (List)null);
}
如下:
/**
* @param context the ReactContext of the application
*/
public NetworkingModule(final ReactApplicationContext context) {
this(context, null, OkHttpClientProvider.getOkHttpClient(), null);
}
/**
* @param context the ReactContext of the application
* @param networkInterceptorCreators list of {@link NetworkInterceptorCreator}'s whose create()
* methods would be called to attach the interceptors to the client.
*/
public NetworkingModule(
ReactApplicationContext context,
List<NetworkInterceptorCreator> networkInterceptorCreators) {
this(context, null, OkHttpClientProvider.getOkHttpClient(), networkInterceptorCreators);
}
/**
* @param context the ReactContext of the application
* @param defaultUserAgent the User-Agent header that will be set for all requests where the
* caller does not provide one explicitly
*/
public NetworkingModule(ReactApplicationContext context, String defaultUserAgent) {
this(context, defaultUserAgent, OkHttpClientProvider.getOkHttpClient(), null);
}
问题:react-native的第三方依赖库,改不了源码咋办呢?
我们直接copy一份NetworkingModule跟mainreactpackage的代码,然后在application中把mainreactpackage替换成我们copy出去的那一份,
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
//替换成我们copy的那个文件
new MainReactPackage(),
new VectorIconsPackage(),
new RCTSplashScreenPackage(),
new RNDeviceInfo(),
new AndroidModulePackage(),
new RCTSwipeRefreshLayoutPackage()
);
}
};
然后点进我们copy的那个MainReactPackage文件,再把MainReactPackage中的:
new ModuleSpec(NetworkingModule.class, new Provider<NativeModule>() {
@Override
public NativeModule get() {
//替换成我们自己copy的那个文件
return new NetworkingModule(context);
}
}),
-
Image部分
但是RN对应的那一部分.jar包是不可改的,,主要的问题还是怎么去改动代码
摘要 :
如何改动android.jar 包rn_androi_https
- webView
- ReactWebviewManager.java
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
* <p>
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package petrochina.cplh.qqgl2.htttps;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Picture;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;
import android.util.Log;
import android.view.ViewGroup.LayoutParams;
import android.webkit.ConsoleMessage;
import android.webkit.CookieManager;
import android.webkit.GeolocationPermissions;
import android.webkit.JavascriptInterface;
import android.webkit.ValueCallback;
import android.webkit.WebChromeClient;
import android.webkit.WebResourceResponse;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.ContentSizeChangeEvent;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.views.webview.WebViewConfig;
import com.facebook.react.views.webview.events.TopLoadingErrorEvent;
import com.facebook.react.views.webview.events.TopLoadingFinishEvent;
import com.facebook.react.views.webview.events.TopLoadingStartEvent;
import com.facebook.react.views.webview.events.TopMessageEvent;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.security.KeyStore;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.annotation.Nullable;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
/**
* Manages instances of {@link WebView}
* <p>
* Can accept following commands:
* - GO_BACK
* - GO_FORWARD
* - RELOAD
* <p>
* {@link WebView} instances could emit following direct events:
* - topLoadingFinish
* - topLoadingStart
* - topLoadingError
* <p>
* Each event will carry the following properties:
* - target - view's react tag
* - url - url set for the webview
* - loading - whether webview is in a loading state
* - title - title of the current page
* - canGoBack - boolean, whether there is anything on a history stack to go back
* - canGoForward - boolean, whether it is possible to request GO_FORWARD command
*/
@ReactModule(name = ReactWebViewManager.REACT_CLASS)
public class ReactWebViewManager extends SimpleViewManager<WebView> {
protected static final String REACT_CLASS = "RCTWebView";
protected static final String HTML_ENCODING = "UTF-8";
protected static final String HTML_MIME_TYPE = "text/html";
protected static final String BRIDGE_NAME = "__REACT_WEB_VIEW_BRIDGE";
protected static final String HTTP_METHOD_POST = "POST";
public static final int COMMAND_GO_BACK = 1;
public static final int COMMAND_GO_FORWARD = 2;
public static final int COMMAND_RELOAD = 3;
public static final int COMMAND_STOP_LOADING = 4;
public static final int COMMAND_POST_MESSAGE = 5;
public static final int COMMAND_INJECT_JAVASCRIPT = 6;
// Use `webView.loadUrl("about:blank")` to reliably reset the view
// state and release page resources (including any running JavaScript).
protected static final String BLANK_URL = "about:blank";
protected WebViewConfig mWebViewConfig;
protected @Nullable
WebView.PictureListener mPictureListener;
private static ThemedReactContext reactContext;
protected static class ReactWebViewClient extends WebViewClient {
// ---https 双向认证------
private SSLContext sslContext;
public ReactWebViewClient() {
// 添加https证书
try {
InputStream is = reactContext.getAssets().open("CNPCCA.cer");
NetConfig.addCertificate(is); // 这里将证书读取出来,,放在配置中byte[]里
} catch (IOException ioe) {
ioe.printStackTrace();
}
// 添加证书cer
List<InputStream> certificates = new ArrayList<>();
List<byte[]> certs_data = NetConfig.getCertificatesData();
// 将字节数组转为数组输入流
if (certs_data != null && !certs_data.isEmpty()) {
for (byte[] bytes : certs_data) {
certificates.add(new ByteArrayInputStream(bytes));
}
}
try {
prepareSslPinning(certificates);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public WebResourceResponse shouldInterceptRequest(final WebView view, String url) {
Log.i("shouldInterceptRequest", "shouldInterceptRequest1");
if (url.indexOf("https") != -1) {
return processRequest(url);
} else {
return null;
}
}
private WebResourceResponse processRequest(String webUrl) {
// LogUtils.i("SSL_PINNING_WEBVIEWS", "GET: " + webUrl.toString());
try {
// Setup connection
URL url = new URL(webUrl);
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
// Set SSL Socket Factory for this request
urlConnection.setSSLSocketFactory(sslContext.getSocketFactory());
urlConnection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});//很重要,校验证书
// Get content, contentType and encoding
InputStream is = urlConnection.getInputStream();
String contentType = urlConnection.getContentType();
String encoding = urlConnection.getContentEncoding();
// If got a contentType header
if (contentType != null) {
String mimeType = contentType;
// Parse mime type from contenttype string
if (contentType.contains(";")) {
mimeType = contentType.split(";")[0].trim();
}
// LogUtils.i("SSL_PINNING_WEBVIEWS", "Mime: " + mimeType);
// Return the response
return new WebResourceResponse(mimeType, encoding, is);
}
} catch (Exception e) {
e.printStackTrace();
// LogUtils.i("SSL_PINNING_WEBVIEWS", e.getLocalizedMessage());
}
// Return empty response for this request
return new WebResourceResponse(null, null, null);
}
private void prepareSslPinning(List<InputStream> certificates) throws IOException {
try {
// 服务器端需要验证的客户端证书,其实就是客户端的keystore
KeyManagerFactory keyManagerFactory = KeyManagerFactory
.getInstance("X509");
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509");
KeyStore keyStore = KeyStore.getInstance("PKCS12");
KeyStore keyStore2 = KeyStore.getInstance(KeyStore.getDefaultType());
//读取证书
InputStream ksIn = reactContext.getResources().getAssets().open("client.p12");//***:你的p12证书
//加载证书
keyStore2.load(null);
keyStore.load(ksIn, "cplh123456".toCharArray());//***:p12证书的密码,必须
ksIn.close();
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
try {
for (int i = 0, size = certificates.size(); i < size; ) {
InputStream certificate = certificates.get(i);
String certificateAlias = Integer.toString(i++);
keyStore2.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
if (certificate != null)
certificate.close();
}
} catch (IOException e) {
e.printStackTrace();
}
sslContext = SSLContext.getInstance("TLS");
// TrustManagerFactory trustManagerFactory =
//
// TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, "cplh123456".toCharArray());
trustManagerFactory.init(keyStore2);
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
} catch (Exception e) {
e.printStackTrace();
}
}
// ---https 双向认证------
protected boolean mLastLoadFailed = false;
protected @Nullable
ReadableArray mUrlPrefixesForDefaultIntent;
@Override
public void onPageFinished(WebView webView, String url) {
super.onPageFinished(webView, url);
if (!mLastLoadFailed) {
ReactWebView reactWebView = (ReactWebView) webView;
reactWebView.callInjectedJavaScript();
reactWebView.linkBridge();
emitFinishEvent(webView, url);
}
}
@Override
public void onPageStarted(WebView webView, String url, Bitmap favicon) {
super.onPageStarted(webView, url, favicon);
mLastLoadFailed = false;
dispatchEvent(
webView,
new TopLoadingStartEvent(
webView.getId(),
createWebViewEvent(webView, url)));
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
boolean useDefaultIntent = false;
if (mUrlPrefixesForDefaultIntent != null && mUrlPrefixesForDefaultIntent.size() > 0) {
ArrayList<Object> urlPrefixesForDefaultIntent =
mUrlPrefixesForDefaultIntent.toArrayList();
for (Object urlPrefix : urlPrefixesForDefaultIntent) {
if (url.startsWith((String) urlPrefix)) {
useDefaultIntent = true;
break;
}
}
}
if (!useDefaultIntent &&
(url.startsWith("http://") || url.startsWith("https://") ||
url.startsWith("file://") || url.equals("about:blank"))) {
return false;
} else {
try {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.getContext().startActivity(intent);
} catch (ActivityNotFoundException e) {
FLog.w(ReactConstants.TAG, "activity not found to handle uri scheme for: " + url, e);
}
return true;
}
}
@Override
public void onReceivedError(
WebView webView,
int errorCode,
String description,
String failingUrl) {
super.onReceivedError(webView, errorCode, description, failingUrl);
mLastLoadFailed = true;
// In case of an error JS side expect to get a finish event first, and then get an error event
// Android WebView does it in the opposite way, so we need to simulate that behavior
emitFinishEvent(webView, failingUrl);
WritableMap eventData = createWebViewEvent(webView, failingUrl);
eventData.putDouble("code", errorCode);
eventData.putString("description", description);
dispatchEvent(
webView,
new TopLoadingErrorEvent(webView.getId(), eventData));
}
protected void emitFinishEvent(WebView webView, String url) {
dispatchEvent(
webView,
new TopLoadingFinishEvent(
webView.getId(),
createWebViewEvent(webView, url)));
}
protected WritableMap createWebViewEvent(WebView webView, String url) {
WritableMap event = Arguments.createMap();
event.putDouble("target", webView.getId());
// Don't use webView.getUrl() here, the URL isn't updated to the new value yet in callbacks
// like onPageFinished
event.putString("url", url);
event.putBoolean("loading", !mLastLoadFailed && webView.getProgress() != 100);
event.putString("title", webView.getTitle());
event.putBoolean("canGoBack", webView.canGoBack());
event.putBoolean("canGoForward", webView.canGoForward());
return event;
}
public void setUrlPrefixesForDefaultIntent(ReadableArray specialUrls) {
mUrlPrefixesForDefaultIntent = specialUrls;
}
}
/**
* Subclass of {@link WebView} that implements {@link LifecycleEventListener} interface in order
* to call {@link WebView#destroy} on activity destroy event and also to clear the client
*/
protected static class ReactWebView extends WebView implements LifecycleEventListener {
protected @Nullable
String injectedJS;
protected boolean messagingEnabled = false;
protected @Nullable
ReactWebViewClient mReactWebViewClient;
protected class ReactWebViewBridge {
ReactWebView mContext;
ReactWebViewBridge(ReactWebView c) {
mContext = c;
}
@JavascriptInterface
public void postMessage(String message) {
mContext.onMessage(message);
}
}
/**
* WebView must be created with an context of the current activity
* <p>
* Activity Context is required for creation of dialogs internally by WebView
* Reactive Native needed for access to ReactNative internal system functionality
*/
public ReactWebView(ThemedReactContext reactContext) {
super(reactContext);
}
@Override
public void onHostResume() {
// do nothing
}
@Override
public void onHostPause() {
// do nothing
}
@Override
public void onHostDestroy() {
cleanupCallbacksAndDestroy();
}
@Override
public void setWebViewClient(WebViewClient client) {
super.setWebViewClient(client);
mReactWebViewClient = (ReactWebViewClient) client;
}
public @Nullable
ReactWebViewClient getReactWebViewClient() {
return mReactWebViewClient;
}
public void setInjectedJavaScript(@Nullable String js) {
injectedJS = js;
}
protected ReactWebViewBridge createReactWebViewBridge(ReactWebView webView) {
return new ReactWebViewBridge(webView);
}
public void setMessagingEnabled(boolean enabled) {
if (messagingEnabled == enabled) {
return;
}
messagingEnabled = enabled;
if (enabled) {
addJavascriptInterface(createReactWebViewBridge(this), BRIDGE_NAME);
linkBridge();
} else {
removeJavascriptInterface(BRIDGE_NAME);
}
}
public void callInjectedJavaScript() {
if (getSettings().getJavaScriptEnabled() &&
injectedJS != null &&
!TextUtils.isEmpty(injectedJS)) {
loadUrl("javascript:(function() {\n" + injectedJS + ";\n})();");
}
}
public void linkBridge() {
if (messagingEnabled) {
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// See isNative in lodash
String testPostMessageNative = "String(window.postMessage) === String(Object.hasOwnProperty).replace('hasOwnProperty', 'postMessage')";
evaluateJavascript(testPostMessageNative, new ValueCallback<String>() {
@Override
public void onReceiveValue(String value) {
if (value.equals("true")) {
FLog.w(ReactConstants.TAG, "Setting onMessage on a WebView overrides existing values of window.postMessage, but a previous value was defined");
}
}
});
}
loadUrl("javascript:(" +
"window.originalPostMessage = window.postMessage," +
"window.postMessage = function(data) {" +
BRIDGE_NAME + ".postMessage(String(data));" +
"}" +
")");
}
}
public void onMessage(String message) {
dispatchEvent(this, new TopMessageEvent(this.getId(), message));
}
protected void cleanupCallbacksAndDestroy() {
setWebViewClient(null);
destroy();
}
}
public ReactWebViewManager() {
mWebViewConfig = new WebViewConfig() {
public void configWebView(WebView webView) {
}
};
}
public ReactWebViewManager(WebViewConfig webViewConfig) {
mWebViewConfig = webViewConfig;
}
@Override
public String getName() {
return REACT_CLASS;
}
protected ReactWebView createReactWebViewInstance(ThemedReactContext reactContext) {
return new ReactWebView(reactContext);
}
@Override
protected WebView createViewInstance(ThemedReactContext reactContext) {
ReactWebView webView = createReactWebViewInstance(reactContext);
webView.setWebChromeClient(new WebChromeClient() {
@Override
public boolean onConsoleMessage(ConsoleMessage message) {
if (ReactBuildConfig.DEBUG) {
return super.onConsoleMessage(message);
}
// Ignore console logs in non debug builds.
return true;
}
@Override
public void onGeolocationPermissionsShowPrompt(String origin, GeolocationPermissions.Callback callback) {
callback.invoke(origin, true, false);
}
});
reactContext.addLifecycleEventListener(webView);
mWebViewConfig.configWebView(webView);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setDisplayZoomControls(false);
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
webView.getSettings().setAllowFileAccess(true);
webView.getSettings().setAllowFileAccessFromFileURLs(true);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setBlockNetworkImage(false);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
webView.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
// Fixes broken full-screen modals/galleries due to body height being 0.
webView.setLayoutParams(
new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
WebView.setWebContentsDebuggingEnabled(true);
}
return webView;
}
@ReactProp(name = "javaScriptEnabled")
public void setJavaScriptEnabled(WebView view, boolean enabled) {
view.getSettings().setJavaScriptEnabled(enabled);
}
@ReactProp(name = "thirdPartyCookiesEnabled")
public void setThirdPartyCookiesEnabled(WebView view, boolean enabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
CookieManager.getInstance().setAcceptThirdPartyCookies(view, enabled);
}
}
@ReactProp(name = "scalesPageToFit")
public void setScalesPageToFit(WebView view, boolean enabled) {
view.getSettings().setUseWideViewPort(!enabled);
}
@ReactProp(name = "domStorageEnabled")
public void setDomStorageEnabled(WebView view, boolean enabled) {
view.getSettings().setDomStorageEnabled(enabled);
}
@ReactProp(name = "userAgent")
public void setUserAgent(WebView view, @Nullable String userAgent) {
if (userAgent != null) {
// TODO(8496850): Fix incorrect behavior when property is unset (uA == null)
view.getSettings().setUserAgentString(userAgent);
}
}
@ReactProp(name = "mediaPlaybackRequiresUserAction")
public void setMediaPlaybackRequiresUserAction(WebView view, boolean requires) {
view.getSettings().setMediaPlaybackRequiresUserGesture(requires);
}
@ReactProp(name = "allowUniversalAccessFromFileURLs")
public void setAllowUniversalAccessFromFileURLs(WebView view, boolean allow) {
view.getSettings().setAllowUniversalAccessFromFileURLs(allow);
}
@ReactProp(name = "saveFormDataDisabled")
public void setSaveFormDataDisabled(WebView view, boolean disable) {
view.getSettings().setSaveFormData(!disable);
}
@ReactProp(name = "injectedJavaScript")
public void setInjectedJavaScript(WebView view, @Nullable String injectedJavaScript) {
((ReactWebView) view).setInjectedJavaScript(injectedJavaScript);
}
@ReactProp(name = "messagingEnabled")
public void setMessagingEnabled(WebView view, boolean enabled) {
((ReactWebView) view).setMessagingEnabled(enabled);
}
@ReactProp(name = "source")
public void setSource(WebView view, @Nullable ReadableMap source) {
if (source != null) {
if (source.hasKey("html")) {
String html = source.getString("html");
if (source.hasKey("baseUrl")) {
view.loadDataWithBaseURL(
source.getString("baseUrl"), html, HTML_MIME_TYPE, HTML_ENCODING, null);
} else {
view.loadData(html, HTML_MIME_TYPE, HTML_ENCODING);
}
return;
}
if (source.hasKey("uri")) {
String url = source.getString("uri");
String previousUrl = view.getUrl();
if (previousUrl != null && previousUrl.equals(url)) {
return;
}
if (source.hasKey("method")) {
String method = source.getString("method");
if (method.equals(HTTP_METHOD_POST)) {
byte[] postData = null;
if (source.hasKey("body")) {
String body = source.getString("body");
try {
postData = body.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
postData = body.getBytes();
}
}
if (postData == null) {
postData = new byte[0];
}
view.postUrl(url, postData);
return;
}
}
HashMap<String, String> headerMap = new HashMap<>();
if (source.hasKey("headers")) {
ReadableMap headers = source.getMap("headers");
ReadableMapKeySetIterator iter = headers.keySetIterator();
while (iter.hasNextKey()) {
String key = iter.nextKey();
if ("user-agent".equals(key.toLowerCase(Locale.ENGLISH))) {
if (view.getSettings() != null) {
view.getSettings().setUserAgentString(headers.getString(key));
}
} else {
headerMap.put(key, headers.getString(key));
}
}
}
view.loadUrl(url, headerMap);
return;
}
}
view.loadUrl(BLANK_URL);
}
@ReactProp(name = "onContentSizeChange")
public void setOnContentSizeChange(WebView view, boolean sendContentSizeChangeEvents) {
if (sendContentSizeChangeEvents) {
view.setPictureListener(getPictureListener());
} else {
view.setPictureListener(null);
}
}
@ReactProp(name = "mixedContentMode")
public void setMixedContentMode(WebView view, @Nullable String mixedContentMode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (mixedContentMode == null || "never".equals(mixedContentMode)) {
view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
} else if ("always".equals(mixedContentMode)) {
view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
} else if ("compatibility".equals(mixedContentMode)) {
view.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
}
}
}
@ReactProp(name = "urlPrefixesForDefaultIntent")
public void setUrlPrefixesForDefaultIntent(
WebView view,
@Nullable ReadableArray urlPrefixesForDefaultIntent) {
ReactWebViewClient client = ((ReactWebView) view).getReactWebViewClient();
if (client != null && urlPrefixesForDefaultIntent != null) {
client.setUrlPrefixesForDefaultIntent(urlPrefixesForDefaultIntent);
}
}
@Override
protected void addEventEmitters(ThemedReactContext reactContext, WebView view) {
this.reactContext = reactContext;
// Do not register default touch emitter and let WebView implementation handle touches
view.setWebViewClient(new ReactWebViewClient());
}
@Override
public @Nullable
Map<String, Integer> getCommandsMap() {
return MapBuilder.of(
"goBack", COMMAND_GO_BACK,
"goForward", COMMAND_GO_FORWARD,
"reload", COMMAND_RELOAD,
"stopLoading", COMMAND_STOP_LOADING,
"postMessage", COMMAND_POST_MESSAGE,
"injectJavaScript", COMMAND_INJECT_JAVASCRIPT
);
}
@Override
public void receiveCommand(WebView root, int commandId, @Nullable ReadableArray args) {
switch (commandId) {
case COMMAND_GO_BACK:
root.goBack();
break;
case COMMAND_GO_FORWARD:
root.goForward();
break;
case COMMAND_RELOAD:
root.reload();
break;
case COMMAND_STOP_LOADING:
root.stopLoading();
break;
case COMMAND_POST_MESSAGE:
try {
JSONObject eventInitDict = new JSONObject();
eventInitDict.put("data", args.getString(0));
root.loadUrl("javascript:(function () {" +
"var event;" +
"var data = " + eventInitDict.toString() + ";" +
"try {" +
"event = new MessageEvent('message', data);" +
"} catch (e) {" +
"event = document.createEvent('MessageEvent');" +
"event.initMessageEvent('message', true, true, data.data, data.origin, data.lastEventId, data.source);" +
"}" +
"document.dispatchEvent(event);" +
"})();");
} catch (JSONException e) {
throw new RuntimeException(e);
}
break;
case COMMAND_INJECT_JAVASCRIPT:
root.loadUrl("javascript:" + args.getString(0));
break;
}
}
@Override
public void onDropViewInstance(WebView webView) {
super.onDropViewInstance(webView);
((ThemedReactContext) webView.getContext()).removeLifecycleEventListener((ReactWebView) webView);
((ReactWebView) webView).cleanupCallbacksAndDestroy();
}
protected WebView.PictureListener getPictureListener() {
if (mPictureListener == null) {
mPictureListener = new WebView.PictureListener() {
@Override
public void onNewPicture(WebView webView, Picture picture) {
dispatchEvent(
webView,
new ContentSizeChangeEvent(
webView.getId(),
webView.getWidth(),
webView.getContentHeight()));
}
};
}
return mPictureListener;
}
protected static void dispatchEvent(WebView webView, Event event) {
ReactContext reactContext = (ReactContext) webView.getContext();
EventDispatcher eventDispatcher =
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
eventDispatcher.dispatchEvent(event);
}
}
- react-native-fetch-blob
-
RNFetchBlobReq
-
RNFetchBlob
-
package com.RNFetchBlob;
import android.app.Activity;
import android.app.DownloadManager;
import android.content.Intent;
import android.net.Uri;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
// Cookies
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.network.ForwardingCookieHandler;
import com.facebook.react.modules.network.CookieJarContainer;
import com.facebook.react.modules.network.OkHttpClientProvider;
import com.facebook.react.modules.network.ReactCookieJarContainer;
import okhttp3.OkHttpClient;
import okhttp3.JavaNetCookieJar;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.cert.CertificateFactory;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManagerFactory;
import static android.app.Activity.RESULT_OK;
import static com.RNFetchBlob.RNFetchBlobConst.GET_CONTENT_INTENT;
public class RNFetchBlob extends ReactContextBaseJavaModule {
// Cookies
private final ForwardingCookieHandler mCookieHandler;
private final CookieJarContainer mCookieJarContainer;
private final OkHttpClient mClient;
static ReactApplicationContext RCTContext;
static LinkedBlockingQueue<Runnable> taskQueue = new LinkedBlockingQueue<>();
static ThreadPoolExecutor threadPool = new ThreadPoolExecutor(5, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
static LinkedBlockingQueue<Runnable> fsTaskQueue = new LinkedBlockingQueue<>();
static ThreadPoolExecutor fsThreadPool = new ThreadPoolExecutor(2, 10, 5000, TimeUnit.MILLISECONDS, taskQueue);
static public boolean ActionViewVisible = false;
static HashMap<Integer, Promise> promiseTable = new HashMap<>();
//https --修改 --
public OkHttpClient initCustomOkHttpClient() {
OkHttpClient.Builder client = new OkHttpClient.Builder()
.connectTimeout(30 * 1000, TimeUnit.MILLISECONDS)
.readTimeout(30 * 1000, TimeUnit.MILLISECONDS)
.writeTimeout(30 * 1000, TimeUnit.MILLISECONDS)
.cookieJar(new ReactCookieJarContainer());
// client.addNetworkInterceptor(new StethoInterceptor());
// client.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY));
try {
//你的证书文件,放在android的assets文件夹下
setCertificates(client, RCTContext.getAssets().open("CNPCCA.cer"));
client.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
} catch (IOException e) {
e.printStackTrace();
}
OkHttpClient.Builder builder = OkHttpClientProvider.enableTls12OnPreLollipop(client);
return builder.build();
}
public void setCertificates(OkHttpClient.Builder client, InputStream... certificates) {
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null);
int index = 0;
for (InputStream certificate : certificates) {
String certificateAlias = Integer.toString(index++);
keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
try {
if (certificate != null)
certificate.close();
} catch (IOException e) {
}
}
SSLContext sslContext = SSLContext.getInstance("TLS");
TrustManagerFactory trustManagerFactory =
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
//初始化keystore
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(RCTContext.getAssets().open("client.bks"), "mima".toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, "mima".toCharArray());
sslContext.init(
keyManagerFactory.getKeyManagers(),
trustManagerFactory.getTrustManagers(),
new SecureRandom()
);
client.sslSocketFactory(sslContext.getSocketFactory());
} catch (Exception e) {
e.printStackTrace();
}
}
//https --修改 --
public RNFetchBlob(ReactApplicationContext reactContext) {
super(reactContext);
RCTContext = reactContext;
mClient = initCustomOkHttpClient();
mCookieHandler = new ForwardingCookieHandler(reactContext);
mCookieJarContainer = (CookieJarContainer) mClient.cookieJar();
mCookieJarContainer.setCookieJar(new JavaNetCookieJar(mCookieHandler));
reactContext.addActivityEventListener(new ActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
if(requestCode == GET_CONTENT_INTENT && resultCode == RESULT_OK) {
Uri d = data.getData();
promiseTable.get(GET_CONTENT_INTENT).resolve(d.toString());
promiseTable.remove(GET_CONTENT_INTENT);
}
}
@Override
public void onNewIntent(Intent intent) {
}
});
}
@Override
public String getName() {
return "RNFetchBlob";
}
@Override
public Map<String, Object> getConstants() {
return RNFetchBlobFS.getSystemfolders(this.getReactApplicationContext());
}
@ReactMethod
public void createFile(final String path, final String content, final String encode, final Callback callback) {
threadPool.execute(new Runnable() {
@Override
public void run() {
RNFetchBlobFS.createFile(path, content, encode, callback);
}
});
}
@ReactMethod
public void actionViewIntent(String path, String mime, final Promise promise) {
try {
Intent intent= new Intent(Intent.ACTION_VIEW)
.setDataAndType(Uri.parse("file://" + path), mime);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
this.getReactApplicationContext().startActivity(intent);
ActionViewVisible = true;
final LifecycleEventListener listener = new LifecycleEventListener() {
@Override
public void onHostResume() {
if(ActionViewVisible)
promise.resolve(null);
RCTContext.removeLifecycleEventListener(this);
}
@Override
public void onHostPause() {
}
@Override
public void onHostDestroy() {
}
};
RCTContext.addLifecycleEventListener(listener);
} catch(Exception ex) {
promise.reject(ex.getLocalizedMessage());
}
}
@ReactMethod
public void createFileASCII(final String path, final ReadableArray dataArray, final Callback callback) {
threadPool.execute(new Runnable() {
@Override
public void run() {
RNFetchBlobFS.createFileASCII(path, dataArray, callback);
}
});
}
@ReactMethod
public void writeArrayChunk(final String streamId, final ReadableArray dataArray, final Callback callback) {
RNFetchBlobFS.writeArrayChunk(streamId, dataArray, callback);
}
@ReactMethod
public void unlink(String path, Callback callback) {
RNFetchBlobFS.unlink(path, callback);
}
@ReactMethod
public void mkdir(String path, Callback callback) {
RNFetchBlobFS.mkdir(path, callback);
}
@ReactMethod
public void exists(String path, Callback callback) {
RNFetchBlobFS.exists(path, callback);
}
@ReactMethod
public void cp(final String path, final String dest, final Callback callback) {
threadPool.execute(new Runnable() {
@Override
public void run() {
RNFetchBlobFS.cp(path, dest, callback);
}
});
}
@ReactMethod
public void mv(String path, String dest, Callback callback) {
RNFetchBlobFS.mv(path, dest, callback);
}
@ReactMethod
public void ls(String path, Callback callback) {
RNFetchBlobFS.ls(path, callback);
}
@ReactMethod
public void writeStream(String path, String encode, boolean append, Callback callback) {
new RNFetchBlobFS(this.getReactApplicationContext()).writeStream(path, encode, append, callback);
}
@ReactMethod
public void writeChunk(String streamId, String data, Callback callback) {
RNFetchBlobFS.writeChunk(streamId, data, callback);
}
@ReactMethod
public void closeStream(String streamId, Callback callback) {
RNFetchBlobFS.closeStream(streamId, callback);
}
@ReactMethod
public void removeSession(ReadableArray paths, Callback callback) {
RNFetchBlobFS.removeSession(paths, callback);
}
@ReactMethod
public void readFile(final String path, final String encoding, final Promise promise) {
threadPool.execute(new Runnable() {
@Override
public void run() {
RNFetchBlobFS.readFile(path, encoding, promise);
}
});
}
@ReactMethod
public void writeFileArray(final String path, final ReadableArray data, final boolean append, final Promise promise) {
threadPool.execute(new Runnable() {
@Override
public void run() {
RNFetchBlobFS.writeFile(path, data, append, promise);
}
});
}
@ReactMethod
public void writeFile(final String path, final String encoding, final String data, final boolean append, final Promise promise) {
threadPool.execute(new Runnable() {
@Override
public void run() {
RNFetchBlobFS.writeFile(path, encoding, data, append, promise);
}
});
}
@ReactMethod
public void lstat(String path, Callback callback) {
RNFetchBlobFS.lstat(path, callback);
}
@ReactMethod
public void stat(String path, Callback callback) {
RNFetchBlobFS.stat(path, callback);
}
@ReactMethod
public void scanFile(final ReadableArray pairs, final Callback callback) {
final ReactApplicationContext ctx = this.getReactApplicationContext();
threadPool.execute(new Runnable() {
@Override
public void run() {
int size = pairs.size();
String [] p = new String[size];
String [] m = new String[size];
for(int i=0;i<size;i++) {
ReadableMap pair = pairs.getMap(i);
if(pair.hasKey("path")) {
p[i] = pair.getString("path");
if(pair.hasKey("mime"))
m[i] = pair.getString("mime");
else
m[i] = null;
}
}
new RNFetchBlobFS(ctx).scanFile(p, m, callback);
}
});
}
@ReactMethod
/**
* @param path Stream file path
* @param encoding Stream encoding, should be one of `base64`, `ascii`, and `utf8`
* @param bufferSize Stream buffer size, default to 4096 or 4095(base64).
*/
public void readStream(final String path, final String encoding, final int bufferSize, final int tick, final String streamId) {
final ReactApplicationContext ctx = this.getReactApplicationContext();
fsThreadPool.execute(new Runnable() {
@Override
public void run() {
RNFetchBlobFS fs = new RNFetchBlobFS(ctx);
fs.readStream(path, encoding, bufferSize, tick, streamId);
}
});
}
@ReactMethod
public void cancelRequest(String taskId, Callback callback) {
try {
RNFetchBlobReq.cancelTask(taskId);
callback.invoke(null, taskId);
} catch (Exception ex) {
callback.invoke(ex.getLocalizedMessage(), null);
}
}
@ReactMethod
public void slice(String src, String dest, int start, int end, Promise promise) {
RNFetchBlobFS.slice(src, dest, start, end, "", promise);
}
@ReactMethod
public void enableProgressReport(String taskId, int interval, int count) {
RNFetchBlobProgressConfig config = new RNFetchBlobProgressConfig(true, interval, count, RNFetchBlobProgressConfig.ReportType.Download);
RNFetchBlobReq.progressReport.put(taskId, config);
}
@ReactMethod
public void df(final Callback callback) {
fsThreadPool.execute(new Runnable() {
@Override
public void run() {
RNFetchBlobFS.df(callback);
}
});
}
@ReactMethod
public void enableUploadProgressReport(String taskId, int interval, int count) {
RNFetchBlobProgressConfig config = new RNFetchBlobProgressConfig(true, interval, count, RNFetchBlobProgressConfig.ReportType.Upload);
RNFetchBlobReq.uploadProgressReport.put(taskId, config);
}
@ReactMethod
public void fetchBlob(ReadableMap options, String taskId, String method, String url, ReadableMap headers, String body, final Callback callback) {
new RNFetchBlobReq(options, taskId, method, url, headers, body, null, mClient, callback).run();
}
@ReactMethod
public void fetchBlobForm(ReadableMap options, String taskId, String method, String url, ReadableMap headers, ReadableArray body, final Callback callback) {
new RNFetchBlobReq(options, taskId, method, url, headers, null, body, mClient, callback).run();
}
@ReactMethod
public void getContentIntent(String mime, Promise promise) {
Intent i = new Intent(Intent.ACTION_GET_CONTENT);
if(mime != null)
i.setType(mime);
else
i.setType("*/*");
promiseTable.put(GET_CONTENT_INTENT, promise);
this.getReactApplicationContext().startActivityForResult(i, GET_CONTENT_INTENT, null);
}
@ReactMethod
public void addCompleteDownload (ReadableMap config, Promise promise) {
DownloadManager dm = (DownloadManager) RNFetchBlob.RCTContext.getSystemService(RNFetchBlob.RCTContext.DOWNLOAD_SERVICE);
String path = RNFetchBlobFS.normalizePath(config.getString("path"));
if(path == null) {
promise.reject("RNFetchblob.addCompleteDownload can not resolve URI:" + config.getString("path"), "RNFetchblob.addCompleteDownload can not resolve URI:" + path);
return;
}
try {
WritableMap stat = RNFetchBlobFS.statFile(path);
dm.addCompletedDownload(
config.hasKey("title") ? config.getString("title") : "",
config.hasKey("description") ? config.getString("description") : "",
true,
config.hasKey("mime") ? config.getString("mime") : null,
path,
Long.valueOf(stat.getString("size")),
config.hasKey("showNotification") && config.getBoolean("showNotification")
);
promise.resolve(null);
}
catch(Exception ex) {
promise.reject("RNFetchblob.addCompleteDownload failed", ex.getStackTrace().toString());
}
}
}
原理就是,import我们copy改动的类,不再使用.jar包里的类,option+return自动import包路径
具体查看管知汇,前期管理