开发一个React-Native的APP

从7月底到9月底两个月的时间开发了一个React-Native的APP。不得不说,用RN的开发效率还是很快的。就来总结一下历程。

整个项目的逻辑大概就是:

  1. 新建项目,导入项目的图片资源,APP的图标,启动图,ios的启动图在Xcode中直接设置Launch Image和icon,对于安卓的启动图要写代码实现才行。对于项目的图片资源,可以两套系统共用一套图片那就可以放在一个文件夹下,引入的时候用相对路径导入;也可以放在Xcode或者是Android Studio中进行uri:+图片名称导入。
  2. 用上一篇的第三方组件和一些基本组件大概完成了一些页面,完成了登录,手势解锁页,还有主页面的框架和主页面中一些子页面的大体显示。接下来遇到的问题大概有两个:

第一:如何跳转,把这个页面都串起来,就像一些珠子如何才能串成一串:导航的使用:Navigator已经废弃,就要使用react-naviagtion来导航。

第二:如何进行数据处理:

  1. 是如何从服务器获取数据.
  2. 是如何把从服务器获取的数据进行持久化存储。
  3. 如何在页面间进行参数的传递
    对于数据的处理也是难点。
    首先对于网络请求要用fetch,
    遇到的相关问题:

  4. 获取设备ID:用第三方库,补充:JS中如何把字符串中的?替换为&:
    var str_replaced = str.replace(/\?/g,"&");//加g就是替换所有的?号

  5. React-Natvie用RSA加密用户的密码:
    react-native-rsa

node-rsa
虽然有这些第三方库,但是我并没有用,这个简直搞到我头大,终于搞定了!
主要是参照这两个网址:
http://www.cnblogs.com/Grart/p/5080228.html

node-rsa的基础库http://www-cs-students.stanford.edu/~tjw/jsbn/
因为密码需要先用公钥RSA加密,然后在用base64加密,加密以后的字符串传给服务器,服务器那边再先base64解密,然后再用私钥解密。
在使用的时候最大的问题就是你要传一个N和E的参数来setPublic,这个问题困扰了我一天之久,终于后端的同学帮我搞定了,Java里面有方法可以根据已有的公钥来逆向出N和E的值,然后再使用下面的那个网页,网页,没错,就是下载下来那个网页,然后就有基本的JS文件了,其实RN就是JS,只怪自己前端不佳,然后把JS文件转化成RN可以用的JS组件库导入直接调用就可以加密成功了!终于加密成功了。感动哭。

  1. 设置假数据显示一般来说都是容易的,与后台交互就需要用网络请求了。一般是GET和POST请求。POST的请求头:

