旧文章迁移自:简书 create by 2019.02.18 11:27
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 端的调用代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <script type="module"> 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) { self.imageBytes = photo.image return '获取图片成功' }) ... </script>
|
selectPhoto
方法的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
export default core => { return { selectPhoto (picker) { this.selectPhoto.picker = picker core.loadWidget('kit', this) core.evaluateNative('NativeKit', 'camera', function (photo) { return $nativeBridgeWidget.kit.selectPhoto.picker(photo) }) } } }
|
native 系统相关的接口可以定义到 native-kit.js 中,或者模块分的粒度更细。
iOS 端使用 JavaScriptCore 实现交互,如何获取 JSContext 等不赘述,参考 iOS demo 即可。
先定义 JSExport
协议:
1 2 3 4 5 6 7
| // HCKitJSExport.h @protocol HCKitJSExport <JSExport> // camera 即为 js 端调用的方法别名 JSExportAs(camera, - (void)cameraWithResult:(JSValue *)result ); @end
|
实现该协议:
头文件
1 2 3 4
| // HCKitJSExportImpl.h @interface HCKitJSExportImpl : NSObject <HCKitJSExport> + (instancetype)instance:(HCJSCoreBaseViewController *)vcContext; @end
|
实现文件:
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
| @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 实现的交互。
实现类如下
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
| 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 :
1
| mMainWebView.addJavascriptInterface(new NativeKitJSImpl(this), "NativeKit");
|
如此就实现 js 与 native 端(iOS,Android)的交互。
NativeKitJSImpl
类中,可以不引用具体的 Activity,如,MainActivity 。这样耦合比较紧,可以引用接口,接口中定义要调用的方法,这样只要我的 Activity 实现了接口方法,就可以传入 jsImpl 类中了。
如这样定义接口:
1 2 3 4 5 6 7 8 9 10 11 12 13
| public interface NativeBaseInterface { WebView getMainWebView(); AppCompatActivity getActivityContext(); }
public interface NativeSelectPhotoInterface extends NativeBaseInterface { public void takePhoto(ObtainPhoto obtainPhoto); public void selectPhoto(ObtainPhoto obtainPhoto); }
|
NativeKitJSImpl
类中引入上下文环境,使用 WeakReference
避免循环引用。
如:
1 2 3 4
| private NativeSelectPhotoInterface mActivity; public BotsNativeKitJSImpl(WeakReference<NativeSelectPhotoInterface> weakReference) { this.mActivity = weakReference.get(); }
|
native 调用 js
原生调用的 js 方法,需要 js 端将被调用的函数注册进来。
1 2 3 4 5 6 7 8 9 10
| var test = function (param) { self.$nativeUi.alert('test js', JSON.stringify(param), function affirm () { console.log('点击了确认ok') }, function cancel () { console.log('点击了取消cancel') }) return 'finished' }
this.$nativeCore.registerJs('testJs', test)
|
iOS 端调用 js 函数的示例:
1 2 3 4 5 6 7 8 9 10
| - (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函数的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| 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 框架中使用更加方便。
插件实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| 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;
|
使用插件:
1 2 3 4 5 6 7 8 9 10 11 12
| 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) })
|
最后
该库我自己已经投入使用,希望大家提出宝贵意见,帮助完善程序。