javascript 回调原生app
JavaScript调用App原生代码(iOS、Android)解决方案 – 掘金
原生App与javascript交互之JSBridge接口原理、设计与实现 – 伊吾鱼 – SegmentFault
H5 调用 原生app 接口
混合app开发,h5页面调用ios原生APP的接口 – lengyue0030 – 博客园
H5 js call native app
JSBridge——Web与Native交互之iOS篇 – 简书
H5与Native交互之JSBridge技术 – 劲风 – 有赞技术团队
html5 – Native code calling JS in Android webapps – Stack Overflow
Best Of Both Worlds: Mixing HTML5 And Native Code – Smashing Magazine
reactjs 调用 原生 iOS
react js 调用 原生 iOS
react js call iOS android
javascript – React component communication with Native Mobile (iOS or Android) – Stack Overflow
h5 call iOS android
jingle1267/AndroidSchemeDemo: H5唤起原生APP
How to launch android application from my html5 mobile web application – Stack Overflow
Call Android methods from JavaScript – Stack Overflow
HTML5 invoke native app (ios and android) – Stack Overflow
android 的webView加载h5,和h5的交互(java和JavaScript的交互) – 彭思正的博客 – CSDN博客
JSBridge——Web与Native交互之iOS篇 – 简书
H5与Native交互之JSBridge技术 – 劲风 – 有赞技术团队
H5(js)中,用:
var url = ‘jsbridge://doAction?title=分享标题&desc=分享描述&link=http%3A%2F%2Fwww.baidu.com’; var iframe = document.createElement(‘iframe’); iframe.style.width = ‘1px’; iframe.style.height = ‘1px’; iframe.style.display = ‘none’; iframe.src = url; document.body.appendChild(iframe); setTimeout(function() { iframe.remove(); }, 100); |
但是,此处是Reactjs,如何写上述的代码?
reactjs add iframe
reactjs – Inserting the iframe into react component – Stack Overflow
How to use Iframely embeds with ReactJS
reactjs insert iframe
此处,已经实现了显示或不显示iframe了:
export default class Header extends Component { state = { curUrl : "/", showIframe : false } constructor(props) { super(props); this.gotoScan = this.gotoScan.bind(this); } gotoScan(){ console.log("gotoScan"); // this.showIframe(); this.toggleIframe(); } toggleIframe(){ if (this.state.showIframe) { this.hideIframe(); } else { this.showIframe(); } } showIframe(){ // console.log(`this.state.showIframe=${this.state.showIframe}`); this.setState({showIframe : true}); // console.log(`this.state.showIframe=${this.state.showIframe}`); } hideIframe(){ this.setState({showIframe : false}); } showOrHideIframe(){ if (this.state.showIframe) { return <iframe src="jsbridge://showScanDevice?para1=value1¶2=value2" width="1px" height="1px"/>; // return <iframe src="http://www.baidu.com’" width="400px" height="500px" display="block"/>; } return null; } showRightIcon(curPageType){ if (curPageType.right.icon === HEADER_ICON.NONE) { return null; } else if (curPageType.right.icon === HEADER_ICON.SCAN) { return ( <a onClick={this.gotoScan} > <div class={style.right_div} > {this.showOrHideIframe()} <i/> </div> </a> ); } else if (curPageType.right.icon == HEADER_ICON.ADD) { return ( <Link href={curPageType.right.link} > <div class={style.right_div} > <i class={style.add_i} /> </div> </Link> ); } return null; } |
效果是:
不显示iframe:
显示只有一个像素的iframe:
但是,听Android开发说,之前其所用的是另外一种:
iOS或Android端,都提供,暴露出一个控件名,比如AppHost,然后js端直接调用
window.AppHost.someFunc
android 的webView加载h5,和h5的交互(java和JavaScript的交互) – 彭思正的博客 – CSDN博客
下面问题就转化为:
reactjs中如何获得window变量
reactjs window
javascript – How do I use the window object in ReactJS? – Stack Overflow
去调试看看,log出this看看其中是否包含对应的window变量
javascript – Get viewport/window height in ReactJS – Stack Overflow
貌似直接可以使用window?
去试了试:
gotoScan(){ console.log("gotoScan"); console.log(window); // this.showIframe(); this.toggleIframe(); } |
果然是可以直接使用window:
然后用代码:
gotoScan(){ console.log("gotoScan"); console.log(window); console.log(window.AppHost); if (window.AppHost !== undefined) { console.log(window.AppHost.scanner); //window.AppHost.scanDevice(); //扫描:scanner()—-scannerCallBack(String s); window.AppHost.scanner(); } } |
移动端的效果是:
点击右上角的扫描按钮,调用原生的app中的扫码
然后去扫码:
扫码到之后,弹出提示:
然后再去考虑:
如何接受来自于app端(iOS和Android)中的回调函数
但是此处,试了几种写法:
其中一个是Header这个Component内部的scannerCallBack
另外一个是外部的function scannerCallBack
export default class Header extends Component { constructor(props) { super(props); this.gotoScan = this.gotoScan.bind(this); this.scannerCallBack = this.scannerCallBack.bind(this); } gotoScan(){ console.log("gotoScan"); console.log(window); console.log(window.AppHost); if (window.AppHost !== undefined) { console.log(window.AppHost.scanner); alert("Header before call gotoScan"); //window.AppHost.scanDevice(); //扫描:scanner()—-scannerCallBack(String s); window.AppHost.scanner(); alert("Header after call gotoScan"); } route("/bindDeviceCow/1"); } scannerCallBack(scannedString){ route("/profile"); alert("inside Header scannerCallBack"); console.log("scannerCallBack"); console.log(scannedString); alert(scannedString); } } function scannerCallBack(scannedString) { route("/function"); alert("outside Header scannerCallBack"); console.log("scannerCallBack"); console.log(scannedString); alert(scannedString); } |
都无法被调用。
然后听说是:
想办法搞清楚,是否有全局的js方法,应该可以被调用
reacjs js global function
components – ReactJs Global Helper Functions – Stack Overflow
reactjs – How to create helper file full of functions in react native? – Stack Overflow
去试试export
export function scannerCallBack(scannedString) { … alert("outside Header scannerCallBack"); console.log("scannerCallBack"); console.log(scannedString); alert(scannedString); } |
结果也没有被调用。
再后来试了试:
export function scannerCallBack(scannedString) { // route("/function"); //TODO: extarct device code from scanned string let deviceCode = scannedString; route(`/bindDeviceCow/1/${deviceCode}`); alert("outside Header scannerCallBack"); console.log("scannerCallBack"); console.log(scannedString); alert(scannedString); } window.AppHost.scannerCallBack = scannerCallBack; window.scannerCallBack = scannerCallBack; |
也是不行。
reactjs – How to declare a global variable in React? – Stack Overflow
然后参考:
componentWillMount: function () { window.MyVars = { ajax: require(‘../helpers/ajax.jsx’), utils: require(‘../helpers/utils.jsx’) }; } |
想到,是不是可以自己也去componentWillMount,加上:
window.myFunc = xxx
呢?
经过尝试,是可以的:
【总结】
1.H5中(ReactJS的)JS代码为:
export default class Header extends Component { constructor(props) { super(props); this.gotoScan = this.gotoScan.bind(this); this.scannerCallBack = this.scannerCallBack.bind(this); } componentWillMount() { window.scannerCallBack = this.scannerCallBack; } gotoScan(){ console.log("gotoScan"); console.log(window); console.log(window.AppHost); if (window.AppHost !== undefined) { console.log(window.AppHost.scanner); //Native(iOS/Android):scanner(); //window.AppHost.scanDevice(); window.AppHost.scanner(); window.AppHost.scanner(); } } //Native(iOS/Android):scannerCallBack(String s); scannerCallBack(scannedString){ //TODO: extarct device code from scanned string let deviceCode = scannedString; alert(deviceCode); route(`/bindDeviceCow/1/${deviceCode}`); } |
2.对应的原生代码:
(1)Android代码
ucowsapp_android/app/src/main/java/com/ucows/aotoso/js/JSBridge.java
package com.ucows.aotoso.js; import android.content.Context; import android.webkit.JavascriptInterface; import android.webkit.WebView; import com.ucows.aotoso.MyEventEnum; import org.greenrobot.eventbus.EventBus; import java.util.Set; import cn.jpush.android.api.JPushInterface; import cn.jpush.android.api.TagAliasCallback; /** * Created by Administrator on 2017/7/5. */ public class JSBridge { private String TAG = getClass().getSimpleName(); private Context mContext; private WebView mWebView; public JSBridge(Context mContext, WebView mWebView) { this.mContext = mContext; this.mWebView = mWebView; } @JavascriptInterface public void scanner() { EventBus.getDefault().post(MyEventEnum.openScanner); //scannerCallBack } @JavascriptInterface public void loginIn(String alias) { if (alias != null) { JPushInterface.setAlias(mContext, alias, new TagAliasCallback() { @Override public void gotResult(int i, String s, Set<String> set) { } }); } } @JavascriptInterface public void loginOut() { JPushInterface.setAlias(mContext, "", new TagAliasCallback() { @Override public void gotResult(int i, String s, Set<String> set) { } }); } } |
ucowsapp_android/app/src/main/java/com/ucows/aotoso/activity/MainActivity.java
package com.ucows.aotoso.activity; import android.Manifest; import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.view.KeyEvent; import android.webkit.WebChromeClient; import android.webkit.WebSettings; import android.webkit.WebView; import android.widget.Toast; import com.ucows.aotoso.MyEventEnum; import com.ucows.aotoso.R; import com.ucows.aotoso.base.BaseActivity; import com.ucows.aotoso.config.ServerConstants; import com.ucows.aotoso.js.JSBridge; import com.uuzuche.lib_zxing.activity.CodeUtils; import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import java.util.List; import butterknife.BindView; import pub.devrel.easypermissions.AppSettingsDialog; import pub.devrel.easypermissions.EasyPermissions; public class MainActivity extends BaseActivity implements EasyPermissions.PermissionCallbacks { @BindView(R.id.webView) WebView webView; /** * 请求CAMERA权限码 */ public static final int REQUEST_CAMERA_PERM = 101; @Override protected int getLayoutId(@Nullable Bundle savedInstanceState) { return R.layout.activity_main; } @SuppressLint("JavascriptInterface") @Override protected void init(Bundle savedInstanceState) { EventBus.getDefault().register(this); WebSettings settings = webView.getSettings(); //允许加在JS settings.setJavaScriptEnabled(true); // settings.setAppCacheEnabled(true); // settings.setAppCachePath(); //不显示缩放指针 settings.setDisplayZoomControls(false); //不允许缩放 settings.setSupportZoom(false); //自适应 settings.setUseWideViewPort(true); settings.setLoadWithOverviewMode(true); webView.setWebChromeClient(new WebChromeClient() { @Override public void onProgressChanged(WebView view, int newProgress) { super.onProgressChanged(view, newProgress); showPd(); if (newProgress == 100) { hidePd(); } } }); webView.addJavascriptInterface(new JSBridge(this, webView), "AppHost"); webView.loadUrl(ServerConstants.baseWebUrl); // webView.loadUrl("file:///android_asset/test.html"); } @Override protected void onDestroy() { EventBus.getDefault().unregister(this); super.onDestroy(); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == 100 && resultCode == RESULT_OK && data != null) { String result = ""; Bundle bundle = data.getExtras(); if (bundle != null) { if (bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_SUCCESS) { result = bundle.getString(CodeUtils.RESULT_STRING); // Toast.makeText(this, "解析结果:" + result, Toast.LENGTH_LONG).show(); } else if (bundle.getInt(CodeUtils.RESULT_TYPE) == CodeUtils.RESULT_FAILED) { // Toast.makeText(MainActivity.this, "解析二维码失败", Toast.LENGTH_LONG).show(); } } webView.loadUrl("javascript:scannerCallBack(" + "’" + result + "’" + ")"); } } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); // Forward results to EasyPermissions EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); } @Override public void onPermissionsGranted(int requestCode, List<String> perms) { if (requestCode == REQUEST_CAMERA_PERM) { startActivityForResult(new Intent(MainActivity.this, ScannerActivity.class), 100); } } @Override public void onPermissionsDenied(int requestCode, List<String> perms) { if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { new AppSettingsDialog.Builder(this, "当前App需要申请camera权限,需要打开设置页面么?") .setTitle("权限申请") .setPositiveButton("确认") .setNegativeButton("取消", null /* click listener */) .setRequestCode(REQUEST_CAMERA_PERM) .build() .show(); } } public void openScanner() { if (EasyPermissions.hasPermissions(MainActivity.this, Manifest.permission.CAMERA)) { startActivityForResult(new Intent(MainActivity.this, ScannerActivity.class), 100); } else { EasyPermissions.requestPermissions(this, "需要请求camera权限", REQUEST_CAMERA_PERM, Manifest.permission.CAMERA); } } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { // if (keyCode == KeyEvent.KEYCODE_BACK) { // // TODO: 2017/7/5 test //// openScanner(); // return true; // } if ((keyCode == KeyEvent.KEYCODE_BACK) && webView.canGoBack()) { webView.goBack(); // goBack()表示返回WebView的上一页面 return true; } return super.onKeyDown(keyCode, event); } @Subscribe public void onEventMainThread(MyEventEnum eventEnum) { if (eventEnum == MyEventEnum.openScanner) { openScanner(); } } } |
/ucowsapp_android/app/src/main/java/com/ucows/aotoso/MyEventEnum.java
package com.ucows.aotoso; public enum MyEventEnum { openScanner, } |
(2)iOS代码
ucowsapp_ios/UCows/UCows/Class/Model/AppHost.h
// JS -> OC and OC -> JS #import <Foundation/Foundation.h> #import <JavaScriptCore/JavaScriptCore.h> #import "MainWebViewController.h" @protocol UCowJavaScriptDelegate <NSObject, JSExport> #pragma mark – Method js->oc // 扫一扫 – (void)scanDeviceCode; // 退出登录 – (void)logout; // 搜索 type=/cow – (void)searchCode:(NSString *)type; // 牛只活动量 – (void)cowActivity:(NSString *)cowCode Count:(NSString *)timestamp; // 切换牛场 – (void)switchCowfarm; // 转舍 – (void)switchCowshed; @end @interface AppHost : NSObject <UCowJavaScriptDelegate> @property (nonatomic, weak) MainWebViewController *webViewController; @property (nonatomic, weak) JSContext *jsContext; #pragma mark – Method oc->js // 扫一扫结束 – (void)scanDeviceCodeCallback:(NSString *)idString; // (设备号、牛号)搜索回调 – (void)searchCallBack:(NSString *)type selectedCode:(NSString *)selectedCode; // 切换牛场->回调 – (void)switchCowfarmCallback:(NSString *)cowfarmId; // 登录成功后->回调 – (void)loginCallback:(NSString *)userInfoJson; @end |
ucowsapp_ios/UCows/UCows/Class/Model/AppHost.m
#import "AppHost.h" #import "SubLBXScanViewController.h" #import "SearchCowCodeViewController.h" #import "SearchDeviceCodeViewController.h" #import "CowActivityAmountViewController.h" #import "SwitchCowFarmViewController.h" #import "SwitchBarnViewController.h" #import "PermissionTool.h" #import "JPUSHService.h" @implementation AppHost #pragma mark – protocol Method js->oc // 扫一扫 – (void)scanDeviceCode { dispatch_async(dispatch_get_main_queue(), ^{ if ([PermissionTool cameraPemission]) { // 判断相机权限 SubLBXScanViewController *scan_vc = [[SubLBXScanViewController alloc] init]; scan_vc.resultBlock = ^(NSString *idString){ // oc掉js [self scanDeviceCodeCallback:idString]; }; [self.webViewController.navigationController pushViewController:scan_vc animated:YES]; } else { [PermissionTool getCameraPemission]; } }); } – (void)logout { dispatch_async(dispatch_get_main_queue(), ^{ [self.webViewController logoutOperation]; }); } // 搜索 type=device/cow – (void)searchCode:(NSString *)type { dispatch_async(dispatch_get_main_queue(), ^{ if ([type isEqualToString:@"cow"]) { SearchCowCodeViewController *cowCode_vc = [[SearchCowCodeViewController alloc] init]; cowCode_vc.resultBlock = ^(NSString *cowCode) { // oc掉js [self searchCallBack:type selectedCode:cowCode]; }; [self.webViewController.navigationController pushViewController:cowCode_vc animated:YES]; } if ([type isEqualToString:@"device"]) { SearchDeviceCodeViewController *deviceCode_vc = [[SearchDeviceCodeViewController alloc] init]; deviceCode_vc.resultBlock = ^(NSString *cowCode) { // oc掉js [self searchCallBack:type selectedCode:cowCode]; }; [self.webViewController.navigationController pushViewController:deviceCode_vc animated:YES]; } }); } #pragma mark – Method oc->js // 扫一扫结束 – (void)scanDeviceCodeCallback:(NSString *)idString { NSString *jsFunctStr = [NSString stringWithFormat:@"scanDeviceCodeCallback(‘%@’)", idString]; [self.jsContext evaluateScript:jsFunctStr]; } // (设备号、牛号)搜索回调 – (void)searchCallBack:(NSString *)type selectedCode:(NSString *)selectedCode { NSString *jsFunctStr = [NSString stringWithFormat:@"searchCallback(‘%@’,’%@’)", type, selectedCode]; [self.jsContext evaluateScript:jsFunctStr]; } |
3.然后即可实现:
(1)H5(Reactjs的JS)中调用原生方法
window.AppHost.scanner(); |
其中:
A,scanner()对应着原生(Android)中提供的方法
B,此处的ReactJS中,任何地方,都可以直接使用window,然后直接调用即可。
页面效果:
点击主页:
右上角的扫描按钮,调用原生的scanner
(2)原生(回调)H5(ReactJS中的JS)中的函数
确保ReactJS中componentWillMount给window中增加对应的函数:
window.scannerCallBack = this.scannerCallBack;
然后后续,原生中就可以调用对应的函数了。
页面效果:
原生扫码内容返回后,弹框提示:
然后跳转到对应的页面:
【后记】
1.后来遇到一个诡异的问题:
JS中调用iOS的原生,结果出现:
调用一个函数时,是2个参数,结果iOS端就无法调用了,接受不到函数调用了。
找了半天,找到个解决办法,很是诡异:
参考的:
JS端的:
window.AppHost.cowActivityCount("", ""); |
对应着iOS端,需要把把之前的一个函数:
cowActivityCount
拆分成2个参数:
cowActivity count
注意第二个参数名字的c是小写,而原先函数中是大写的C
TODO:把相关代码贴过来。
再后记:
后来发现:
cowActivity Count
也是可以的
-》反推内部的JS的逻辑:
iOS的内部原生的JS解析库JavaScriptCore 自己内部拆分参数时,是按照单词去拆分的。。。
然后才能接收到2个参数。
TODO:
带后续再参考上面帖子去试试用JSON互相传递参数的方式:
// 通过JSON传过来 – (void)callWithDict:(NSDictionary *)params; |
如果可行,那么以后如果超过1个参数的函数调用,都用JSON方式去传递参数,就不用这么变态的写法了。