一般常见的网络请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var login=function(data,doSuccess,doError){
fetch('http://'+url+'/login',
{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
.then(function(response) {return response.json();})
.then(
function(resData){
doSuccess(resData);
}
)
.done();
};

而我们这个post请求头呢?是下面这样的。

1
'Content-Type':'application/x-www-form-urlencoded'

一般在post的body中是用:
{userName:this.state.userName,passWord:_pwd}这样的JSON传过去,再body: JSON.stringify(data)。。。
而我们这个呢?data是拼接起来的,我也是醉了,这我是用Charles发请求后才发现的,也是折腾了半天才明白的。也用postman还有终端httpie发现都可以请求成功,唯独用这个请求不到,后来就拼接了一下,成功了。

  1. JSON.stringify(result);和JSON.parse(result)有什么区别啊?
    用于把JavaScript对象序列化为JSON字符串和把JSON字符串解析为原生JavaScript。
    JSON.stringify将JavaScript对象转换为JSON文本,并将该JSON文本存储在字符串中。
    JSON.parse将一串JSON文本转换成Javascript对象。

当你在fetch请求后 .then((response)=> response.json())的时候就不用解析直接用.取。。。
当你在fetch请求后, .then((response) => response.text())的时候就需要JSON.parse(result);

  1. 初学者最容易犯错误的地方就是this指针了!!我是在导航跳转的时候意识到这个问题的。每个组件都是有props和states的。
    子组件如何调用父组件:this.props。
    父组件如何调用子组件:首先用属性ref给子组件取个名字吧,this.refs.名字.getDOMNode().

  2. 从服务返回的JSON数据的解析,有些需要遍历一下。

1
2
let brandata = result.dangqiAnalysisBrandList;
let brandeach=brandata.map((val) => {return val['dangqiAnalysisList'] });

安卓打包

  1. 生成一个签名密钥

keytool -genkey -v -keystore my-release-key.keystore -alias my-key-alias -keyalg RSA -keysize 2048 -validity 10000

最后它会生成一个叫做my-release-key.keystore的密钥库文件

  1. 找到路径/android/app/src/main,并在该目录下新建assets文件夹

  2. 在工程目录下将index.android.bundle下载并保存到assets资源文件夹中

curl -k "http://localhost:8081/index.android.bundle" > android/app/src/main/assets/index.android.bundle

这句命令是重点,如果assets目录中不存在该文件,则打包的apk在执行时显示空白。

Protocol ‘http not supported or disabled in libcurl

Windows下安装使用curl命令:http://jingyan.baidu.com/article/a681b0dec4c67a3b1943467c.html

  1. 添加gradle的android keystore配置

打包的apk在未签名的情况下,在手机中(非root)是不允许安装的

在build.gradle文件中

//签名
signingConfigs{ release { storeFile file("/my-release-key.keystore") storePassword "密码" keyAlias "keyAlias的名字" keyPassword "密码" } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release //添加这句话引用签名配置 } }

  1. 启用Proguard代码混淆来缩小APK文件的大小

Proguard是一个Java字节码混淆压缩工具,它可以移除掉React Native Java(和它的依赖库中)中没有被使用到的部分,最终有效的减少APK的大小。

重要:启用Proguard之后,你必须再次全面地测试你的应用。Proguard有时候需要为你引入的每个原生库做一些额外的配置。参见app/proguard-rules.pro文件。

def enableProguardInReleaseBuilds = true

  1. 在/android/目录中执行gradle assembleRelease命令,打包后的文件在 android/app/build/outputs/apk目录中,例如app-release.apk。如果打包碰到问题可以先执行 gradle clean 清理一下。

一定要注意:在打包的时候要运行起来安卓。
安装gradle工具(版本与android\gradle\wrapper下的一致),并配置环境变量,配置GRADLE_HOME到你的gradle根目录当中,然后把%GRADLE_HOME%/bin(linux或mac的是$GRADLE_HOME/bin)加到PATH的环境变量。
mac要配置gradle的环境变量的路径

配置完成之后,运行gradle -v,检查一下是否安装无误

  1. 将apk发布到各大应用市场(BUILD SUCCESSFUL)

iOS打包

  1. 在项目的目录下建立bundle文件夹。然后执行如下命令

react-native bundle --entry-file index.ios.js --platform ios --bundle-output ./bundle/main.jsbundle --dev false --assets-dest ./bundle/
几分钟过后,在 bundle 文件夹里会多出个 assets 文件夹和 main.jsbundle 文件。不然会提示npm install…

  1. 把刚刚得到的 main.jsbundle 和 assets 文件夹添加到项目中, 在 Xcode 中 assets 资源文件夹比较特殊,必须用 Create folder references 的方式添加进去,添加完后是蓝色文件夹图标.

  2. 修改 AppDelegate.m 文件
    在XCODE里打开 APPDELEGATE.M 文件,找到代码 JSCODELOCATION = [[RCTBUNDLEURLPROVIDER SHAREDSETTINGS],在其下方添加以下代码

jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
注意:代码里的 @”main” 和 @”jsbundle”,与之前生成的文件 main.jsbundle 对应。

  1. 在XCODE里点击运行,使用模拟器测试APP能否正常运行。
  2. 和在Xcode里面打包原生应用一样的。

打包遇到的问题

native-echarts的组件问题 —–真机上面React-echart显示不了吗?白色的。

主要是因为路径不对。

Android
将node_modules/native-echarts/src/components/Echarts/tpl.html拷贝到assets下需要修改node_modules/native-echarts/src/components/Echarts/index.js
IOS
将tpl.html 放置 Xcode项目 下面 然后如下引用,修改源码下面的index.js
为下面的:

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
 import React, { Component } from 'react';
import { WebView, View, StyleSheet } from 'react-native';
import renderChart from './renderChart';
import echarts from './echarts.min';

export default class App extends Component {
componentWillReceiveProps(nextProps) {
if(nextProps.option !== this.props.option) {
this.refs.chart.reload();
}
}

render() {
return (
<View style={{flex: 1, height: this.props.height || 400,}}>

{Platform.OS==='ios'?(
<WebView
ref="chart"
scrollEnabled = {false}
injectedJavaScript = {renderChart(this.props)}
style={{
height: this.props.height || 400,
}}

source={{uri:'tpl.html'}}
/>
):(
<WebView
ref="chart"
scrollEnabled = {false}
injectedJavaScript = {renderChart(this.props)}
style={{
height: this.props.height || 400,
}}

source={{uri:'file:///android_asset/tpl.html'}}
/>
)}
</View>
);
}
}

真机测试时候发现的问题

react-navigation的问题:

  1. ios上面:
    手势左边滑动就能返回到上一个页面,这样在你不需要返回的时候也返回了。
    解决方法:
    gesturesEnabled:是否支持滑动返回收拾,iOS默认支持,安卓默认关闭。设置为false试了一下。
    参照文章:https://hans007.github.io/react-native/2017/06/19/react-navigation

  2. Android上面:物理返回键的处理。
    BackAndroid已经废弃了,使用BackHandler,大概思路就是添加监听,
    之前使用Navigator的时候,可以通过下面的方法实现监听安卓的返回键,但使用了react-navigation后,会很迷茫,不知该怎么监听了。

解决办法:集成Redux咯!集成完Redux,在跳转之后,就能获得路由的length,可以通过length来判断当前页面是第几层。

Navigator的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
componentWillMount() {
if (Platform.OS === 'android') {
BackAndroid.addEventListener('hardwareBackPress', this.onBackAndroid);
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
BackAndroid.removeEventListener('hardwareBackPress', this.onBackAndroid);
}
}
onBackAndroid = () => {
const nav = this.navigator;
const routers = nav.getCurrentRoutes();
if (routers.length > 1) {
nav.pop();
return true;
}
return false;
};
……
}

react-navigation的方式

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
componentWillMount() {
if (Platform.OS === 'android') {
BackHandler.addEventListener('handwareBackPress',this.onBackAndroid)
}
}
componentWillUnmount() {
if (Platform.OS === 'android') {
BackHandler.addEventListener('handwareBackPress',this.onBackAndroid)
}
}
onBackAndroid = () => {
const routers = nav.getCurrentRoutes();
if (routers.length > 1) {
return true;
}
return false;
};
……
}

// 在跳转之后的页面中
onBackAndroid = ()=> {

const {routes} = this.props;
console.log(routes);
// alert(routes)
if (routes.length > 1) {
// 因为其他页面获得不到this.props,所以只能每个页面都写这个方法。
this.props.navigation.goBack();
return true;
}
}

其他小问题

需要把服务器取过来的数字(也有可能是数字字符串),将小数点前的数字每隔三位添加一个逗号(前面数是一个四位数的值)。比如取到的值是123456,要将其转换成123,456。搜索了一下,这叫数字分位符号。

总结

代码量:近1万行,调通近40个接口。还有很多方面要优化,比如没有用Redux。还有一些其他方面的逻辑也有待优化。

文章目录
  1. 1. 安卓打包
  2. 2. iOS打包
  3. 3. 打包遇到的问题
    1. 3.0.1. native-echarts的组件问题 —–真机上面React-echart显示不了吗?白色的。
  • 4. 真机测试时候发现的问题
    1. 4.0.1. react-navigation的问题:
    2. 4.0.2. 其他小问题
  • 5. 总结
  • ,