Code-push 使用

Code-push 使用

正常热更新升级

将Entry 中 isTest改为true
测试: code-push release-react appname ios(或android) –description “1. 测试更新 \n\n2. 测试换行 \n\n3” –deploymentName Staging

将Entry 中 isTest改为false
正式: code-push release-react appname ios –description “1. 测试更新 \n\n2. 测试换行 \n\n3. 测试” –deploymentName Production

强制升级

code-push release-react appname ios(或android) –description “1. 测试更新 \n\n2. 测试换行 \n\n3. 测试 <强制升级>2.0” –deploymentName Staging –targetBinaryVersion ‘1.0’

线上热更新流程

采用code-push 发布补丁,指定版本( –targetBinaryVersion ‘~1.’ 即1.*),每三次补丁,升级一个Version(例: 1.0 => 1.1)

每次发补丁,先发Staging(dev分支中),测试通过发Production
升级version,修改ios Info.plist文件中的CFBundleShortVersionString和CFBundleVersion,android中的build.gradle文件中的versionName(大版本同时修改versionCode)

每次升级都要分别发送appname-ios ios appname-android android

注意 上线前一定要把istest改为false,同理 测试前一定要把isTest改为true

React-native 使用native第三方sdk

React-native 使用native第三方sdk

ios(以阿里百川用户反馈为例)

  1. 首先安装cocopods(类似于npm,ios开发的依赖管理工具,教程:http://www.code4app.com/article/cocoapods-install-usage)

  2. 在ios根目录下创建Podfile文件,添加如下代码(使用的是百川feedback1.1.1版本),然后执行pod install命令

1
2
3
target ‘Xss’ do
pod 'YWFeedbackFMWK', '~> 1.1.1'
end
  1. pod安装完成后,使用xcode打开Xss.xcworkspace(我的项目名是Xss),在项目中创建BCBridge.h以及BCBridge.m文件,以建立js和原生的bridge,.h文件只是个头文件,.m文件代码如下
    在这里简要介绍下ios下的controllerView切换机制,controllerView 切换主要有两种,push和present,其中,push必须在同一UINavigationController发生,push的动画表现为横向切入,present的动画为底部向上切入(类似于弹窗),由于react-native本身处于一个UINavigationController中,然后我目前还没有找到能向这个UINavigationController中push的方法,所以这里采用的是present的方式。
    由于这种controller切换在oc里限制比较多,且使用别人的viewController可自定义的部分太受限,所以不是很推荐这种方式。
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
#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
#import "RCTRootView.h"
#import "BCBridge.h"
#import "YWFeedbackFMWK/YWFeedbackKit.h"
static YWFeedbackKit *feedbackKit; // 声明一个阿里百川feedback对象
@interface BCBridge ()
@property (nonatomic, strong) UINavigationController *navi;
@end
@implementation BCBridge
+(void)initialize {
// 使用在百川后台申请的appkey来初始化feedbackKit
feedbackKit = [[YWFeedbackKit alloc] initWithAppKey: @"yourappkey"];
}
// 建立Bridge,在js中直接使用
RCT_EXPORT_MODULE(BCBridge);
// 在js中调用时函数名为BCFeedback
RCT_EXPORT_METHOD(BCFeedback: (NSDictionary *)style) {
// 自定义的样式注入,style变量为NSDictionary类型的,有js方法调用时传入,js中表现为Object
feedbackKit.customUIPlist = style;
// 将present操作提升到主进程来做(这里我也不太懂oc),这里百川1.0的feedback必须这样做才能切换过去,2.0不存在这个问题
dispatch_async(dispatch_get_main_queue(), ^{
// 调用阿里百川提供的初始化方法,此方法接受一个回调函数,默认参数为初始化后的viewController
[feedbackKit makeFeedbackViewControllerWithCompletionBlock:^(YWLightFeedbackViewController *viewController, NSError *error) {
// 创建一个新的UINavigationController以阿里百川返回的viewController为RootViewController
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:viewController];
// 将此controller设为当前域,可以退出
self.navi = nav;
// 设置title
viewController.title = @"意见反馈";
// 设置关闭按钮
viewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"关闭" style:UIBarButtonItemStylePlain target:self action:@selector(back)];
// 执行present操作(此view将从屏幕下方向上切入)
[[UIApplication sharedApplication].delegate.window.rootViewController presentViewController:nav animated:YES completion:nil];
}];
});
}
// 声明退出函数
- (void)back
{
[self.navi dismissViewControllerAnimated:YES completion:nil];
}
@end

