React-Native实例

前言

移动开发也走过了将近10年的历程,开发的技术也随着移动设备各个版本的迭代发生了巨大的变化,不管是iphone X和android 8.X和最初的iphone 1以及android1.X,无疑都发生了巨大的变化,哪怕一两年的节点之间,移动设备的软硬件都会产生巨大的变化。于此同时,开发技术和模式也有了长足的发展,从早前的纯native开发,到后来的webview+html的hybrid native开发模式,以及现在以React-Native、Weex等融合前端技术的DyNative模式,以及Google推出instant run之后各大公司开发的热修复框架、相应开发场景的开源框架等等,目标都是一致的,在合适的开发场景运用相应最合适的技术,为用户带来最好的使用体验,这篇文章就已我当前的项目的一小部分改造来聊聊React-Native的一些特性。

React-Native的优缺点

下图是几种开发模式的描述图:

三种模式.png

从上图描述中能得到几个关键的特性,1、性能比WebView+Html的传统APP要好。2、开发体系不完善。对于第一点,传统的webview实际上是基于webkit的微型浏览器,通过webkit的内核对页面进行渲染,由于webkit本身就是一个重量级内核,执行效率一般,React-Native较高的效率的原因是由于RN基于虚拟DOM技术,虚拟DOM技术指的是使用javascript Object模拟DOM树,计算变更,减少重绘,提升效率的一种技术实现。对于第二点,由于React-Native是基于前端的跨平台的框架,因此很难做到平衡各平台之间的特性,往往会出现各种各样的问题,例如某些特性IOS上有,Android平台不支持,或者各平台表现不一致,这都是开发者需要面对的问题。

总的来讲,按我个人的理解,React-Native最大的优点在于两点,第一,能够统一各大平台的UI显示(这点有点类似于BootStrap);第二,执行效率较传统webapp要快,能一定程度提升用户体验;第三,较于原生开发,能够支持热更新。缺点在于,React-Native并不能简化开发流程,减少开发量,React-Native提出的“Learn Once ,Write Anywhere”口号描绘的开发者基于React-Native的移动app能运行于android、ios平台能打破传统一个平台至少一个开发者的愿景是不太可能实现的,原因无他,因为即便是android或者ios自家平台的不同系统版本都无法做到完全适配,目前使用Dynative的互联网公司的配置一般是前端组、android组、ios组,前端组专门负责这些页面的绘制、逻辑和于各平台的native开发人员进行交互。再一个开发体系不完善,不同平台之间不能做到完全无差异化,以及对native特性的完全支持。

本篇文章要达到的目标

目前我从事的项目执行效果如下:

手机端
PC端

本篇文章的目标就是将手机端的主页面改造成React-Native,模拟Native端发送连接信息,改变主页连接信息的显示以及React-Native的主页点击扫描按钮调用Native端的扫码页面。

准备工作:

React-Native的配置:

环境的搭建可以参考React-Native 入门(环境搭建及运行)

但是大多数时候第一运行结果都会报错

运行失败.png

总结报错的原因,主要原因有以下几点:

本地的端口和真机或模拟器的ip未设置好。

解决办法:

adb reverse tcp:8081 tcp:8081,然后在项目运行后摇动手机进入设置页面,设置项目的ip端口,如进入不了设置页面。

需要将设置页面的声明加入到manifest

Could not get BatchedBridge, make sure your bundle is packaged correctly:

解决办法:

在app/src/main目录下新建assets文件夹,然后启动cmd,回到项目根目录执行

react-native bundle --platform android --dev false --entry-file index.android.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res

react-native服务没有启动

解决方法:

在terminal里面执行npm start启动react-native服务

React-Native集成方案

一种方式是在原有布局中添加com.facebook.react.ReactRootView,可以参考现有Android项目引入ReactNative--九步大法

但是这种方式的话,不管是通过adb的命令还是摇晃手机都无法调出DevSettingActivity的设置界面,无法修改真机调试的ip,涉及到修改配置脚本,较为麻烦,推荐使用Activity继承ReactActivity的方式加载React-Native页面。


