2017-08-12 10:57:12 u013148287 阅读数 6203
  • FFmpeg音视频开发实战5 iOS/Android/windows/Linux

    本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL ES渲染视频。OpenAL播放音频。到pcm实时转AAC,到H.264+AAC合成mp4, 整个流程,涵盖iOS,Android ,Mac 嵌入式Linux音视频相关绝大多数实用场景。以及Posix编程接口,C C++ Qt,FFmpeg跨平台开发,iOS,Android,Mac,linux,桌面软件都不再是障碍。让学员能够,融汇贯通掌握音视频领域相关知识,从事音视频相关职业,年薪轻松三四十万不是梦。 付费学员加入QQ群,可获得1~3年的专业解答,周六晚8:00 ~10:00 QQ群内部直播答疑, 以及就业指导,项目练习等服务.

    164321 人正在学习 去看看 陈超

在此记录一下 HBuilder IOS离线打包过程一些操作步骤以及遇到的我认为比较关键注意点:

说明:以下是基于官方提供HBuilder-Hello工程基础上改的

1:下载下载HBuilder离线打包iOS版SDK

http://ask.dcloud.net.cn/article/103

           请详细了解IOS平台5+SDK技术白皮书中的内容。

     

 

2:复制图中两个文件夹到一个新的目录下,作为即将改造的工程

    

 

3:打开刚才复制HBuilder-Hello.xcodeproj工程

 

 

