RN开发之调用WebView爬坑记

RN 开发之调用 WebView 爬坑记

解决 React Native 之 Android 手机中 WebView 不能调用相机相册的问题

记录最近在 RN 开发中踩的一个坑以及分享我的爬坑记:Android 手机不能在 WebView 中调用相机相册;我刚开始很困惑,大家都一致认为这是链接的第三方写的 H5 中调用相机相册的方法有问题所导致的,后来我通过查找原因,发现这是 Android 原生 WebView 中没有实现调用相机相册的功能;soga,知道原因后开始在网上查找解决方法,大概找到两三个类似的依赖包,于是开始尝试集成到现在的项目中去,结果全部 jj:不是和其他依赖包冲突就是 gradle build 失败;唉,各种心塞心累,于是决定自己尝试桥接原生 WebView,桥接的过程也是各种蜿蜒曲折啊;下面开始分享我桥接的过程,希望可以帮助有需要的人少踩点坑。

1. 为什么 RN 的 android 端 webview 不支持上传图片和调用相机?

android 原生的 webview,本身就需要配置一个方法来配合上传图片,所以 RN 封装的 webView 没有配置这个方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// For Android 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = uploadMsg;
showPopSelectPic();
}

public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = filePathCallback;
showPopSelectPic();
return true;
}
2. 如何给 RN 的 webview 配置上这个方法?

在 js 代码中暂时没办法处理,那只好改原生的方法,原生的 webview 封装在 react native 包里,没办法改,只好重新封装一个 webview.

3. 怎么重新封装一个 RN 组件?

可参考封装 RN 组件的教程

  1. 首先把/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/views/webview中的 ReactWebViewManager 的复制到自己的 src 的 java 包目录里
  2. 然后根据自己的需求进行更改,比如现在的需求事添加选择图片的配置

(1). 新建一个 ActivityResultInterface 接口回调

1
2
3
interface ActivityResultInterface {
void callback(int requestCode, int resultCode, Intent data);
}

(2). PickerActivityEventListener onActivityResult 回调和自定义回调链接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class PickerActivityEventListener extends BaseActivityEventListener {

private ActivityResultInterface mCallback;

public PickerActivityEventListener(ReactApplicationContext reactContext, ActivityResultInterface callback) {
reactContext.addActivityEventListener(this);
mCallback = callback;
}

// < RN 0.33.0
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mCallback.callback(requestCode, resultCode, data);
}

// >= RN 0.33.0
public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
mCallback.callback(requestCode, resultCode, data);
}
}

(3). ReactWebViewManager 自定义添加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
public class ReactWebViewManager extends SimpleViewManager<WebView> implements ActivityResultInterface {
//一些初始化变量
private ReactApplicationContext reactApplicationContext;
private ValueCallback mUploadMessage;
private Uri imageUri;
private static final int TAKE_PHOTO = 10001;
private static final int CHOOSE_PHOTO = 10002;
protected static final String REACT_CLASS = "RCTWebView2";
......
//修改构造方法
public ReactWebViewManager(ReactApplicationContext reactApplicationContext) {
this.reactApplicationContext = reactApplicationContext;
new PickerActivityEventListener(reactApplicationContext, this);
mWebViewConfig = new WebViewConfig() {
public void configWebView(WebView webView) {
}
};
}
......
protected WebView createViewInstance(final ThemedReactContext reactContext) {
ReactWebView webView = createReactWebViewInstance(reactContext);
webView.setWebChromeClient(new WebChromeClient() {
......
// For Android 4.1
public void openFileChooser(ValueCallback<Uri> uploadMsg, String acceptType, String capture) {
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = uploadMsg;
showPopSelectPic(reactContext);
}

public boolean onShowFileChooser(WebView webView, ValueCallback<Uri[]> filePathCallback, FileChooserParams fileChooserParams) {
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
}
mUploadMessage = filePathCallback;
showPopSelectPic(reactContext);
return true;
}

});
......
}