public class XXXActivity extends ReactActivity{

@Override

protected String getMainComponentName() {

return "XXX";

}

在项目中引入React-Native中常见错误:
Cannot resolve ReactApplication或者ReactNativeHost。
解决办法:

allprojects {
    repositories {
        mavenLocal()
        jcenter()
        maven {
            url 'http://maven.aliyun.com/nexus/content/groups/public/'//用国内aliyun、不用google
        }

        maven {
            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
            url "$rootDir/node_modules/react-native/android"
        }
        google()
    }
}

rootDir/node_modules/react-native/android路径配置错误 ,很多教程把这个路径配置成url "rootDir/../node_modules/react-native/android",这个路径要配置成根目录和node_modules之间的正确路径,当前项目的真实路径是怎样的就该怎样配置。否则会出现加载错误的react-native的本地库,导致ReactApplication解析错误。

代码改造

完整的js代码如下。

import React, {Component} from 'react';
import {Platform, StyleSheet, Text, View,Image,Dimensions,PixelRatio,NativeModules,DeviceEventEmitter,Alert,TouchableWithoutFeedback,
} from 'react-native';
const deviceWidth = Dimensions.get('window').width;
const deviceHeight = Dimensions.get('window').height;

const px2dp = px=>PixelRatio.roundToNearestPixel(px);
const instructions = Platform.select({
  ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',
  android:
    'Double tap R on your keyboard to reload,\n' +
    'Shake or press menu button for dev menu',
});

type Props = {};
export default class LmsaHomePage extends Component<Props> {
constructor(props) {
    super();
    this.state = {
        connect_img: require('.././image/connect_disconnected.png'),
        connect_text:'Device Disconnected',
        connect_color:'#6D6D6D',
        model_name:'Welcome',
    };

}


componentWillMount(){
    DeviceEventEmitter.addListener('LmsaConnect',this.handleConnectMessage);
    DeviceEventEmitter.addListener('LmsaModelName',this.handleModelMessage);
  }

