本文基于 React Native 的实践项目进行总结, 该项目基于 React Native 和 H5 在开发效率、功能性能、用户体验等方面的差别性,对功能模块进行精心设计,主要基于咱们如今实际项目的业务,结合移动端特有的特性。javascript
本文围绕 React Native 项目的环境配置、运行,React Native 介绍,项目的主要功能,React Native 开发存在的坑等多个方面进行展开。若是你尚未 React Native 开发经验,那么这篇文章将很好的向你展现 React Native 的各方面,包括官方文档、生态、兼容性等等,但愿你在这篇文章中找到你想要的答案。html
辛苦整理良久,还望手动点赞鼓励~java
博客 github地址为:github.com/fengshi123/… ,汇总了做者的全部博客,也欢迎关注及 star ~node
本项目 github 地址为:github.com/fengshi123/…react
配套的服务端 express 项目 github 地址为:github.com/fengshi123/…android
在这个 React Native App 开发中,个人开发环境相关配置以下:ios
工具名称 | 版本号 |
---|---|
node.js | 11.12.0 |
npm | 6.7.0 |
yarn | 1.17.3 |
Android Studio | 3.4.1 |
JDK | 1.8 |
react | 16.8.6 |
react-native | 0.60.5 |
(1)安装 yarn、react-native 命令行工具git
$ npm install -g yarn react-native-cli
复制代码
(2)设置 yarn 镜像源github
$ yarn config set registry https://registry.npm.taobao.org --global
$ yarn config set disturl https://npm.taobao.org/dist --global
复制代码
(3)安装第三方插件web
进入到 react_native_project 目录底下,安装第三方插件:
$ yarn
复制代码
(4)Android Studio 配置
Android Studio 的配置这里再也不作介绍,能够参考 react-native 官网;
(5)编译并运行项目
$ react-native run-android
复制代码
(6)启动项目
第 5 步后,若是真机或模拟器提示,Metro 没有启动,可关闭第 5 步开启的 node 窗口,再重启 Metro:
npm start
复制代码
(7)服务端配套项目
记得 clone 本项目配套的服务端 express 项目,并启动它。
“ Learn once, write anywhere ”,React Native 的定义就像是:学习 React ,同时掌握 web 与 app 两种开发技能。 React Native 使用 React 的设计模式,开发者编写 js 代码,经过 React Native 的中间层转化为原生控件和操做,拥有接近原生开发的用户体验。下面引用官网上 4 条特性:
(1)使用 JavaScript 和 React 编写原生移动应用
React Native 使你只使用 JavaScript 也能编写原生移动应用。 它在设计原理上和 React 一致,经过声明式的组件机制来搭建丰富多彩的用户界面。
(2)React Native 应用是真正的移动应用
React Native 产出的并非“网页应用”, 或者说“HTML5应用”,又或者“混合应用”。 最终产品是一个真正的移动应用,从使用感觉上和用 Objective-C 或 Java 编写的应用相比几乎是没法区分的。 React Native 所使用的基础 UI 组件和原生应用彻底一致。 你要作的就是把这些基础组件使用 JavaScript 和 React 的方式组合起来。
(3)别再傻等编译了
React Native 让你能够快速迭代开发应用。 比起传统原生应用漫长的编译过程,如今你能够在瞬间刷新你的应用。开启 Hot Reloading 的话,甚至能在保持应用运行状态的状况下热替换新代码!
(4)可随时呼叫原生外援
React Native 完美兼容使用 Objective-C、Java 或是 Swift 编写的组件。 若是你须要针对应用的某一部分特别优化,中途换用原生代码编写也很容易。 想要应用的一部分用原生,一部分用 React Native 也彻底没问题。
考虑到更好的体验 React Native 和 H5 在开发效率、功能性能、用户体验等方面的差别性,咱们对功能模块进行精心设计,主要基于咱们如今实际项目的业务,结合移动端特有的特性。相关的模块功能设计以下图所示。
截取一些功能展现以下:
咱们的项目目录结构以下:
> ├─ .vscode 编辑器配置
> ├─ android android 原生目录
> ├─ ios ios 原生目录
> ├─node_modules 项目依赖包
> ├─ src 代码主目录
> │ ├─assets 存放样式文件
> │ │ ├─images 存放图片
> │ │ └─styles 样式文件的 js 目录
> │ │ ├─index.js 存放图片路径,能够参照主页面模块写法
> │ ├─components 存放块级组件
> │ ├─navigation 存放导航配置
> │ │ ├─ index.js 导航配置主文件
> │ ├─pages 存放页面级组件,不一样模块不一样目录
> │ └─utils 存放工具方法
> │ │ ├─ constant.js 一些常量配置,例如:服务器 IP 端口等
> │ │ ├─ globalVar.js 一些全局变量
> │ │ └─ request.js ajax 请求
> ├─.eslintrc.js eslint 配置
> ├─.gitignore.js git 忽略配置
> ├─index.js 项目入口
> ├─package.json 项目依赖包配置
复制代码
此模块包含功能:文件夹建立、重命名、文件上传、下载、侧滑操做、长按列表操做、下拉刷新操做、文件预览(包含图片)等。
(1) 使用插件
react-native-popup-menu
复制代码
(2)功能实现
yarn add react-native-popup-menu
复制代码
在 react_native_project/src/pages/netDisk/NetDisk.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import {
Menu,
MenuProvider,
MenuOptions,
MenuOption,
MenuTrigger,
} from 'react-native-popup-menu';
// render
<MenuProvider>
<Menu>
<MenuTrigger
onAlternativeAction={() => this.getDirFile(rowData.item)}
triggerOnLongPress={true}
customStyles={triggerStyles}>
<Image
source={ rowData.item.icon }
style={styles.thumbnail}
/>
<View>
<Text>{rowData.item.name}</Text>
<Text>{dayjs(rowData.item.time).format('YYYY-MM-DD HH:mm:ss')}</Text>
</View>
<View>
{
rowData.item.type === 'dir'?
<NBIcon type="AntDesign" name="right"/> : null
}
</View>
</MenuTrigger>
<MenuOptions customStyles={optionsStyles}>
<MenuOption value={1} text='重命名' onSelect={() => {this.setState({
modalVisible: true,
fileItem: rowData.item,
dialogType: 'Rename',
hasInputText: true,
inputVal: rowData.item.name,
isSideSlip: false
});}}/>
<MenuOption value={2} text='删除' onSelect={() => {
this.setState({
modalVisible: true,
fileItem: rowData.item,
dialogType: 'Delete',
confirmText: '肯定删除?',
hasInputText: false,
isSideSlip: false
});
}}/>
<MenuOption value={3} text='下载'
onSelect={() => this.downloadFile(rowData.item)} disabled={rowData.item.type === 'dir'}/>
</MenuOptions>
</Menu>
</MenuProvider>
复制代码
(3)注意事项
(4)参考文档
(1)使用插件
react-native-swipe-list-view
复制代码
(2)功能实现
yarn add react-native-swipe-list-view
复制代码
在 react_native_project/src/pages/netDisk/NetDisk.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import { SwipeListView } from 'react-native-swipe-list-view/lib/index';
// render
<SwipeListView
style={styles.list}
data={this.state.filesList}
renderItem={ (rowData) => (
<TouchableHighlight style={styles.rowFront} underlayColor={'#AAA'} > <View style={{flexDirection:'row',flex: 1,alignItems:'center'}}> <Text>{rowData.item.name}</Text> </View> </TouchableHighlight>
)}
renderHiddenItem={ (rowData, rowMap) => {
return (
<View style={styles.standaloneRowBack} key={rowData.item.time}> <NbButton style={[styles.backRightBtn, styles.backRightBtnLeft]} onPress={() =>{ this.setState({ modalVisible: true, fileItem: rowData.item, fileIndex: rowData.item.key, fileRowMap: rowMap, dialogType: 'Rename', hasInputText: true, inputVal: rowData.item.name, isSideSlip: true }); }}> <Text style={styles.backTextWhite}>重命名</Text> </NbButton> <NbButton style={[styles.backRightBtn, styles.backRightBtnRight]} onPress={() => { this.setState({ modalVisible: true, fileItem: rowData.item, fileIndex: rowData.item.key, fileRowMap: rowMap, dialogType: 'Delete', confirmText: '肯定删除?', hasInputText: false, isSideSlip: true }); }}> <Text style={styles.backTextWhite}>删除</Text> </NbButton> </View>
);}
}
rightOpenValue={-150}
stopRightSwipe={-150}
disableRightSwipe={true}
swipeToOpenPercent={20}
swipeToClosePercent={0}
/>
复制代码
(3)注意事项
// 关闭侧滑
closeRow(rowMap, rowKey) {
if (rowMap[rowKey]) {
rowMap[rowKey].closeRow();
}
}
复制代码
(4)参考文档
(1) 使用插件
rn-fetch-blob
复制代码
(2)功能实现
yarn add rn-fetch-blob
复制代码
由于该插件涉及到 Android 原生功能,因此配置完该插件,须要从新编译 Android。
在 react_native_project/src/pages/netDisk/NetDisk.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import RNFetchBlob from 'rn-fetch-blob';
// 下载方法
async actualDownload(item) {
let dirs = RNFetchBlob.fs.dirs;
const android = RNFetchBlob.android;
RNFetchBlob.config({
fileCache : true,
path: `${dirs.DownloadDir}/${item.name}`,
// android only options, these options be a no-op on IOS
addAndroidDownloads : {
// Show notification when response data transmitted
notification : true,
// Title of download notification
title : '下载完成',
// File description (not notification description)
description : 'An file.',
mime : getMimeType(item.name.split('.').pop()),
// Make the file scannable by media scanner
mediaScannable : true,
}
})
.fetch('GET', `${CONSTANT.SERVER_URL}${item.path}`)
.then(async(res) => {
await android.actionViewIntent(res.path(), getMimeType(item.name.split('.').pop()));
});
}
复制代码
(3)注意事项
// 问题
So basically this needs to be added to line 122-123 of file android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:
// 解决办法
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
If above is not working do to the below step: overwrite the 121 line in android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 121 line
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 122 line
复制代码
(4)参考文档
(1)使用插件
// 获取本机文件
react-native-file-selector
复制代码
(2)功能实现
yarn add react-native-file-selector
复制代码
由于该插件涉及到 Android 原生功能,因此添加完该插件,须要从新编译 Android。
在 react_native_project/src/pages/netDisk/NetDisk.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import RNFileSelector from 'react-native-file-selector';
// 选择文件并上传
RNFileSelector.Show(
{
title: '请选择文件',
onDone: (filePath) => {
let data = new FormData();
let file = { uri: 'file://' + filePath, type: 'multipart/form-data', name: escape(path.basename(filePath))};
data.append('file', file);
let options = {
url: '/files/uploadFile', // 请求 url
data: data,
tipFlag: true, // 默认统一提示,若是须要自定义提示,传入 true
};
request(options).then(async (res) => {
if (res.status == 200) {
await this.fetchData();
ToastAndroid.show(
'上传成功',
ToastAndroid.SHORT,
ToastAndroid.CENTER
);
}
});
},
onCancel: () => {
ToastAndroid.show(
'取消上传',
ToastAndroid.SHORT,
ToastAndroid.CENTER
);
}
}
);
复制代码
(3)注意事项
(4)参考文档
(1) 使用插件
react-native-doc-viewer
复制代码
(2)功能实现
yarn add react-native-doc-viewer
复制代码
由于该插件涉及到 Android 原生功能,因此添加完该插件,须要从新编译 Android。
在 react_native_project/src/pages/netDisk/NetDisk.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import OpenFile from 'react-native-doc-viewer';
// 文件预览
OpenFile.openDoc([{
url: `${CONSTANT.SERVER_URL}${item.path}`,
fileName: item.name.split('.').shift(),
cache: false,
fileType: item.name.split('.').pop()
}], (error) => {
if (error) {
this.setState({ animating: false });
console.log(error);
ToastAndroid.show('请先安装相关应用软件', ToastAndroid.SHORT);
} else {
this.setState({ animating: false });
// ToastAndroid.show('该文件不支持预览', ToastAndroid.SHORT);
}
});
复制代码
(3)注意事项
node_modules/react-native-doc-viewer/android/src/main/java/com/reactlibrary/RNReactNativeDocViewerModule.java
文件中 删除 import com.facebook.react.views.webview.ReactWebViewManager;
(4)参考文档
(1) 使用插件
react-native-image-zoom-viewer
复制代码
(2)功能实现
react-native-image-zoom-viewer
复制代码
在 react_native_project/src/pages/netDisk/NetDisk.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import ImageViewer from 'react-native-image-zoom-viewer';
// 图片预览方法
saveImg(url) {
let promise = CameraRoll.saveToCameraRoll(url);
promise.then((result) => {
console.log(result);
ToastAndroid.show('已保存到相册', ToastAndroid.SHORT);
}).catch((error) => {
console.log(error);
ToastAndroid.show('保存失败', ToastAndroid.SHORT);
});
}
// render
<Modal
transparent={true}
visible={imgModalVisible}
onRequestClose={() => this.props.closeImg()}>
<ImageViewer onCancel={()=> this.props.closeImg()} onClick={(onCancel) => {onCancel();}} onSave={(url) => this.saveImg(url)} saveToLocalByLongPress={true} imageUrls={images} index={imgIndex} doubleClickInterval={1000} menuContext={{ 'saveToLocal': '保存到相册', 'cancel': '取消' }}/> </Modal>
复制代码
(3)注意事项
(4)参考文档
此模块包含功能:音/视频上传、下载、删除、判断网络、播放、全屏播放、转向全屏播放、评论、分享等功能,其中上传、下载、删除功能在网盘模块和试题模块已说明。
(1)使用插件
react-native-video
复制代码
(2)功能实现
yarn add react-native-video
复制代码
由于该插件涉及到 Android 原生功能,因此添加完该插件,须要从新编译 Android。
在 react_native_project/src/pages/video/VideoPlayer.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import Video from 'react-native-video';
// 视频进度时间方法
function formatTime(second) {
let h = 0, i = 0, s = parseInt(second);
if (s > 60) {
i = parseInt(s / 60);
s = parseInt(s % 60);
}
// 补零
let zero = function (v) {
return (v >> 0) < 10 ? '0' + v : v;
};
return [zero(h), zero(i), zero(s)].join(':');
}
// render
// 自带参数和方法请看 api
<Video
ref={(ref) => this.videoPlayer = ref}
source={{uri: CONSTANT.SERVER_URL + '/' + this.state.videoUrl}}
rate={this.state.playRate}
volume={this.state.volume}
muted={this.state.isMuted}
paused={!this.state.isPlaying}
resizeMode={'contain'}
playWhenInactive={false}
playInBackground={false}
ignoreSilentSwitch={'ignore'}
progressUpdateInterval={250.0}
onLoadStart={this._onLoadStart}
onLoad={this._onLoaded}
onProgress={this._onProgressChanged}
onEnd={this._onPlayEnd}
onError={this._onPlayError}
onBuffer={this._onBuffering}
style={{ width: this.state.videoWidth, height: this.state.videoHeight}}
/>
复制代码
(3)参考文档
(1) 使用插件
react-native-orientation
复制代码
(2)功能实现
yarn add react-native-orientation
复制代码
由于该插件涉及到 Android 原生功能,因此添加完该插件,须要从新编译 Android。
在 react_native_project/src/pages/video/VideoPlayer.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import Orientation from 'react-native-orientation';
// 点击工具栏上的全屏按钮
onControlShrinkPress() {
if (this.state.isFullScreen) {
Orientation.lockToPortrait();
} else {
Orientation.lockToLandscapeRight();
}
}
// 屏幕旋转时宽高会发生变化,能够在onLayout的方法中作处理,比监听屏幕旋转更加及时获取宽高变化
_onLayout = (event) => {
//获取根View的宽高
let {width, height} = event.nativeEvent.layout;
// 通常设备横屏下都是宽大于高,这里能够用这个来判断横竖屏
let isLandscape = (width > height);
if (isLandscape && !this.showKeyboard){
this.setState({
videoWidth: width,
videoHeight: height,
isFullScreen: true,
});
} else {
this.setState({
videoWidth: width,
videoHeight: width * 9/16,
isFullScreen: false,
});
}
Orientation.unlockAllOrientations();
};
复制代码
(3)参考文档
(1) 使用插件
react-native-wechat
复制代码
(2)功能实现
yarn add react-native-wechat
复制代码
由于该插件涉及到 Android 原生功能,因此添加完该插件,须要从新编译 Android。
在 react_native_project/src/components/video/VideoShare.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import * as WeChat from 'react-native-wechat';
// const wxAppId = ''; // 微信开放平台注册的app id
// const wxAppSecret = ''; // 微信开放平台注册获得的app secret
// WeChat.registerApp(wxAppId);
// 分享
shareItemSelectedAtIndex(index) {
// this.props.onShareItemSelected && this.props.onShareItemSelected(index);
WeChat.isWXAppInstalled().then((isInstalled) => {
this.setState({
isWXInstalled: isInstalled
});
if (isInstalled && index === 0) {
WeChat.shareToSession({
title: this.state.videoTitle,
type: 'video',
videoUrl: CONSTANT.SERVER_URL + '/' + this.state.videoUrl
}).catch((error) => {
console.log(error.message);
});
} else if (isInstalled && index === 1) {
WeChat.shareToTimeline({
title: this.state.videoTitle,
type: 'video',
videoUrl: CONSTANT.SERVER_URL + '/' + this.state.videoUrl
}).catch((error) => {
console.log(error.message);
});
} else {
console.log('微信未安装');
}
});
}
复制代码
(3)参考文档
(1)使用插件
react-native-image-crop-picker
复制代码
(2)功能实现
yarn add react-native-image-crop-picker
复制代码
由于该插件涉及到 Android 原生功能,因此添加完该插件,须要从新编译 Android。
在 react_native_project/src/components/exam/ImageAudioTab.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import ImagePicker from 'react-native-image-crop-picker';
// 从相册选择图片
ImagePicker.openPicker(paramObj).then(image => {
this.props.handleImage(qsIndex, image);
}).catch(err => {
console.log(err);
});
// 调用摄像头功能
openCamera(qsIndex) {
ImagePicker.openCamera({
width: 300,
height: 400,
cropping: true,
}).then(image => {
this.props.handleImage(qsIndex, image);
}).catch(err => {
console.log(err);
});
}
复制代码
(3)注意事项
(4)参考文档
(1) 使用插件
react-native-audio // 语音录入
react-native-sound // 语音播放
react-native-spinkit // 动画效果
复制代码
(2)功能实现
yarn add react-native-audio react-native-sound react-native-spinkit
复制代码
由于语音录入插件涉及到 Android 原生功能,因此添加完该插件,须要从新编译 Android。
在 react_native_project/src/components/exam/ImageAudioTab.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import { AudioRecorder, AudioUtils } from 'react-native-audio';
import Sound from 'react-native-sound';
import Spinkit from 'react-native-spinkit';
// 音频路径配置
prepareRecordingPath = (path) => {
const option = {
SampleRate: 44100.0, //采样率
Channels: 2, //通道
AudioQuality: 'High', //音质
AudioEncoding: 'aac', //音频编码
OutputFormat: 'mpeg_4', //输出格式
MeteringEnabled: false, //是否计量
MeasurementMode: false, //测量模式
AudioEncodingBitRate: 32000, //音频编码比特率
IncludeBase64: true, //是不是base64格式
AudioSource: 0, //音频源
};
AudioRecorder.prepareRecordingAtPath(path, option);
}
// 开始录音
startSoundRecording(qsIndex, stemAudio) {
if (stemAudio.length >= 5) {
ToastAndroid.show('每道题最多 5 段语音哦', ToastAndroid.SHORT);
return;
}
console.log('startSoundRecording....');
// 请求受权
AudioRecorder.requestAuthorization()
.then(isAuthor => {
if (isAuthor) {
this.prepareRecordingPath(this.audioPath + qsIndex + '_' + stemAudio.length + '.aac');
// 录音进展
AudioRecorder.onProgress = (data) => {
this.recordTime = Math.floor(data.currentTime);
};
// 完成录音
AudioRecorder.onFinished = (data) => {
// data 返回须要上传到后台的录音数据;
this.isRecording = false;
if (!this.recordTime) {
ToastAndroid.show('录音时间过短...', ToastAndroid.SHORT);
return;
}
this.props.handleAudio(qsIndex, data.audioFileURL, this.recordTime);
// 重置为 0
this.recordTime = 0;
};
// 录音
AudioRecorder.startRecording();
this.isRecording = true;
}
});
}
// 结束录音
stopSoundRecording() {
console.log('stopSoundRecording....');
// 已经被节流操做拦截,没有在录音
if (!this.isRecording) {
return;
}
AudioRecorder.stopRecording();
}
// 播放录音
playSound(qsIndex, index, stemAudio, audioFlag, path) {
this.props.changeAudioState(qsIndex, index, 2);
let whoosh = new Sound(path.slice(7), '', (err) => {
if (err) {
return console.log(err);
}
whoosh.play(success => {
if (success) {
console.log('success - 播放成功');
} else {
console.log('fail - 播放失败');
}
this.props.changeAudioState(qsIndex, index, 1);
});
});
}
复制代码
(3)注意事项
(4)参考文档
(1) 使用插件
victory-native // 图标绘制插件
react-native-svg // svg 图片绘制
复制代码
(2)功能实现
yarn add victory-native react-native-svg
复制代码
由于该插件涉及到 Android 原生功能,因此添加完该插件,须要从新编译 Android。
在 react_native_project/src/pages/exam/ResultStatistics.js
组件中实现相应逻辑,关键代码及注释以下:
// 插件引入
import {
VictoryPie,
VictoryLegend,
VictoryTooltip
} from 'victory-native';
// 图形绘制组件使用
<VictoryLegend
orientation="vertical"
data={[
{
name: '不及格 < 60 分',
symbol: { fill: colorScale[0], type: 'square' },
},
{
name: '及格 60 - 75 分',
symbol: { fill: colorScale[1], type: 'square' },
},
{
name: '良好 75 - 85 分',
symbol: { fill: colorScale[2], type: 'square' },
},
{
name: '优秀 > 85 分',
symbol: { fill: colorScale[3], type: 'square' },
}
]}
width={180}
height={125}
/>
<VictoryPie
colorScale={colorScale}
data={[
{ y: this.state.result[3], label: '不及格:' + this.state.result[3] + '人'},
{ y: this.state.result[2], label: '及格:' + this.state.result[2] + '人' },
{ y: this.state.result[1], label: '良好:' + this.state.result[1] + '人' },
{ y: this.state.result[0], label: '优秀:' + this.state.result[0] + '人' }
]}
innerRadius={60}
height={300}
width={345}
animate={{
duration: 2000
}}
labelComponent={
<VictoryTooltip
active={({ datum }) => datum.y === 0 ? false : true}
constrainToVisibleArea={true}
flyoutHeight={30}
flyoutStyle={{ strokeWidth: 0.1}}
/>
}
/>
复制代码
(3)注意事项
(4)参考文档
(1)使用插件
Linking // react native 自带的插件
复制代码
(2)功能实现
在 react_native_project/src/components/user/ListItem.js
组件中实现相应逻辑,关键代码及注释以下:
// 拨打电话功能 or 短信功能
call(flag) {
let tel = flag === 1 ? 'tel:10086' : 'smsto:10086';
Linking.canOpenURL(tel).then(supported => {
if (!supported) {
ToastAndroid.show.show('您未受权通话和短信权限');
} else {
return Linking.openURL(tel);
}
}).catch(err => console.error('An error occurred', err));
}
复制代码
(3)注意事项
(4)参考文档
(1) 使用插件
(2)功能实现
在 react_native_project/android/app/src/main/java/com/react_native_project/module
目录中建立实现类 LocationModule.java,须要注意的是这个类须要实现 ReactContextBaseJavaModule 这个类:
public class LocationModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext mContext;
public LocationModule(ReactApplicationContext reactContext) {
super(reactContext);
mContext = reactContext;
}
/** * @return js调用的模块名 */
@Override
public String getName() {
return "LocationModule";
}
/** * 使用ReactMethod注解,使这个方法被js调用 */
@ReactMethod
public void getLocation(Callback locationCallback) {
// 省略一些逻辑实现 ...
locationCallback.invoke(lat,lng,country,locality);
}else{
locationCallback.invoke(false);
}
}
}
复制代码
对刚刚实现定位功能的模块进行注册,在 react_native_project/android/app/src/main/java/com/react_native_project/module
目录中建立注册包管理类 LocationReactPackage .java,相关逻辑以下:
public class LocationReactPackage implements ReactPackage {
/** * @param reactContext 上下文 * @return 须要调用的原生控件 */
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
/** * @param reactContext 上下文 * @return 须要调用的原生模块 */
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new LocationModule(reactContext));
return modules;
}
}
复制代码
在 react_native_project/android/app/src/main/java/com/react_native_project/MainApplication.java
中添加包管理类,相关逻辑以下:
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new LocationReactPackage());
return packages;
}
复制代码
咱们在 react_native_project/src/components/user/ListItem.js
组件中实现相应逻辑,关键代码及注释以下:
import { NativeModules } from 'react-native';
// 获取地理位置
showLocation() {
NativeModules.LocationModule.getLocation((lat, lng, country, locality) => {
let str = '获取位置信息失败,您可能手机位置信息没有开启!';
if (lat && lng) {
str = country + ',' + locality + ',纬度:' + lat + ',' + '经度:' + lng;
}
ToastAndroid.show(str, ToastAndroid.SHORT);
});
}
复制代码
(3)注意事项
(4)参考文档
(1) 使用插件
rn-fetch-blob
复制代码
(2)功能实现
yarn add rn-fetch-blob
复制代码
由于该插件涉及到 Android 原生功能,因此添加完该插件,须要从新编译 Android。
咱们实如今线升级功能的大概逻辑是,在 app 管理端上传 apk 安装包,而后点击发布,这时服务端会经过 websocket 将最新发布的版本号通知 app,app 收到最新版本号,会跟当前的 app 版本比较,若是当前版本号小于最新版本号,则会弹窗提示有最新版本,询问用户是否下载安装,用户若是确认安装最新版本,则会从服务器下载最新的 apk,并进行安装。在 react_native_project/src/components/user/ListItem.js
组件中实现相应逻辑,关键代码及注释以下:
import RNFetchBlob from 'rn-fetch-blob';
checkUpdate = () => {
const android = RNFetchBlob.android;
//下载成功后文件所在path
const downloadDest = `${ RNFetchBlob.fs.dirs.DownloadDir }/app_release.apk`;
RNFetchBlob.config({
//配置手机系统通知栏下载文件通知,下载成功后点击通知可运行apk文件
addAndroidDownloads: {
useDownloadManager: true,
title: 'RN APP',
description: 'An APK that will be installed',
mime: 'application/vnd.android.package-archive',
path: downloadDest,
mediaScannable: true,
notification: true
}
}).fetch(
'GET',
CONSTANT.SERVER_URL+'/appVersion/download?path='+this.newVersionInfo.path
).then(res => {
//下载成功后自动打开运行已下载apk文件
android.actionViewIntent(
res.path(),
'application/vnd.android.package-archive'
);
});
}
复制代码
(3)注意事项
(4)参考文档
4.一、运行 react-native run-android 出现错误:Task :app:mergeDebugAssets FAILED OR Task :app:processDebugResources FAILED 。
解决:
cd android && ./gradlew clean
cd .. && react-native run-android
复制代码
4.二、若是手机真机出现链接不上开发开发服务器的状况。
解决:
命令窗口运行如下命令:
adb reverse tcp:8081 tcp:8081
复制代码
4.三、kotlin 相关 jar 包没法下载。
解决:
对应的插件的 android/build.gradle 配置阿里云仓库(例如遇到这个问题时,是在插件 react-native-webview)
// Maven中心仓库墙内版
maven { url "https://maven.aliyun.com/repository/central" }
// jCenter中心仓库墙内版
maven { url "https://maven.aliyun.com/repository/jcenter" }
maven{url 'http://maven.aliyun.com/nexus/content/groups/public/'}
复制代码
4.四、文件预览插件:react-native-doc-viewer安装完 run-android 编译失败。
解决:
Could be fixed by removing the import in node_modules/react-native-doc-viewer/android/src/main/java/com/reactlibrary/RNReactNativeDocViewerModule.java
Remove the ununsed import:
import com.facebook.react.views.webview.ReactWebViewManager;
复制代码
4.五、第三方插件 rn-fetch-blob 下载文档没法打开。
解决:
So basically this needs to be added to line 122-123 of file android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
If above is not working do to the below step: overwrite the 121 line in android/src/main/java/com/RNFetchBlob/RNFetchBlob.java:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); // 121 line
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // 122 line
复制代码
本文主要基于 React Native 框架的实践进行总结,分享了 React Native 理念、React Native 项目的功能介绍、React Native 项目编译以及 React Native 存在的一些坑,但愿对彻底阅读完的你有启发和帮助,若是有不足,欢迎批评、指正、交流!
姐妹篇《 Weex 实践总结 》,能够进行 React Native 和 Weex 的对比。
辛苦整理良久,还望手动点赞鼓励~
博客 github地址为:github.com/fengshi123/… ,汇总了做者的全部博客,也欢迎关注及 star ~
本项目 github 地址为:github.com/fengshi123/…
配套的服务端 express 项目 github 地址为:github.com/fengshi123/…