js中调用

1
2
3
4
5
import {
NativeModules
} from 'react-native'
NativeModules.BCBridge.BCFeedback(options)
  1. 至此,封装完毕,但是这种方式并不友好,而且也不符合react-native统一ui的思想,所以建议使用此种方式来封装第三方sdk的方法(获取数据),然后使用react-native实现一套统一的ui(既可用于android也可用于ios)。但阿里百川并没有提供直接获取数据的方法,所以选择sdk时一定要慎重。

android

  1. 依据官方文档下载对应版本的sdk(这里使用的是1.1.3版本的)
  2. 在app下建立libs文件夹(如果没有的话),将sdk中文件放进去,将项目根目录下的build.gradle文件对应位置添加如下语句
1
2
3
4
5
6
7
8
9
allprojects {
repositories {
...
flatDir {
dirs 'libs'
}
...
}
}

在app目录下的build.gradle文件对应位置添加如下语句
有个大坑是因为阿里百川feedbackSdk默认使用multidex模式编译,如果不在项目中做对应设置,会导致一直编译不通过,看了无数种解决办法才解决此问题,泪崩

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
``` java
defaultConfig {
...
multiDexEnabled true // 开启multidex模式编译,此处为大坑,否则编译不过
}
dependencies {
...
compile 'com.android.support:multidex:1.0.0' // 此依赖用于开启mulidex模式编译
compile(name: 'feedbackSdk', ext: 'aar')
compile files('libs/securityguard-3.1.27.jar')
compile files('libs/utdid4all-1.0.4.jar')
compile files('libs/alisdk-ut-5.jar')
}
```
3. 初始化
在MainActivity类中的onCreate方法中添加如下语句(如果FeedbackAPI无法引入,说明sdk依赖为添加成功,请检查上一步)
```java
MultiDex.install(this); // 同样是开启multidex模式编译,网上大部分解决方案都没提这个设置,泪崩

FeedbackAPI.initAnnoy(this.getApplication(), "yourappkey");    // 初始化阿里百川意见反馈
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
4. 封装activity切换方法
创建BCBridge类(注意引入对应依赖)
具体代码如下
``` java
public class BCBridge extends ReactContextBaseJavaModule {
public BCBridge(final ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
// 设置在js中调用的类名
return "BCBridge";
}
// 在js中调用的方法名同样为BCFeedback,readableMap对应js中的Object
@ReactMethod
public void BCFeedback(ReadableMap map) {
ReadableNativeMap middleMap = (ReadableNativeMap) map;
// 将ReadableMap转化为hashMap
Map nativeMap = middleMap.toHashMap();
// 设置部分ui样式
FeedbackAPI. setUICustomInfo(nativeMap);
// 切换到阿里百川反馈界面
FeedbackAPI.openFeedbackActivity(getReactApplicationContext());
}
}
  1. 建立BCBridgePackage
    将上一步封装的方法集成到应用中(我是这样理解的)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class BCBridgePackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
    return Arrays.<NativeModule>asList(
    new BCBridge(reactContext)
    );
    }
    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
    return Collections.emptyList();
    }
    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
    return Collections.emptyList();
    }
    }

同时在MainApplication中对应位置添加如下代码(如果引用一些别人封装好的rn-原生组件,通过rn link 也能实现此操作,但是手动更改此文件时可能会导致一些情况下rn link失效,请注意检查)

1
2
3
4
5
6
7
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
...
new BCBridgePackage()
);
}

  1. 对比于oc,java的代码好理解些,但是使用android的activity同样会有ios中提到的问题。

总结(个人心得)

由于上面提到的封装原生的页面(ios中体现为viewController,android中体现为activity),所以不提倡直接去使用别人集成好的viewController和activity,比较提倡使用这类方式来集成原生中的方法或者是组件,然后用rn来实现整体的ui布局,这样在开发成本上以及性能上都能得到很大的提高。

利用iframe实现ajax跨域请求,抓取网页中ajax数据

如何利用网页ajax请求暴露出来的接口去抓取网页数据?很多爬虫都能实现这个功能。不过今天要来和大家八一八单从前端的角度,利用js解决这个问题。

大家都知道,在不同域的情况下是不能发送ajax请求的,浏览器会报如下错误:

