WebViewJsBridge-iOS 和 WebViewJsBridge-Android 是我新写的 Js-Bridge 桥接库,简单易用,功能更完整,供大家参考。
Github 项目地址
说明:
iOS 端支持最低 iOS7 以上的设备,但是 demo 中的 js 因为使用 es6 语法,所以 iOS10 以下会出现语法错误,请使用 Babel 库来做兼容。
Android 端支持最低 sdk19 4.4 以上设备,测试过 Android 7.0 的设备没问题,如果出现低版本不兼容 es6 问题,同样使用 Babel 库来做下兼容。
运行 demo:
Mac 电脑自带 web 服务器,将 js 项目拖入 /Library/WebServer/Documents
目录下,使用终端敲击如下命令 sudo apachectl start
便起来一个 web
服务,浏览器输入 http://127.0.0.1
便能访问 webServer 的 Documents 目录,iOS,Android Demo 的 WebView 访问 js demo 下 index.html 文件,iOS,Android demo 分别使用 Xcode 和 Android Studio 运行。
基础用法
iOS,Android 客户端的混合开发,避免不了 js 和 native 之间的交互,一些常用的 js-bridge 库实现都是只支持一种系统。
js 调用 native 端的接口要简单,且一个函数就能调用 iOS 和 Android 两个系统,并且尽量模块化,在存在大量 native 接口的情况下便于维护这些函数。
使用方法,以 js 端调用系统的相机或相册获取一张图片为例,其它功能大同小异。
js 端的调用代码如下:
// index.html
<script type="module">
// 导入对应的 native 功能模块,其中核心模块 native-core.js 必须导入
import NCore from './native-bridge/native-core.js'
import NKit from './native-bridge/native-kit.js'
var nCore = NCore()
var nKit = NKit(nCore)
...
var self = this
nKit.selectPhoto(function (photo) {
// 图片为 base64 数据
self.imageBytes = photo.image
return '获取图片成功'
})
...
</script>
selectPhoto
方法的定义如下:
// native-kit.js
// 导入 native-core 核心模块
export default core => {
return {
selectPhoto (picker) {
// 全局记录回调函数
this.selectPhoto.picker = picker
core.loadWidget('kit', this)
// NativeKit 是 native 端注册的全局对象,camera 是对应的方法名,如此就能调用到原生客户端的方法
core.evaluateNative('NativeKit', 'camera', function (photo) {
// 调用之前的回调函数
return $nativeBridgeWidget.kit.selectPhoto.picker(photo)
})
}
}
}
native 系统相关的接口可以定义到 native-kit.js 中,或者模块分的粒度更细。
iOS 端使用 JavaScriptCore 实现交互,如何获取 JSContext 等不赘述,参考 iOS demo 即可。
先定义 JSExport
协议:
// HCKitJSExport.h
@protocol HCKitJSExport <JSExport>
// camera 即为 js 端调用的方法别名
JSExportAs(camera,
- (void)cameraWithResult:(JSValue *)result
);
@end
实现该协议:
头文件
// HCKitJSExportImpl.h
@interface HCKitJSExportImpl : NSObject <HCKitJSExport>
+ (instancetype)instance:(HCJSCoreBaseViewController *)vcContext;
@end
实现文件:
@interface HCKitJSExportImpl ()<UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (nonatomic, weak) HCJSCoreBaseViewController *vcContext;
@property (nonatomic, strong) JSValue *imageValue;
@end
@implementation HCKitJSExportImpl
+ (instancetype)instance:(HCJSCoreBaseViewController *)vcContext {
HCKitJSExportImpl *impl = [HCKitJSExportImpl new];
impl.vcContext = vcContext;
return impl;
}
- (void)cameraWithResult:(JSValue *)result {
// 保障 oc 调 js 的回调函数和 js 在同一线程
self.vcContext.jsThread = [NSThread currentThread];
// result 该 JSValue 即为 js 的回调函数
_imageValue = result;
// ui 在主线程
dispatch_async(dispatch_get_main_queue(), ^{
UIImagePickerController * imagePicker = [[UIImagePickerController alloc] init];
imagePicker.editing = YES;
imagePicker.delegate = self;
imagePicker.allowsEditing = YES;
UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"请选择打开方式" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction * camera = [UIAlertAction actionWithTitle:@"相机" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
imagePicker.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePicker.modalPresentationStyle = UIModalPresentationFullScreen;
imagePicker.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
[self.vcContext presentViewController:imagePicker animated:YES completion:nil];
}];
UIAlertAction * photo = [UIAlertAction actionWithTitle:@"相册" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
[self.vcContext presentViewController:imagePicker animated:YES completion:nil];
}];
UIAlertAction * cancel = [UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
[self.vcContext dismissViewControllerAnimated:YES completion:nil];
}];
[alert addAction:camera];
[alert addAction:photo];
[alert addAction:cancel];
[self.vcContext presentViewController:alert animated:YES completion:nil];
});
}
#pragma mark - imagePickerController delegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
[picker dismissViewControllerAnimated:YES completion:nil];
UIImage * image = [info valueForKey:UIImagePickerControllerEditedImage];
NSData *imageData = UIImagePNGRepresentation(image);
NSString *imageBase64 = [imageData base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength];
NSDictionary *dict = @{@"image": imageBase64};
if (self.imageValue) {
[self.vcContext executeJSValueThreadSafe:self.imageValue args:@[dict]];
}
}
@end
Android 端使用的是 JavaScriptInterface 实现的交互。
实现类如下
public class NativeKitJSImpl {
private static final String TAG = "NativeKitJSImpl";
private MainActivity mActivity;
public NativeKitJSImpl(MainActivity activity) {
this.mActivity = activity;
}
@JavascriptInterface
public void camera(final String picker) {
mActivity.tempCallback = picker;
new AlertDialog.Builder(mActivity)
.setTitle("提示")
.setMessage("选择相机或者相册")
.setPositiveButton("相机", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
mActivity.takePhoto(new ObtainPhoto() {
@Override
public void getPhotoBase64(String image) {
final JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("image", image);
JsInterfaceUtils.evaluateJs(mActivity.mMainWebView, picker, new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
Log.d(TAG, s);
}
}, jsonObject);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
})
.setNegativeButton("相册", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
mActivity.selectPhoto(new ObtainPhoto() {
@Override
public void getPhotoBase64(String image) {
final JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("image", image);
JsInterfaceUtils.evaluateJs(mActivity.mMainWebView, picker, new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
Log.d(TAG, s);
}
}, jsonObject);
} catch (JSONException e) {
e.printStackTrace();
}
}
});
}
}).show();
}
}
在 MainActivity 中添加此 JavaScriptInterface :
mMainWebView.addJavascriptInterface(new NativeKitJSImpl(this), "NativeKit");
如此就实现 js 与 native 端(iOS,Android)的交互。
NativeKitJSImpl
类中,可以不引用具体的 Activity,如,MainActivity 。这样耦合比较紧,可以引用接口,接口中定义要调用的方法,这样只要我的 Activity 实现了接口方法,就可以传入 jsImpl 类中了。
如这样定义接口:
// 定义基础接口
public interface NativeBaseInterface {
WebView getMainWebView();
// 提供 Activity 上下文
AppCompatActivity getActivityContext();
}
public interface NativeSelectPhotoInterface extends NativeBaseInterface {
// 拍照获取图片
public void takePhoto(ObtainPhoto obtainPhoto);
// 相册获取图片
public void selectPhoto(ObtainPhoto obtainPhoto);
}
NativeKitJSImpl
类中引入上下文环境,使用 WeakReference
避免循环引用。
如:
private NativeSelectPhotoInterface mActivity;
public BotsNativeKitJSImpl(WeakReference<NativeSelectPhotoInterface> weakReference) {
this.mActivity = weakReference.get();
}
native 调用 js
原生调用的 js 方法,需要 js 端将被调用的函数注册进来。
var test = function (param) {
self.$nativeUi.alert('test js', JSON.stringify(param), function affirm () {
console.log('点击了确认ok')
}, function cancel () {
console.log('点击了取消cancel')
})
return 'finished'
}
// 调用 core 核心模块的 registerJs 函数,test 是要被原生调用的函数
this.$nativeCore.registerJs('testJs', test)
iOS 端调用 js 函数的示例:
- (IBAction)testJs:(id)sender {
NSDictionary *dict = @{@"foo":@"hello", @"bar":@YES};
JSValue *value = [self callJsBridge:@"testJs" args:@[dict]];
NSLog(@"测试返回值:%@", [value toString]);
}
- (JSValue *)callJsBridge:(NSString *)methodName args:(NSArray *)args {
JSValue * jsBridge = self.appJSContext[@"$jsBridge"];
JSValue *jsFunction = [jsBridge valueForProperty:methodName];
return [jsFunction callWithArguments:args];
}
Android 端调用 js函数的示例:
JSONObject jsonObject = new JSONObject();
try {
jsonObject.put("bar", "hello");
jsonObject.put("foo", true);
String script = "$jsBridge.testJs";
JsInterfaceUtils.evaluateJs(mMainWebView, script, new ValueCallback<String>() {
@Override
public void onReceiveValue(String s) {
Log.d(TAG, s);
}
}, jsonObject);
} catch (JSONException exception) {
exception.printStackTrace();
}
vue 插件
实现 Vue 插件,在 Vue 框架中使用更加方便。
插件实现如下:
// native-vue.js
import NCore from './native-bridge/native-core.js'
import NUI from './native-bridge/native-ui.js'
import NStore from './native-bridge/native-store.js'
import NKit from './native-bridge/native-kit.js'
import NRequest from './native-bridge/native-request.js'
var jsBridge = {}
jsBridge.install = function (Vue, options) {
var nCore = NCore()
var nUi = NUI(nCore)
var nStore = NStore(nCore)
var nKit = NKit(nCore)
var nRequest = NRequest(nCore)
Vue.prototype.$nativeCore = nCore
Vue.prototype.$nativeUi = nUi
Vue.prototype.$nativeStore = nStore
Vue.prototype.$nativeKit = nKit
Vue.prototype.$nativeRequest = nRequest
}
export default jsBridge;
使用插件:
// 使用前引入插件
import nativeVue from './native-vue.js'
Vue.use(nativeVue);
var self = this
var params = {'id':2, 'pageNum':3, 'pageSize':10, 'keyword':'xx'}
this.$nativeRequest.get('https://api.github.com/', params, function success(response) {
console.log(response)
self.resultMsg = response
}, function fail(error) {
console.log(error)
})
最后
该库我自己已经投入使用,希望大家提出宝贵意见,帮助完善程序。