4:更改工程Bundle Identifier

      注意:Deplement Target 最好改成6.0 否则 打包Product--->Archives时候可能会报错( 编译错误:library not found for -lcrt1.3.1.o

 

4:更改工程名称
     修改项目名称请参考 :http://www.cnblogs.com/tbfirstone/p/3601541.html

 

5:将已经开发好的应用资源替换Hellow5+中的资源

     

     修改 id ,图中红色框中三个地方修改要保持一样,否则程序无法运行 。

    注意:文件替换后,manifest.json文件中的注释要删除!!!技术白皮书也有特别说明。否则会影响app正常运行

 

 

 

6  修改应用图标和启动图片

   将制作好的对应像素大小图片替换icon和splash两个文件夹中图片

   

 

7:根据项目具体需要,移除不必要的引入的库和相关文件

         项目中各模块需要的库文件和相关文件请参考 Feature-IOS.xls,

         

        由于官方提供的HBuilder-Hello提供的项目引入的插件较多,导致应用打包会比较大,有几十M,如果项目应用中没有使用到的模块,可以移除相关库文件。

        

 


8:下面就是启动和打包过程了,XCODE启动和打包过程请网上自己搜索教程

参考:

    http://ask.dcloud.net.cn/article/103

    http://ask.dcloud.net.cn/article/931 隐私权限配置

   http://www.bcty365.com/content-146-5221-1.html

   http://www.jianshu.com/p/dbf39c212c0e

   修改整个项目名称 http://www.cnblogs.com/tbfirstone/p/3601541.html

2019-05-23 11:06:13 qq934235475 阅读数 131
  • FFmpeg音视频开发实战5 iOS/Android/windows/Linux

    本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL ES渲染视频。OpenAL播放音频。到pcm实时转AAC,到H.264+AAC合成mp4, 整个流程,涵盖iOS,Android ,Mac 嵌入式Linux音视频相关绝大多数实用场景。以及Posix编程接口,C C++ Qt,FFmpeg跨平台开发,iOS,Android,Mac,linux,桌面软件都不再是障碍。让学员能够,融汇贯通掌握音视频领域相关知识,从事音视频相关职业,年薪轻松三四十万不是梦。 付费学员加入QQ群,可获得1~3年的专业解答,周六晚8:00 ~10:00 QQ群内部直播答疑, 以及就业指导,项目练习等服务.

    164321 人正在学习 去看看 陈超

目前想调用第三方(某图场景)的活体识别,但是官网明明写着,有 H5 的方式,但是提供的 SDK 只有原生的,所以需要调原生,并且是在原生中调用三方的检测页面,然后回调结果给 RN


总体的思路:

如下图所示,我们的核心点在于对 RCT 的单例设计以及回调的使用


总体的步骤:

1,RN 页面引入 NativeModules

2,编写原生桥接页面(这里桥接 RN 页面与三方活体识别页面)

3,原生三方活体识别页面返回值

4,RN 页面展示结果

那么,让我们准备好 Xcodecoffee 加上耳机开搞吧。


一,RN 页面引入 NativeModules

这里很简单

import { NativeModules } from 'react-native';

var FaceRecognition = NativeModules.FaceRecognition;
// 参数为 projectId 以及 hostUrl,Promise 的调用方法
FaceRecognition.startFaceRecognition(projectId, hostUrl)
    .then((msg) => {
          msg = JSON.stringify(msg);
          if (msg.includes('checkPass')){
             this.props.navigator.push({
             id: 'ContractList',
             comp: ContractList,
              param: {
                      ...this.props.param,
              }});
           }else{
              //alert('对比失败,请重试!');
           }
          }).catch((error) => {
              //alert('活体检测对比失败,请重试!');
     });

 这里,为什么叫 FaceRecognition 以及为什么是调用 startFaceReconition ,请看第二步


 二,编写原生桥接页面(桥接 RN 页面与三方活体识别页面)

这里建议用 Xcode 打开,以便有相对应的代码提示及着色。

分别创建 .h.m 文件,命名如下:

FcaReconition.h:

//
//  FaceRecognition.h
// 
//
//  Created by supervons on 2019/2/27.
//  Copyright © 2019年 Facebook. All rights reserved.
//

#import "RCTViewManager.h"
#import <UIKit/UIKit.h>
#import "RCTBridgeModule.h"

// 继承 RN 方法
@interface FaceRecognition : RCTViewManager
// promise 成功回调
@property (nonatomic, retain) RCTPromiseResolveBlock resolve;
// promise 失败回调
@property (nonatomic, retain) RCTPromiseRejectBlock reject;
// 接受的两个参数
@property (nonatomic, retain) NSString *projectId;
@property (nonatomic, retain) NSString *hostUrl;

// 用于在第三方页面调用该实例回调
+ (id)sharedInstance;

+ (id)allocWithZone:(NSZone *)zone;

+ (id)copyWithZone:(struct _NSZone *)zone;

+ (id)mutableCopyWithZone:(struct _NSZone *)zone;

@end

FcaReconition.m:

//
//  FaceRecognition.m
//  rzzl
//
//  Created by supervons on 2019/2/27.
//  Copyright © 2019年 Facebook. All rights reserved.
//

#import "FaceRecognition.h"
#import "OliveappLivenessDetectionViewController.h"
#import <Foundation/Foundation.h>
#import "AppDelegate.h"
#import "RCTBridge.h"

@implementation FaceRecognition{
  
  // 默认的识别成功的回调
  void (^_successHandler)(id);
  // 默认的识别失败的回调
  void (^_failHandler)(NSError *);
}
static FaceRecognition * _instance = nil;
static bool isFirstAccess = YES;

RCT_EXPORT_MODULE()
#pragma 生命周期相关方法

RCT_EXPORT_METHOD(startFaceRecognition:(NSString *) projectId hostUrl:(NSString *) hostUrl resolver:(RCTPromiseResolveBlock) resolve rejecter:(RCTPromiseRejectBlock) reject)
{
  //获取projectId与hostUrl
  self.projectId = projectId;
  self.hostUrl = hostUrl;
  //以下样例代码展示了如何初始化活体检测
  UIStoryboard * board = [UIStoryboard storyboardWithName:@"LivenessDetection" bundle:nil];
  OliveappLivenessDetectionViewController * livenessViewController;
  livenessViewController = (OliveappLivenessDetectionViewController*) [board instantiateViewControllerWithIdentifier: @"LivenessDetectionStoryboard"];

  //初始化参数
  //FaceRecognition* inst = [FaceRecognition sharedInstance];
  self.resolve = resolve;
  self.reject = reject;
  __weak typeof(self) weakSelf = self;
  NSError *error;
  BOOL isSuccess;
  isSuccess = [livenessViewController setConfigLivenessDetection: weakSelf withError: &error];
  
  //弹出活体检测界面,可用show,push
  UIViewController* curVc = [FaceRecognition getCurrentViewController];
  dispatch_async(dispatch_get_main_queue(), ^{
  [curVc presentViewController:livenessViewController
                     animated:YES
                   completion:^{
                     [[UIApplication sharedApplication] endIgnoringInteractionEvents];
                   }];
  });
}

@end

 这里的核心方法是 RCT_EXPORT_METHOD,可以看到第一个参数 startFaceRecognition 即为 RN 中调用的方法名,在这里调用 resolvereject,即进入了 Promise 的回调,但是我们是用于第三方页面获取参数后再进行回调,所以要把当前 View 单例化,把其传递给第三方页面进行处理回调,就 ok 啦。(例子的话,稍后闲下来再补上吧),这里可以看到,我把其传递给了第三方页面 OliveappLivenessDetectionViewController(这是友方厂商的sdk,故只贴部分代码,请看步骤三)


三,三方页面调用

在这里调用单例的对象的成功方法,回调给 RN 页面即可。 


四,RN 页面展示结果

和第一步一样的,只是在成功后,跳转到下一页面。


 总结一下就是,RN编写页面 -> 原生编写 ->(若调原生第三方页面,则需要把原生的对象单例化传递)-> 处理后调用 resolvereject

2017-10-25 22:39:55 c339069773 阅读数 1963
  • FFmpeg音视频开发实战5 iOS/Android/windows/Linux

    本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL ES渲染视频。OpenAL播放音频。到pcm实时转AAC,到H.264+AAC合成mp4, 整个流程,涵盖iOS,Android ,Mac 嵌入式Linux音视频相关绝大多数实用场景。以及Posix编程接口,C C++ Qt,FFmpeg跨平台开发,iOS,Android,Mac,linux,桌面软件都不再是障碍。让学员能够,融汇贯通掌握音视频领域相关知识,从事音视频相关职业,年薪轻松三四十万不是梦。 付费学员加入QQ群,可获得1~3年的专业解答,周六晚8:00 ~10:00 QQ群内部直播答疑, 以及就业指导,项目练习等服务.

    164321 人正在学习 去看看 陈超

近期接 IOS SDK 简单记录下踩的坑:


----

  • 1.库文件丢失问题

项目中是使用工具添加的库文件,暂时不知道为什么没有加上(加在项目里面了,但是路劲不对),要重新手动添加下,在X-Code中,具体渠道文件夹或者Library都行 右键=》Add Files to "you project"

  • 2.类似下面的错误


网上有很多同样问题:

https://stackoverflow.com/questions/33865332/errors-when-building-with-xcode-7-unity-5-2-3f1-ios-9-1-base-sdk


大体都是说 在X-Code 中Builde出错,但真机跑起来没问题, 一些修改方式说在Unity导出时 Editor--ProjectSetting---Player---Other Setting--SDK Version--选择 Simulator SDK (在导出 Builder Setting中同样,不同平台设置不同,以上需要在 IOS平台下才有), 查了下 这个选项的意思就是 打出来的包是模拟器上运行的,并不是在真机上运行,也有的说这个是调试版本用的 以便在X-Code中快速出包,当版本发布时还是一定要选Device SDK 的。这个要与X-Code中的设置对应上。具体哪里对应上呢?就这下面这个位置:

即 X-Code面板左上角。点开可以发现 里面有 Device (即对应设置 Device SDK)和 IOS Simulators (对应 Simulator SDK), 一定要选择一致的。

我这里unity导出选的Device SDK , X-Code 选的 IOS Simulators ,所以会有问题, 上文引用网页中 修改 unity 设置好使了也就是与X-Code对应上了。


在Unity 导出 X-Code 的包时候一定要注意下 导出设置和X-Code 里面的设置对应上。

---


  • 3.工程卡死 

因为我们游戏是强制横屏的, 渠道要求:

//防止ios9以下系统调用客服系统闪退
//增加此接口的配置

-(UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window{
    return UIInterfaceOrientationMaskAll;
}
写在UnityAppController中,因为原有的这个方法也有返回值,我写成这两个值取或了, 导致程序崩溃卡死(不是闪退)。改成只返回渠道这个就好了。、

关于这个部分 有两个脚本要注意下 一个是渠道Demo中给出的 AppDelegate.m 一个是Unity导出X-Code 工程生成的 UnityAppController.m ,渠道要求有些代码要写到 AppDelegate 中对应的方法中。 其实这两个文件方法基本都相同,看起来都是一些应用的声明周期方法,我开始用AppDelegate 继承UnityAppController然后使用它Demo中的代码,但这样像应的父类(UnityAppController)方法都被覆盖了也会有问题。要在子类中调用下 super.XXX base.XX 诸如此类。(我用的是下一种方法)或者将Demo要求的代码插写到

UnityAppController中。

  • 4→ →脚本的编码格式

工程中infolist也是脚本自动生成的,在windows VS下面显示汉字,到 mac 中 MOno中文乱码。 在windows下用记事本打开源代码文件,另存为 下面编码方式为 ANSI 。改为UTF-8就好了。


  • 5 ← ← OC的单例与self.

第一次接触OC ,很坎坷啊,。渠道方有这样一句代码:

 [EyougameSDK shareSDK].delegate = self;
就是他们SDK的支付回调,通过委托回来调对应的回调方法。 我这里这个self就是访问不到,原因在于 这句话写在了这个位置:

#if defined (__cplusplus)
extern "C"
{
#endif
    ............................
    
    void __SDKLogout()
    {
		NSLog(@"__SDKLogout.");
        [SDKUnity3DBridge LogoutCallBackToUnity];
    }
    
    
    void __SDKPay(void * orderId,void * prodectName,void * productId,NSString *money,void * payUri)
    {
		NSLog(@"__SDKPay.");
        NSLog(@"__SDKPay orderId is:%@",orderId);
        NSLog(@"__SDKPay prodectName is:%@",prodectName);
        NSLog(@"__SDKPay productId is:%@",productId);
        NSLog(@"__SDKPay money is:%@",money);
        NSLog(@"__SDKPay payUri is:%@",payUri);
        
        
        if ([[EyougameSDK shareSDK] goSKProductsRequest]) {
            [[EyougameSDK shareSDK] skProductsRequestFor:productId orderNumber:orderId cText:nil money:money tradeName:prodectName mode:nil ctype:nil endPay:^{
                 NSLog(@"支付处理完成回调");
            }];
            [EyougameSDK shareSDK].delegate = [EYOUSDKMain sharedManager];//这句给委托赋值 ,回调的方法要写到 后者中
        }else{
            NSLog(@"正在购买");
        }
    }
    
    -(void)payStatusCallBack:(payStatus)code  //这个就是回调的方法
    {
        if (code == payStatus_chenggong) {
            NSLog(@"充值成功");
            [SDKUnity3DBridge EYOUPayCallBackToUnity:(nil)];
        }else if (code == payStatus_payIng)
        {
            NSLog(@"正在充值");
        }else
        {
            NSLog(@"充值失败");
        }
    }
    
    bool __SDKGetAreaIsTWD(){
        
        [[EyougameSDK shareSDK] getLocalCurrency:^(NSInteger code) { //这里是拉姆达表达式的写法,area要定义到这段条件判断外,也就是OC代码中,
                                                                     //因为形参的格式是NSINterer 属于OC的格式 此处只能定义 C++的 也就是 Int 
            area = code;                                             //他们两个不能相互赋值
            NSLog(@"area=%ld",(long)area);
            if(code==1){
                NSLog(@"充值面板显示台币");
            }
            else{
                NSLog(@"充值面板显示美金");
            }
        }];
        
	return  area==1;
    }

#if defined (__cplusplus)
}
#endif


单例这样写: (问人家渠道才知道。→ →。蓝介。调用:[EYOUSDKMain shareManager] OC中单例貌似都用shareManager,不像C# instance)

@implementation EYOUSDKMain

+ (EYOUSDKMain *)sharedManager
{
    static EYOUSDKMain *sharedAccountManagerInstance = nil;
    static dispatch_once_t predicate;
    dispatch_once(&predicate, ^{
        sharedAccountManagerInstance = [[self alloc] init];
    });
    return sharedAccountManagerInstance;
}
@end

代码头尾出的定义就是表示这段要写C++的代码(在OC中),所以self不是关键字(this也不好使),这个地方只能通过单例来访问。在这段代码中设计给OC的属性赋值,值来自C++的,写成下面这样:

void __SDKSubmitExtendData(void * roleId,int serverid)
{
		NSLog(@"__SDKSubmitExtendData.");

        //角色进入游戏后调用 初始化和设置参数
        [EyougameSDK shareSDK].gameRid = roleId;
        [EyougameSDK shareSDK].gameSid = serverid;

}
将String 与int 直接赋值给 OC 的NSString 和NSString(gameSid在OC中定义类型是NSString),结果报错,死循环:

warning: could not load any Objective-C class information. This will significantly reduce the qu

下面这样也不可以:虽然转换了格式 但是直接赋值了

		NSString *roleIdStr = [NSString stringWithCString:roleId encoding:NSUTF8StringEncoding];
		NSString *serverIdStr = [NSString stringWithFormat:@"%d",serverid];

最后下面这样才通过:

void __SDKSubmitExtendData(void * roleId,int serverid)
{
		NSLog(@"__SDKSubmitExtendData.");

        //角色进入游戏后调用 初始化和设置参数
        NSString *roleIdStr = [NSString stringWithCString:roleId encoding:NSUTF8StringEncoding];
        NSString *serverIdStr = [NSString stringWithFormat:@"%d",serverid];
        [[EyougameSDK shareSDK]setGameRid:roleIdStr];
        [[EyougameSDK shareSDK]setGameSid:serverIdStr];

}

其实根本问题就是格式转换的问题,与直接赋值还是通过属性SetValue的问题。 改完编译看一下 特么的 下面打印的log未转换呢,结果还得重新编译一遍。。。

      NSLog(@"__SDKroleId.=%@",roleId);
      NSLog(@"__SDKserverid.=%d",serverid);//也是不行的 - -

//要用转换过的:
      NSLog(@"__SDKroleId.=%@",roleIdStr);
      NSLog(@"__SDKserverid.=%@",serverIdStr);



参考错误:

http://www.cnblogs.com/hanjun/archive/2012/11/06/2757671.html
http://www.cnblogs.com/shidaying/p/3651443.html


2016-04-05 16:35:28 u012960049 阅读数 313
  • FFmpeg音视频开发实战5 iOS/Android/windows/Linux

    本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL ES渲染视频。OpenAL播放音频。到pcm实时转AAC,到H.264+AAC合成mp4, 整个流程,涵盖iOS,Android ,Mac 嵌入式Linux音视频相关绝大多数实用场景。以及Posix编程接口,C C++ Qt,FFmpeg跨平台开发,iOS,Android,Mac,linux,桌面软件都不再是障碍。让学员能够,融汇贯通掌握音视频领域相关知识,从事音视频相关职业,年薪轻松三四十万不是梦。 付费学员加入QQ群,可获得1~3年的专业解答,周六晚8:00 ~10:00 QQ群内部直播答疑, 以及就业指导,项目练习等服务.

    164321 人正在学习 去看看 陈超

由于系统原因,iOS不允许使用第三方定位,因此地图SDK中的定位方法,本质上是对原生定位的二次封装。通过封装,开发者可更便捷的使用。此外,地图SDK中还提供了相应的定位图层(支持定位三态效果),帮助开发者显示当前位置信息。

注:自iOS8起,系统定位功能进行了升级,SDK为了实现最新的适配,自v2.5.0起也做了相应的修改,开发者在使用定位功能之前,需要在info.plist里添加(以下二选一,两个都添加默认使用NSLocationWhenInUseUsageDescription):

NSLocationWhenInUseUsageDescription ,允许在前台使用时获取GPS的描述

NSLocationAlwaysUsageDescription ,允许永久使用GPS的描述

获取位置信息

定位功能可以和地图功能分离使用,单独的定位功能使用方式如下:

-(void)viewDidLoad  
{    
    //初始化BMKLocationService  
    _locService = [[BMKLocationService alloc]init];  
    _locService.delegate = self;  
    //启动LocationService  
    [_locService startUserLocationService];  
}  
//实现相关delegate 处理位置信息更新  
//处理方向变更信息  
- (void)didUpdateUserHeading:(BMKUserLocation *)userLocation  
{  
    //NSLog(@"heading is %@",userLocation.heading);  
}  
//处理位置坐标更新  
- (void)didUpdateBMKUserLocation:(BMKUserLocation *)userLocation  
{  
    //NSLog(@"didUpdateUserLocation lat %f,long %f",userLocation.location.coordinate.latitude,userLocation.location.coordinate.longitude);  
}

展示定位信息

展示定位信息的功能位于“地图和覆盖物”这个功能模块,开发者在使用时要注意选择。核心代码如下:(完整信息请参考Demo)

//普通态  
//以下_mapView为BMKMapView对象  
_mapView.showsUserLocation = YES;//显示定位图层  
[_mapView updateLocationData:userLocation];
2016-04-28 10:27:02 li_shuang_long 阅读数 4724
  • FFmpeg音视频开发实战5 iOS/Android/windows/Linux

    本课程适合从事音视频,网络通讯开发的程序员。实战案例可用于 音视频处理,无人机,安防,直播等所有音视频领域。课程从Linux音视频采集,到TCP/IP UDP Socket服务器,客户端编程, 如何去定义网络通讯私有协议,x264,FFmpeg编解码,OpenGL ES渲染视频。OpenAL播放音频。到pcm实时转AAC,到H.264+AAC合成mp4, 整个流程,涵盖iOS,Android ,Mac 嵌入式Linux音视频相关绝大多数实用场景。以及Posix编程接口,C C++ Qt,FFmpeg跨平台开发,iOS,Android,Mac,linux,桌面软件都不再是障碍。让学员能够,融汇贯通掌握音视频领域相关知识,从事音视频相关职业,年薪轻松三四十万不是梦。 付费学员加入QQ群,可获得1~3年的专业解答,周六晚8:00 ~10:00 QQ群内部直播答疑, 以及就业指导,项目练习等服务.

    164321 人正在学习 去看看 陈超

关于原生和hybid之争,这里不做探讨.主要讲讲JS和OC交互


开讲前附上一个牛逼的第三方
JavascriptBridge

OC执行JS代码

  • 1.stringByEvaluatingJavaScriptFromString
    这个方法是UIWebView里面的方法,也是最为简单的和JS交互的方式
- (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

用法比较简单,一般在代理方法- (void)webViewDidFinishLoad:(UIWebView*)webView中使用

// 获取当前页面的title
NSString *title = [webview stringByEvaluatingJavaScriptFromString:@"document.title"];

// 获取当前页面的url
NSString *url = [webview stringByEvaluatingJavaScriptFromString:@"document.location.href"];

OC执行JS&JS执行OC

  • 1.JavaScriptCore
    这个是iOS7以后引进的,使用起来可简单,也可以比较复杂.
    熟悉一下里面常见得几个对象及协议
JSContext:给JavaScript提供运行的上下文环境,通过-evaluateScript:方法就可以执行一JS代码
JSValue:JavaScript和Objective-C数据和方法的桥梁,封装了JS与ObjC中的对应的类型,以及调用JS的API等
JSManagedValue:管理数据和方法的类
JSVirtualMachine:处理线程相关,使用较少
JSExport:这是一个协议,如果采用协议的方法交互,自己定义的协议必须遵守此协议

对象简介


简单方式:直接调用JS代码

// 一个JSContext对象,就类似于Js中的window,
// 只需要创建一次即可。
self.jsContext = [[JSContext alloc] init];

//  jscontext可以直接执行JS代码。
[self.jsContext evaluateScript:@"var num = 10"];
[self.jsContext evaluateScript:@"var squareFunc = function(value) { return value * 2 }"];
// 计算正方形的面积
JSValue *square = [self.jsContext evaluateScript:@"squareFunc(num)"];

// 也可以通过下标的方式获取到方法
JSValue *squareFunc = self.jsContext[@"squareFunc"];
JSValue *value = [squareFunc callWithArguments:@[@"20"]];
NSLog(@"%@", square.toNumber);
NSLog(@"%@", value.toNumber);

快速调用Block,可传参数
各种数据类型可以转换,Objective-C的Block也可以传入JSContext中当做JavaScript的方法使用。虽然JavaScritpCore没有自带(毕竟不是在网页上运行的,自然不会有window、document、console这些类了),仍然可以定义一个Block方法来调用NSLog来模拟:

JSContext *context = [[JSContext alloc] init];
context[@"log"] = ^() {
NSLog(@"+++++++Begin Log+++++++");
NSArray *args = [JSContext currentArguments];
for (JSValue *jsVal in args) {
NSLog(@"%@", jsVal);
}
JSValue *this = [JSContext currentThis];
NSLog(@"this: %@",this);
NSLog(@"-------End Log-------");
};
[context evaluateScript:@"log('ider', [7, 21], { hello:'world', js:100 });"];
//
// Output:
// +++++++Begin Log+++++++
// ider
// 7,21
// [object Object]
// this: [object GlobalObject]
// -------End Log-------

通过Block成功的在JavaScript调用方法回到了Objective-C,而且依然遵循JavaScript方法的各种特点,比如方法参数不固定。也因为这样,JSContext提供了类方法来获取参数列表(+ (JSContext *) currentArguments;)和当前调用该方法的对象(+ (JSValue *)currentThis)。对于"this"
,输出的内容是GlobalObject,这也是JSContext对象方法- (JSValue *)globalObject;
所返回的内容。因为我们知道在JavaScript里,所有全局变量和方法其实都是一个全局变量的属性,在浏览器中是window,在JavaScriptCore是什么就不得而知了。Block可以传入JSContext作方法,但是JSValue没有toBlock方法来把JavaScript方法变成Block在Objetive-C中使用。毕竟Block的参数个数和类型已经返回类型都是固定的。虽然不能把方法提取出来,但是JSValue提供了- (JSValue *)callWithArguments:(NSArray *)arguments;
方法可以反过来将参数传进去来调用方法。

JSContext *context = [[JSContext alloc] init];
[context evaluateScript:@"function add(a, b) { return a + b; }"];
JSValue *add = context[@"add"];
NSLog(@"Func: %@", add);
JSValue *sum = [add callWithArguments:@[@(7), @(21)]];
NSLog(@"Sum: %d",[sum toInt32]);
// OutPut:
// Func: function add(a, b) { return a + b; }
// Sum: 28
  • JSValue
    还提供- (JSValue *)invokeMethod:(NSString *)method withArguments:(NSArray *)arguments;
    让我们可以直接简单地调用对象上的方法。只是如果定义的方法是全局函数,那么很显然应该在JSContext的globalObject对象上调用该方法;如果是某JavaScript对象上的方法,就应该用相应的JSValue

对象调用。
异常处理Objective-C的异常会在运行时被Xcode捕获,而在JSContext中执行的JavaScript如果出现异常,只会被JSContext捕获并存储在exception属性上,而不会向外抛出。时时刻刻检查JSContext
对象的exception是否不为nil显然是不合适,更合理的方式是给JSContext
对象设置exceptionHandler,它接受的是^(JSContext context, JSValue exceptionValue)
形式的Block。其默认值就是将传入的exceptionValue赋给传入的context的exception属性:

^(JSContext *context, JSValue *exceptionValue) {
context.exception = exceptionValue;
};

我们也可以给exceptionHandler
赋予新的Block以便在JavaScript运行发生异常的时候我们可以立即知道:

JSContext *context = [[JSContext alloc] init];
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
NSLog(@"%@", exception);
con.exception = exception;
};
[context evaluateScript:@"ider.zheng = 21"];
//Output:
// ReferenceError: Can't find variable: ider
  • 使用Block的注意事项
    从之前的例子和介绍应该有体会到Block在JavaScriptCore中起到的强大作用,它在JavaScript和Objective-C之间的转换 建立起更多的桥梁,让互通更方便。但是要注意的是无论是把Block传给JSContext对象让其变成JavaScript方法,还是把它赋给exceptionHandler属性,在Block内都不要直接使用其外部定义的JSContext对象或者JSValue,应该将其当做参数传入到Block中,或者通过JSContext的类方法+ (JSContext )currentContext;来获得。否则会造成循环引用使得内存无法被正确释放。比如上边自定义异常处理方法,就是赋给传入JSContext对象con,而不是其外创建的context对象,虽然它们其实是同一个对象。这是因为Block会对内部使用的在外部定义创建的对象做强引用,而JSContext也会对被赋予的Block做强引用,这样它们之间就形成了循环引用使得内存无法正常释放。对于JSValue也不能直接从外部引用到Block中,因为每个JSValue上都有JSContext
    的引用 (@property(readonly, retain) JSContext
    context;),JSContext再引用Block同样也会形成引用循环。
    前面十分的简单方便而且高效,不过也仅限于数值型、布尔型、字符串、数组等这些基础类型。
    为了方便起见,以下所有代码中的JSContext对象都会添加如下的log
    方法和eventHandler
JSContext *context = [[JSContext alloc] init];
context.exceptionHandler = ^(JSContext *con, JSValue *exception) {
NSLog(@"%@", exception);
con.exception = exception;
};
context[@"log"] = ^() {
NSArray *args = [JSContext currentArguments];
for (id obj in args) {
NSLog(@"%@",obj);
}
};

复杂但强大的方式:通过协议,模型实现(事先和前端协商格式)
首先,我们需要先定义一个协议,而且这个协议必须要遵守JSExport协议。
注意js中调用oc方法时参数的写法

@protocol JavaScriptObjectiveCDelegate <JSExport>

// JS调用此方法来调用OC的系统相册方法
- (void)callSystemCamera;

// 在JS中调用时,函数名应该为showAlertMsg(arg1, arg2)
// 这里是只两个参数的。
- (void)showAlert:(NSString *)title msg:(NSString *)msg;

// 通过JSON传过来
- (void)callWithDict:(NSDictionary *)params;

// JS调用Oc,然后在OC中通过调用JS方法来传值给JS。
- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params;

@end

为了在调用原生方法之后调用对应的JS方法,写两个JS方法:

 var jsFunc = function() {
   alert('Objective-C call js to show alert');
 }

 var jsParamFunc = function(argument) {
   document.getElementById('jsParamFuncSpan').innerHTML
   = argument['name'];
 }

接下来,我们还需要定义一个模型,实现了上面定义协议里面的方法:

// 此模型用于注入JS的模型,这样就可以通过模型来调用方法。
@interface ObjCModel : NSObject <JavaScriptObjectiveCDelegate>

@property (nonatomic, weak) JSContext *jsContext;
@property (nonatomic, weak) UIWebView *webView;

@end

实现这个模型:

@implementation ObjCModel

- (void)callWithDict:(NSDictionary *)params {
 NSLog(@"Js调用了OC的方法,参数为:%@", params);
}

// Js调用了callSystemCamera
- (void)callSystemCamera {
 NSLog(@"JS调用了OC的方法,调起系统相册");

 // JS调用后OC后,又通过OC调用JS,但是这个是没有传参数的
 JSValue *jsFunc = self.jsContext[@"jsFunc"];
 [jsFunc callWithArguments:nil];
}

- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params {
 NSLog(@"jsCallObjcAndObjcCallJsWithDict was called, params is %@", params);

 // 调用JS的方法
 JSValue *jsParamFunc = self.jsContext[@"jsParamFunc"];
 [jsParamFunc callWithArguments:@[@{@"age": @10, @"name": @"lili", @"height": @158}]];
}

- (void)showAlert:(NSString *)title msg:(NSString *)msg {
 dispatch_async(dispatch_get_main_queue(), ^{
   UIAlertView *a = [[UIAlertView alloc] initWithTitle:title message:msg delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles:nil, nil];
   [a show];
 });
}

@end

接下来,我们在controller中在webview加载完成的代理中,给JS注入模型。

注意,获取webview的jsContext的方法 self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView {
 self.jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];

 // 通过模型调用方法,这种方式更好些。
 ObjCModel *model  = [[ObjCModel alloc] init];
 self.jsContext[@"OCModel"] = model;
 model.jsContext = self.jsContext;
 model.webView = self.webView;

 self.jsContext.exceptionHandler = ^(JSContext *context, JSValue *exceptionValue) {
   context.exception = exceptionValue;
   NSLog(@"异常信息:%@", exceptionValue);
 };
}

通过KVC取得context,其路径为documentView.webView.mainFrame.javaScriptContext。这样就可以获取到JS的context,然后为这个context注入我们的模型对象。

这里我们定义了两个JS方法,一个是jsFunc,不带参数。
另一个是jsParamFunc,带一个参数。

接下来,我们在html中的body中添加以下代码:

<div style="margin-top: 100px">
<h1>Test how to use objective-c call js</h1>
<input type="button" value="Call ObjC system camera" onclick="OCModel.callSystemCamera()">
<input type="button" value="Call ObjC system alert" onclick="OCModel.showAlertMsg('js title', 'js message')">
</div>

<div>
<input type="button" value="Call ObjC func with JSON " onclick="OCModel.callWithDict({'name': 'testname', 'age': 10, 'height': 170})">
<input type="button" value="Call ObjC func with JSON and ObjC call js func to pass args." onclick="OCModel.jsCallObjcAndObjcCallJsWithDict({'name': 'testname', 'age': 10, 'height': 170})">
</div>

<div>
<span id="jsParamFuncSpan" style="color: red; font-size: 50px;"></span>
</div>

现在就可以测试代码了。

当我们点击第一个按钮:Call ObjC system camera时,
通过OCModel.callSystemCamera(),就可以在HTML中通过JS调用OC的方法。
在OC代码中,我们的callSystemCamera方法体中,添加了以下两行代码,就是获取HTML中所定义的JS就去jsFunc,然后调用它。

 JSValue *jsFunc = self.jsContext[@"jsFunc"];
 [jsFunc callWithArguments:nil];

这样就可以在JS调用OC方法时,也让OC反馈给JS。

看看下面传字典参数:

- (void)jsCallObjcAndObjcCallJsWithDict:(NSDictionary *)params {
 NSLog(@"jsCallObjcAndObjcCallJsWithDict was called, params is %@", params);

 // 调用JS的方法
 JSValue *jsParamFunc = self.jsContext[@"jsParamFunc"];
 [jsParamFunc callWithArguments:@[@{@"age": @10, @"name": @"lili", @"height": @158}]];
}

获取我们在HTML中定义的jsParamFunc方法,然后调用它并传了一个字典作为参数。

步骤有点多,但是理顺了确实很好用.

JavaScriptCore使用注意

JavaStript调用本地方法是在子线程中执行的,这里要根据实际情况考虑线程之间的切换,而在回调JavaScript方法的时候最好是在刚开始调用此方法的线程中去执行那段JavaStript方法的代码

根据url前缀特殊处理(协议拦截)-简单传递参数

在UIWebView的代理方法:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType判断url前缀,然后,根据协议类型进行特殊处理.

如果需要从url接受参数,我们可以把参数拼接到url上,从而传递到原生中.但是这种方式局限比较大

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSString *urlStr = request.URL.absoluteString;
    if ([urlStr rangeOfString:@"test://"].location != NSNotFound) {
        // url的协议头是test的特殊处理
        NSLog(@"test");

        NSURL *url = [NSURL URLWithString:urlStr];
        NSString *scheme  = url.scheme;
        NSString *host = url.host;
        NSString *qurey = url.query;
        NSString *parameter = url.parameterString;

       // 根据参数做进一步处理
       // TODO

        return NO;
    }
    return YES;
}


文/纸简书生(简书作者)
原文链接:http://www.jianshu.com/p/fad8c7844d3e
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

MUI_5+SDK_Native.js

阅读数 3101

没有更多推荐了,返回首页