  componentWillunMount(){
     DeviceEventEmitter.remove('LmsaConnect',this.handleConnectMessage);
     DeviceEventEmitter.remove('LmsaModelName',this.handleModelMessage);
  }
  handleConnectMessage=(msg)=>{
       //RN端获得native端传递的数据
       if(msg=='USB_CONNECTING'||msg=='USB_CONNECED')
       {
       /*this.refs.ref_connect_img.setNativeProps({
                                            *//*source: {require('.././image/new_version_usb.png')}*//*style:{width:200,}
                                        });*/
           this.setState({
                           connect_img: require('.././image/new_version_usb.png'),
                           connect_text:msg,
                           connect_color:'#04E861',
                       });
       }
  }
  handleModelMessage=(msg)=>{
       //RN端获得native端传递的数据
      this.setState({
                                 model_name: msg,
                                 connect_text: 'USB_CONNECTING',
                                 connect_color:'#04E861',
                             });
  }
  goToScanActivity() {
         //alert('gotoscan');
         NativeModules.homePageReactNativeModule.scanToConnect(phone);
  }
  render() {
    return (
      <View style={styles.container}>
        <View style={styles.topframe}>
        <Image source={require('.././image/home_page_top_bg_new.png')}
         style={styles.topframeBg}
         />
         <Image source={require('.././image/home_page_lmsa_log.png')}
         style={styles.topframeLogo}
         />
         <Text style={{position:'absolute',fontSize:deviceWidth*0.04,color:'#FFFFFF',marginTop:deviceHeight*0.17}}>{this.state.model_name}</Text>
         
        </View>
        <Image source={this.state.connect_img/*require('.././image/connect_disconnected.png')*/}
         style={styles.topframeConnect} ref={'ref_connect_img'}/*{(c) => this.ref_connect_img = c}*/
         />
         <Text style={{position:'absolute',fontSize:deviceWidth*0.042,color:this.state.connect_color,marginTop:deviceHeight*0.2428,marginLeft:deviceWidth*0.346}}>{this.state.connect_text}</Text>
         <Image source={require('.././image/home_page_connect_bg.png')}
         style={styles.topframeConnectBg}
         />
         <TouchableWithoutFeedback
             onPress={()=> {
                  NativeModules.homePageReactNativeModule.scanToConnect();
             }}
         >
         <Image source={require('.././image/home_page_scan_30.png')}
         style={styles.topframeConnectScan}
         />
         </TouchableWithoutFeedback>
         <Text style={{position:'absolute',fontSize:deviceWidth*0.035,color:'#3E8DDC',marginTop:deviceHeight*0.316,marginLeft:deviceWidth*0.42}}>Scan to Connect</Text>
        <View style={styles.mainframe}>
            <View style={styles.mainframe_colomn}>
                <View style={styles.mainframe_colomn_item}><Image source={require('.././image/home_page_new_version_device_info.png')} style={styles.mainframe_colomn_item_image}/>
                <Text style={{fontSize:deviceWidth*0.04,color:'#3E8DDC',marginTop:deviceHeight*0.046}}>Device Info.</Text></View>
                <View style={styles.mainframe_colomn_item}><Image source={require('.././image/home_page_new_version_cpu_info.png')} style={styles.mainframe_colomn_item_image}/>
                <Text style={{fontSize:deviceWidth*0.04,color:'#3E8DDC',marginTop:deviceHeight*0.046}}>CPU Info.</Text></View>
            </View>
            <View style={styles.mainframe_colomn}>
                <View style={styles.mainframe_colomn_item}><Image source={require('.././image/home_page_new_version_hw_detection.png')} style={styles.mainframe_colomn_item_image}/>
                <Text style={{fontSize:deviceWidth*0.04,color:'#3E8DDC',marginTop:deviceHeight*0.046}}>HW Detection</Text></View>
                <View style={styles.mainframe_colomn_item}><Image source={require('.././image/home_page_new_version_rom_clean.png')} style={styles.mainframe_colomn_item_image}/>
                <Text style={{fontSize:deviceWidth*0.04,color:'#3E8DDC',marginTop:deviceHeight*0.046}}>ROM CleanwIII</Text></View>
            </View>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    
    backgroundColor: '#F5FCFF',
  },
  welcome: {
    fontSize: 20,
    textAlign: 'center',
    margin: 10,
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5,
  },
  topframe: {
    flex:4,
    
    alignItems: 'center',
  },
  mainframe: {
    flex:6,
    justifyContent: 'center',
    alignItems: 'center',   
    backgroundColor:'rgba(0,0,0,0)',
    flexDirection: 'column',
  },
  topframeBg: {
      flex:1,
        alignItems:'center',
        justifyContent:'center',
       resizeMode :'contain',
        
        
        backgroundColor:'rgba(0,0,0,0)',
  },
  topframeLogo: {
    alignItems:'center',
    justifyContent:'center',
    position:'absolute',
     marginTop:deviceHeight*0.053,
      width:deviceWidth*0.158,
      height:deviceWidth*0.158,
  },
  topframeConnect: {
     
    position:'absolute',
     marginTop:deviceHeight*0.24,
     marginLeft:deviceWidth*0.26,
      width:deviceWidth*0.07,
      height:deviceWidth*0.07,
  },
  topframeConnectBg: {
      position:'absolute',
     marginTop:deviceHeight*0.298,
     alignItems:'center',
     marginLeft:deviceWidth*0.3,
     justifyContent:'center',
      width:deviceWidth*0.405,
      height:deviceWidth*0.104,
  },
  topframeConnectScan: {
       position:'absolute',
     marginTop:deviceHeight*0.312,
     alignItems:'center',
     marginLeft:deviceWidth*0.335,
     justifyContent:'center',
      width:deviceWidth*0.063,
      height:deviceWidth*0.063,
  },
  mainframe_colomn: {
    flex:1,  
    flexDirection: 'row',
  },
  mainframe_colomn_item: {
      flex:1,
      alignItems:'center',
      
      flexDirection: 'column',
      backgroundColor:'rgba(0,0,0,0)',
      borderWidth: 0.3,
      borderTopWidth: 0,
      borderLeftWidth: 0,
      borderColor:'#7C7C7C', 
  },
  mainframe_colomn_item_image: {
      
      alignItems:'center',
     
      flexDirection: 'column',
      backgroundColor:'rgba(0,0,0,0)',
       
      marginTop:deviceHeight*0.095,
      width:deviceWidth*0.127,
      height:deviceWidth*0.127,
  },
});

代码主要做了三件事,1完成首页的js布局;2接受native发送过来连接信息改变text文字和图片;3点击扫描图标,调用native方法跳转本地扫码界面。
js的布局主要根据当前的屏幕宽度和高度对每个元素的宽高属性进行设置,保证机型适配;native页面于react-native之间的数据交互原理可以参考React-Native数据交互
最终效果图如下

效果图.gif

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,568评论 25 707
  • 用两张图告诉你,为什么你的 App 会卡顿? - Android - 掘金 Cover 有什么料? 从这篇文章中你...
    hw1212阅读 12,473评论 2 59
  • 评论——回复评论:走同一个编辑框 recycleAdapter.setOnItemClickListener(ne...
    自然之秋阅读 401评论 0 0
  • 源码 参考链接 http://www.cnblogs.com/52cik/p/js-regexp-s.html h...
    lousoandso阅读 162评论 0 0
  • 姓名: 刘威 公司:瑞亨电子 365期感谢二组学员 【日精进打卡第25天】 【知~学习】 ...
    刘威356期学员阅读 110评论 0 0