private void showPopSelectPic(final ThemedReactContext Context) {
String[] items = new String[]{"相机", "相册"};
AlertDialog.Builder builder = new AlertDialog.Builder(Context);
builder.setTitle("提示")
.setSingleChoiceItems(items, 0, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
if (which == 0) {
//openCamera
File outputImage = new File(Context.getExternalCacheDir(), "output_image.jpg");
try {
if (outputImage.exists()) {
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
if (Build.VERSION.SDK_INT < 24) {
imageUri = Uri.fromFile(outputImage);
} else {
imageUri = FileProvider.getUriForFile(Context, Context.getPackageName() + ".provider", outputImage);
}
// 启动相机程序
if (ContextCompat.checkSelfPermission(Context, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
//ActivityCompat.requestPermissions(Context, new String[]{Manifest.permission.CAMERA}, 2);
} else {
openCamera();
}
} else if (which == 1) {
//openAlbum
if (ContextCompat.checkSelfPermission(Context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
//ActivityCompat.requestPermissions(Context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
} else {
openAlbum();
}
}
}
})
.setOnCancelListener(new DialogInterface.OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
if (mUploadMessage != null) {
mUploadMessage.onReceiveValue(null);
mUploadMessage = null;
}
}
});
builder.create().show();
}

void openCamera() {
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
Activity currentActivity = reactApplicationContext.getCurrentActivity();
currentActivity.startActivityForResult(intent, TAKE_PHOTO);
}

void openAlbum() {
Intent intent = new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
Activity currentActivity = reactApplicationContext.getCurrentActivity();
currentActivity.startActivityForResult(intent, CHOOSE_PHOTO); // 打开相册
}

@Override
public void callback(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case TAKE_PHOTO:
if (resultCode == RESULT_OK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mUploadMessage.onReceiveValue(new Uri[]{imageUri});
} else {
mUploadMessage.onReceiveValue(imageUri);
}
mUploadMessage = null;
} else {
mUploadMessage.onReceiveValue(null);
mUploadMessage = null;
return;
}
break;
case CHOOSE_PHOTO:
if (resultCode == RESULT_OK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mUploadMessage.onReceiveValue(new Uri[]{data.getData()});
} else {
mUploadMessage.onReceiveValue(data.getData());
}
mUploadMessage = null;
} else {
mUploadMessage.onReceiveValue(null);
mUploadMessage = null;
return;
}
break;
default:
break;
}
}
}

(4). 注意 需要注册 FileProvider 和设置 xml path
(注意此处可能会和其他上传图片的依赖包中的清单文件冲突,原因是配置的 authorities 冲突,只要修改一致即可)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//清单文件中注册
<application
...>

<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.company.app.provider" //最好是包名+'.provider', 如果你的工程里集成有图片上传的依赖包,那么编译可能会有冲突,解决:你将此处修改成与冲突的依赖包一致即可,注意桥接方法里的图片路径(FileProvider)也要同步修改
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" //注意和冲突的依赖包进行比对修改
/>
</provider>
</application

(5). res 资源文件中新建 xml 文件夹新建文件 provider_paths.xml

1
2
3
4
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="app_images" path="." />
</paths>
4. 新建 WebViewReactPackage.java 文件,将写好的 ReactWebViewManager 写入到 WebViewReactPackage 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class WebViewReactPackage implements ReactPackage {

public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}

@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
return Arrays.<ViewManager>asList(
new ReactWebViewManager(reactApplicationContext)
);
}

@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactApplicationContext) {
return Collections.emptyList();
}
}
5. 将 WebViewReactPackage 写入 MainApplication 中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class MainApplication extends Application implements ReactApplication {
......
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
......
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new WebViewReactPackage()
);
}
......
};
......
}
6. 在项目新建 BridgeWebView.js 文件,把/node_modules/react-native/Libraries/Components/WebView中的 webview.android.js,复制到自己的 js 文件夹中,做一定的修改
1
2
3
4
5
6
7
// 修改部分
var RCT_WEBVIEW_REF = 'webview';
......
class WebViewBridge extends React.Component {
......
var RCTWebView = requireNativeComponent('RCTWebViewOpenImg', WebViewBridge, WebViewBridge.extraNativeComponentConfig); //RCTWebViewOpenImg与ReactWebViewManager的REACT_CLASS对应
module.exports = WebViewBridge;
7. 最后你在需要用的 js 文件,引入 BridgeWebView.js 文件,通过 Platform 判断 Android 平台调用桥接的 WebViewBridge,iOS 平台调用 RN 封装的 WebView.
0%