最新消息:20210816 当前crifan.com域名已被污染,为防止失联,请关注(页面右下角的)公众号

【已解决】ReactJS的JS的H5中如何回调原生的iOS和Android的app

Android crifan 5431浏览 0评论

javascript 回调原生app

JavaScript调用App原生代码(iOS、Android)解决方案 – 掘金

原生App与javascript交互之JSBridge接口原理、设计与实现 – 伊吾鱼 – SegmentFault

h5与App原生交互方案 – 简书

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

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&para2=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?

javascript – React.js best practice regarding listening to window events from components – Stack Overflow

去试了试:

  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;

也是不行。

Global functions?:reactjs

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端就无法调用了,接受不到函数调用了。

找了半天,找到个解决办法,很是诡异:

参考的:

iOS中的HTML交互简说 – Cocoa开发者

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方式去传递参数,就不用这么变态的写法了。

转载请注明:在路上 » 【已解决】ReactJS的JS的H5中如何回调原生的iOS和Android的app

发表我的评论
取消评论

表情

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
90 queries in 0.237 seconds, using 22.21MB memory