跨域ajax报错

同时,内嵌的iframe中无法进行跨域通信的,也就是说不同域的iframe是无法互相读取数据的(当然利用hash变化可以从父window传入数据到子iframe,不过并没有什么意义)。iframe跨域通信时,浏览器会报如下错误:

跨域iframe报错

其实这两个问题都是由于跨域造成的。

下面就介绍如何解决这个问题。

其实问题的关键就在于,浏览器在解析ajax请求地址时会和当前网页的地址进行比较,如果是跨域的,那就禁止掉并且报错。那么我们如果让浏览器解析出的ajax地址和当前网页的解析地址一样,浏览器不就不会禁止我们的请求了么。

那么浏览器是如何解析url的呢?

首先当浏览器访问一个域名时,会查询本地的DNS缓存中是否有关于这个网址对应ip地址,如果有的话,直接从本地取得ip地址然后访问,如果没有,浏览器就会向DNS服务器发出DNS请求获得该域名对应的ip地址然后存入本地缓存然后访问。

那么介于以上问题,我们只要在本地伪造一条域名的解析方式,然后再通过伪造的域和目标域进行跨域请求不就可以了么。

windows下的打开C:\Windows\System32\drivers\etc
这个文件夹下有一个hosts文件,如果改过hosts来上谷歌的同学对这个应该很熟悉,在hosts文件里加上这样一段代码:

1
127.0.0.1 a.目标网址.com

这样你的访问a.目标网址.com就和访问localhost一样了,这样做的目的是方便搭起本地的服务时,本地的服务和目标的域名之间就不会存在跨域问题了,这样就能在本地,通过在目标网页植入iframe标签的方式,向目标域发起跨域请求,取得目标域的数据。

直接上代码(用了jQuery)

脚本代码,直接插在父域

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
var mySrc = "http://a.目标网址.com:9000/myIframe.html";
document.domain = "目标网址.com"; //关键代码,将域提升到根域
$("body").append('<iframe src=' + mySrc + ' name="myIframe" id="getData"></frame>'); //向目标网页插入iframe
var interval;
function start() {
$("#getData").attr({"src": mySrc});
interval = setInterval(function() {
window.myIframe.run(getLogitic); //向子域传入回调函数
},10000)
}
function stop() {
clearInterval(interval);
}
function getLogitic(orderId) {
$.ajax({
url: '/query?'+ orderId +'&id=1&valicode=&temp=' + Math.random(),
method: 'GET',
success: function(res) {
console.log(res); //可以在此再调用子域的方法,向本地文件传输数据
},
error: function(err) {
console.log('err: ', err);
}
})
}

iframe中html代码

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<script src="bower_components/jquery/dist/jquery.js"></script>
<script>
document.domain = "目标网址.com"; //关键代码,将子域提升到根域
var int;
function run(callback) {
//此请求用于向本地请求数据,然后根据本地的数据,利用父域传过来的回调函数向目标域发起请求,得到目标域的数据
$.ajax({
url: './getOrderList.json',//本地数据存储的地方,偷懒直接写了个json文件,可以是数据库中的数据
method: 'GET',
success: function(res) {
var data = res.list;
int = setInterval(function(){
callback(data[0]); //执行父域传入的回调函数
data.shift();
if (data.length === 0) clearInterval(int);
}, 1000);
},
error: function(err) {
console.log(err)
}
})
}
</script>
</body>
</html>

注意:

  1. 只有将iframe提升到根域,这样才能与父window通信,耳document.domain指令只能提升当前域到当前的根域,这也是必须要修改本地hosts文件的原因,这是解决跨域问题的根本。
  2. 在抓取目标网页数据之前,要先看目标网页发送ajax请求的方式,得到请求的api,通过目标网页的控制台插入脚本,然后运行,得到要得到的数据,在通过和本地请求的方式,发送到本地。

下面是抓取某物流查询网页中物流信息的过程:

涂掉的为目标网址;这是向目标网页插入我的脚本,成功后网页中就会被插入了一个地址为本地的,但是域名和目标域相同的iframe。

图片描述

####结果

这些数据可以在请求成功会传回本地。

图片描述

最后,其实博主也是初识前端,还处在学习和探索当中,希望能与大家一起学习进步,欢迎大家指出文章中的错误和不足,一定虚心接受!

btw,首篇博文,希望大家多多支持!!!