前置说明
你好我是 无双-Joney ,好久没有发文了 下面发一下最近我正在干的一件事,从零开始 设计一个现代化的APP开发架构并实现它,项目仓库是 在文章后面
为了实现和探究ReactNative的分包技术,以及搭建一个 相对从性能上 和 技术上都比较ok 的项目架构,我打算用几篇文章 ,和大家分享一下,我在工作中实际使用的一个App 架构方案,它主要由 客户端(Android/IOS) + React Native + WebView 结合H5 的SSR 和 各种缓存和分包技术,构建而成。本文主要详细分享 RN 的拆包和自建的热更新相关的内容。⚠️ 可能包含一部分的Native 知识 ,你得先了解一下 Android(java) 和 IOS(Oc相关的一些语法),为了让大家能够更方便的看得懂 能吸收和内化知识,我把文章的脉络和连载如下图所示, 本文包含了下面这些内容
项目 | Android | IOS |
---|---|---|
依照官方进行集成 | ✅ 完成 | ✅ 完成 |
dev是否正常运行 | ✅ 完成 | ✅ 完成 |
build 一下是否正常运行 | ✅ 完成 | ✅ 完成 |
Assets 资源加载逻辑 | ✅ 完成 | ✅ 完成 |
native版本的包管理 | ✅ 完成 | ✅ 完成 |
理论知识
Android 项目如何集成RN
具体的连接见下午,我们从这里开始开始把rn 集成到android 的原生应用中,我需要解释一下,为什么不直接使用 rn 的脚手架 构建一个 完整的rn 项目呢,原因主要还是rn的完整项目牵扯的东西太多了,也不好修改,而且在我的实践中 开发一款App native 要占有较多的成分在里面,ok我们现在开始(注意,如果你需要 kotlin 来干Android 去新原文档 看就好了链接在后文
1.第一步 构建好一个初步的完整的 项目结构
$ mkdri myrnapp
$ cd myrnapp
$ mkdir android
$ mkdri ios
$ touch index.js
$ yarn init
# 一路回车
你现在的目录应该是这个样纸
myrnapp
|---android/
|---ios/
|---node_modules/
|---index.js
|---package.json
2.安装相关的依赖 和修改一下启动脚本
$ yarn add react-native
# 注意 这个时候你要看看 它的版本是多少,去package.json 中自己查看 ,下面安装react 依赖的时候一定要一一对应!高了不行低了也不行
$ yarn add react@16.2.0
.gitignore 文件 这个比较的简单,我直接从rn 脚手架创建的项目中的文档cv 过来就好了
# Dependencies
node_modules/
# Compiled output
dist
# Runtime data
database.sqlite
# Test coverage
coverage
# Logs
log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editors and IDEs
.idea
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
launch.json
# OSX
#
.DS_Store
# node.js
#
node_modules/
npm-debug.log
yarn-error.log
package-lock.json
# BUCK
buck-out/
\.buckd/
*.keystore
# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://docs.fastlane.tools/best-practices/source-control/
*/fastlane/report.xml
*/fastlane/Preview.html
*/fastlane/screenshots
.vs/
lerna-debug.log
junit.xml
test-reporter.xml
locales/
debug.log
tsconfig.tsbuildinfo
/km2/
/conf/**/*.js
tools/**/*.js
/gulpfile.js
修改一下启动脚本 package.json 中
"scripts": {
"start": "react-native start --port=8082 ",
}
- 在index.js中写一些内容,一会儿用它来测试
import React from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
class HelloWorld extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.hello}>Hello, World</Text>
</View>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center'
},
hello: {
fontSize: 20,
textAlign: 'center',
margin: 10
}
});
AppRegistry.registerComponent(
'MyReactNativeApp',
() => HelloWorld
);
- 下面的内容开始去配置我们的 原生Android 应用
首先是创建一个空的应用
然后在Android stuido中进行操作和配置 gradle (强烈建议 你应该在 原生开发的IDE中写原生的代码,而不是在vscode 中....) 我们找到模块的gradle 写入下面的内容
dependencies {
++++
// 加入jsc依赖
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "org.webkit:android-jsc:+"
}
// 在项目的 `build.gradle` 文件中为 React Native 和 JSC 引擎添加 maven 源的路径,必须写在 "allprojects" 代码块中 ,但最新的android 项目是有问题的,我们这里按下不表。一会儿处理
allprojects {
repositories {
maven {
// All of React Native (JS, Android binaries) is installed from npm
url "$rootDir/../node_modules/react-native/android"
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
...
}
...
}
加上自动连接的功能
// 前往 项目的gradle 添加 RN ‘自动连接’的功能
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
// 前往setting.gradle 也是一样
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
- 为Android项目 配置权限
<!--
AndroidManifest.xml 中把fecebook 的叠加菜单加上 、把http请求放开(原生中一般不允许http 他不安全,一般使用https,但dev 模式的rn 需要 http
-->
<uses-permission android:name="android.permission.INTERNET" />
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<!-- ... -->
<application
android:usesCleartextTraffic="true" tools:targetApi="28" >
<!-- ... -->
</application>
<!-- ... -->
- 写一些Native 代码 (JAVA
// 首先我们找到 MainActivity 这个类 把它改造一下
public Activity extends Activity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SoLoader.init(this, false);
mReactRootView = new ReactRootView(this);
List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
// 有一些第三方可能不能自动链接,对于这些包我们可以用下面的方式手动添加进来:
// packages.add(new MyReactNativePackage());
// 同时需要手动把他们添加到`settings.gradle`和 `app/build.gradle`配置文件中。
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackages(packages)
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 注意这里的MyReactNativeApp 必须对应"index.js"中的
// "AppRegistry.registerComponent()"的第一个参数
mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);
setContentView(mReactRootView);
}
// 由于 需要实现 DefaultHardwareBackBtnHandler 来处理 物理按键 所以要重写下面几个方法
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
}
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
}
}
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
// 还有一个需要注意的是 为了让“错误”就是那个RN 开发中经常看到的 红色的屏幕展示出来你需要
private final int OVERLAY_PERMISSION_REQ_CODE = 1; // 不一定是1 只是说你可以用来判断错误,做一些容错兜底逻辑
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// SYSTEM_ALERT_WINDOW permission not granted
}
}
}
mReactInstanceManager.onActivityResult( this, requestCode, resultCode, data );
}
}
- 启动
yarn start
Android 项目,请在 Android Studio 中启动它
这就是官方对于Android 如何集成RN的所有文档了,但是它没有说资源资本png 图片等.... 也没有说 android 的keystore 和build ,不过不要着急,我在后文中有详细的说明 和验证
IOS 项目如何集成RN
IOS的项目集成也相对的比较简单没有怎么多的问题 ,但是问题也不少哈哈哈,有一个问题需要特别注意,注意这个时候请确保你有一个 比较稳定的科学上网
- 配置你的IOS Native 应用
在上文的基础文件夹中,构建自己的IOS程序(请使用Xcode 去创建一个空的App),然后进入这个目录下,使用cocoaPods管理依赖(如果你电脑上没有cocaPods 请去安装一个
# 我习惯用 brew 当然你可以使用的别的
$ brew install cocoapods
然后安装官方的说法就 ,还是原来的IOS项目的根目录下 执行pod init
pod init
在生成的文件中,加入下面的依赖 Podfile 文件中
# target的名字一般与你的项目名字相
# 'node_modules'目录一般位于根目录中
# 但是如果你的结构不同,那你就要根据实际路径修改下面的`:path`
pod 'FBLazyVector', :path => "../node_modules/react-native/Libraries/FBLazyVector"
pod 'FBReactNativeSpec', :path => "../node_modules/react-native/Libraries/FBReactNativeSpec"
pod 'RCTRequired', :path => "../node_modules/react-native/Libraries/RCTRequired"
pod 'RCTTypeSafety', :path => "../node_modules/react-native/Libraries/TypeSafety"
pod 'React', :path => '../node_modules/react-native/'
pod 'React-Core', :path => '../node_modules/react-native/'
pod 'React-CoreModules', :path => '../node_modules/react-native/React/CoreModules'
pod 'React-Core/DevSupport', :path => '../node_modules/react-native/'
pod 'React-RCTActionSheet', :path => '../node_modules/react-native/Libraries/ActionSheetIOS'
pod 'React-RCTAnimation', :path => '../node_modules/react-native/Libraries/NativeAnimation'
pod 'React-RCTBlob', :path => '../node_modules/react-native/Libraries/Blob'
pod 'React-RCTImage', :path => '../node_modules/react-native/Libraries/Image'
pod 'React-RCTLinking', :path => '../node_modules/react-native/Libraries/LinkingIOS'
pod 'React-RCTNetwork', :path => '../node_modules/react-native/Libraries/Network'
pod 'React-RCTSettings', :path => '../node_modules/react-native/Libraries/Settings'
pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text'
pod 'React-RCTVibration', :path => '../node_modules/react-native/Libraries/Vibration'
pod 'React-Core/RCTWebSocket', :path => '../node_modules/react-native/'
pod 'React-cxxreact', :path => '../node_modules/react-native/ReactCommon/cxxreact'
pod 'React-jsi', :path => '../node_modules/react-native/ReactCommon/jsi'
pod 'React-jsiexecutor', :path => '../node_modules/react-native/ReactCommon/jsiexecutor'
pod 'React-jsinspector', :path => '../node_modules/react-native/ReactCommon/jsinspector'
pod 'ReactCommon/callinvoker', :path => "../node_modules/react-native/ReactCommon"
pod 'ReactCommon/turbomodule/core', :path => "../node_modules/react-native/ReactCommon"
pod 'Yoga', :path => '../node_modules/react-native/ReactCommon/yoga'
pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
end
然后在刚才的目录下 执行
# 让其去下载依赖 到IOS 项目中
pod install
在IOS Native 项目中新增 一个可见的界面,主要的目的是提供一个交互能够,在IOS native App中点击跳转到使用RN的页面中去。过程比较繁琐,无这里就不过多的说明了,下面的实践中,无将不会使用这种方式,我们先跳过这一步,(在IOS Native 代码中写一个原生的事件 写一个link ,点击之后跳转到RN的页面View中)
在Native 上我们还需要做一件事,就是把http 的安全限制去掉,这个和Android 一样没有什么好说的,在XCode 配置就好了,如果Xcode 不会,也可以去VSCode 中写代码去配置
-> 项目的 Info.plist 文件中:
+++
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
+++
- 前面都是可看做是一些前置的步骤,现在我们来看看Code 代码中如何集成
我们这里新增一个View 来承载 RN
#import <React/RCTRootView.h>
- (IBAction)highScoreButtonPressed:(id)sender {
NSLog(@"High Score Button Pressed");
NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8081/index.bundle?platform=ios"];
RCTRootView *rootView =
[[RCTRootView alloc] initWithBundleURL: jsCodeLocation
moduleName: @"RNHighScores"
initialProperties:
@{
@"scores" : @[
@{
@"name" : @"Alex",
@"value": @"42"
},
@{
@"name" : @"Joel",
@"value": @"10"
}
]
}
launchOptions: nil];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[self presentViewController:vc animated:YES completion:nil];
}
- JS的代码和上述Android 一样使用同一份,然后Metro run 起来,XCode 把IOS项目Build 出来,等他跑在模拟器中,你就能看到效果了(哈哈哈,再次强调,你并不会看到效果,因为官方的文档,现在提供的方式还有问题,真正有效果的方式,我们实践中有说明具体到底如何写,所有代码 我都会放在github上
重要细节
讲道理应该是先实际操作,然后发现问题,再记录下来的,但是为了让大家少踩坑,我把我遇到的问题都先放出来,我一直认为,面对一个陌生的领域 你的学习策略应该是 像下面这样(PDCA),这样的话一定会事半功倍的!记住 时间是检验真理的的唯一标准!
实际操作
Android 项目如何集成RN
好!我们来叨叨一下 前面Android 部分的坑, 对了需要说明一下,我的Android Studio 版本,我的版本如下(🤣 没错IDE的版本也有坑 一会儿我会说到
- 首先是权限相关的问题,有的Android 版本和厂商 会有些魔改,导致RN的Debuger 窗口需要声明特定的权限,你一定要找到手机的App 权限管理,把这个权限开给我们的App开放就好了, 下面是完整的权限 和http 配置
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapprnn">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyappRNN"
android:usesCleartextTraffic="true"
tools:targetApi="31">
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<activity
android:name=".MainActivity"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
++++部分配置
- gradle 的配置,这一部分坑比较的多
首先是这个 Android 版本的变化,导致 原来在 gradle 中有个 allprojects 的东西现在被移动到 settings.gradle 中去了,而且还要说明一点,settings.gradle 这个文件中的 下面的字段会有问题,你得改改,具体的原因GitHub上有一个 ,见文末尾连接 repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS), 不仅如此 ,我这个项目构建的时候,因为以前些RN 的时候 都会习惯性 link 一下,但是我这个项目开始之后 RN 的cli 被改了,源代码中已经没有link 这个命令了,所以不要再 用link 了哦
下面的代码是完整的 settings.gradle 代码
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
// 注意这里 要去掉 请见一个 github 的issuss https://github.com/realm/realm-java/issues/7374 ,以及 7.0 下 gradle 的管理文档
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
repositories {
google()
mavenCentral()
maven {
// All of React Native (JS, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
}
}
rootProject.name = "myappRNN"
// 在新版本中 (@react-native-community/cli )它把 link 给删除了, 会自动进行link 不需要你手动加
// 添加自动link 库
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
applyNativeModulesSettingsGradle(settings)
include ':app'
- 在主View Activity 上代码上不用改动,这一部分没有坑,下面是我的commit 上的代码 有些许不一样,不过区别不大,用上面的代码大致也能运行
public class MainActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
private final int OVERLAY_PERMISSION_REQ_CODE = 1; // 任写一个值
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:" + getPackageName()));
startActivityForResult(intent, OVERLAY_PERMISSION_REQ_CODE);
}
}
SoLoader.init(this, false);
mReactRootView = new ReactRootView(this);
List<ReactPackage> packages = new PackageList(getApplication()).getPackages();
// 有一些第三方可能不能自动链接,对于这些包我们可以用下面的方式手动添加进来:
// packages.add(new MyReactNativePackage());
// 同时需要手动把他们添加到`settings.gradle`和 `app/build.gradle`配置文件中。
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setCurrentActivity(this)
.setBundleAssetName("index.android.bundle")
.setJSMainModulePath("index")
.addPackages(packages)
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
// 注意这里的MyReactNativeApp 必须对应"index.js"中的
// "AppRegistry.registerComponent()"的第一个参数
mReactRootView.startReactApplication(mReactInstanceManager, "MyReactNativeApp", null);
setContentView(mReactRootView);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == OVERLAY_PERMISSION_REQ_CODE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (!Settings.canDrawOverlays(this)) {
// SYSTEM_ALERT_WINDOW permission not granted
}
}
}
mReactInstanceManager.onActivityResult( this, requestCode, resultCode, data );
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
protected void onPause() {
super.onPause();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostPause(this);
}
}
@Override
protected void onResume() {
super.onResume();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
}
if (mReactRootView != null) {
mReactRootView.unmountReactApplication();
}
}
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
return super.onKeyUp(keyCode, event);
}
}
通过以上的调整你大概率是能够把整个 Android项目 在Dev 环境下RUN 起来了,如果Android Studio 还在build 的时候报错,请自觉去google 吧,世界上怎么多人 总有人与你有一样的问题
我仅仅在Dev 环境下运行,我希望build 怎么办呢?
这个问题我们需要分两个部分去处理,第一个部分,先build js部分 ,上述官方告诉你的方式 文件路径不对哈,你要自己调整到android 项目的 assets 文件夹中 ,我的js build 脚本是这样的, 你需要确保你的这个js bundle 能正常的打出来,并且放在对的文件夹中哈。
$ react-native bundle
--platform android
--dev false
--entry-file index.js
--bundle-output ./android/app/src/main/assets/index.android.bundle
--assets-dest ./android/app/src/main/res/
Android部分 ,你需要先生成keystore (证书,然后丢到android 项目的app目录下,还需要去 gradle配置文件中配置
keystore 如何生成? 以Mac 为例子
a. 确保自己安装 了java
$ /usr/libexec/java_home -V
# 在打印的信息中找到 Matching Java Virtual Machines (1): 目录cd进去
$ cd /Library/Java/JavaVirtualMachines/adoptopenjdk-8.jdk/Contents/Home
$ sudo keytool -genkey -alias test.keystore -keyalg RSA -sigalg SHA1WithRSA -validity 20000 -keysize 1024 -keystore test.keystore -v
# 然后就是按要求回答问题 最后他会给你生存 keystore 然后你把他cv 出来
b. 使用 文件夹 ”前往“ 功能 前往刚才的路径 cv keystore 出来 ,当然我们不要old 版本的 keystore,我们要正常的 keystore 你可以不用cv 用shell 也ok
c. 如果你要查看 证书的内容,可以使用下面的命令
keytool -list -v -keystore
gradle.properties 文件中
MYAPP_RELEASE_STORE_FILE=my-release-key.keystore
MYAPP_RELEASE_KEY_ALIAS=my-key-alias
MYAPP_RELEASE_STORE_PASSWORD=123456
MYAPP_RELEASE_KEY_PASSWORD=123456
配置好之后 我的习惯是每次修改gradle 都会重新 载入一下 看看会不会报错,这是一个好习惯
开始你的build release 版本吧,点击这里,然后切换版本 build 出APK 就好了
- 如何处理 原生第三方模块呢?比如我需要 使用 react-native-device-info
很简单 首先js 这边先安装,如果这个lib 比较的nice 那么它是自动link 的你就不用关注了 ,直接在android 项目中 重新载入gradle 就能看到它了
🤨 如果不幸它不能自动link ,而且我们的版本也的cli 也失去的link 的功能怎么办?
你需要在这 和以前一样 cv link 的代码然后丢到 gredle 配置中,并且最后 在 代码中 声明它
include ':react-native-device-info'
project(':react-native-device-info').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-device-info/android')
/Main Activey中
packages = new PackageList(this).getPackages();
packages.add(new RNDeviceModule(reactContext)); // 依据不同的库有不同的要求,比如它需要传上下文进行初始化
- 如果你需要 放置一些图片怎么办?比如一些 @2x @3x 的图
首先我们在js 中以dev 环境看看是否能够正常的加载这些static的资源 注意 Metro build 的时候会 自动区分2x 3x 的图片来适配不同的屏幕
/依然是上文的那个组件,我们改一下它加入一张图片
import Imgx from "./assets/img/1024_500.png";
++++
<View style={styles.imgView}>
<Image
resizeMethod="resize"
resizeMode="contain"
source={Imgx}
// source={imgICON}
style={styles.img}
/>
</View>
+++
运行的效果 看起来没有问题。我们试一下build ,我们什么都不 改发现事情有些不一样,build 之后 android res 下的图片自动分配出去了 ,而且名字的用路径+ _ 连接,mdpi 和xhdpi ....是Androi自己的的一套分辨率适配体系,这里我们不深入了。但是我们知道它是这样一回事就好了,现在build 也完全ok ,到目前为止 Android 的集成就没有什么其他的问题了 🥳
IOS 项目如何集成RN
好!我们来叨叨一下 前面IOS 部分的坑,先说一下我的IDE 版本和 模拟器配置 这些代码有的涉及到 IOS OC 和UIKit 的代码 ,请自行去学习相关的内容
- 最大的一个坑就是IOS 的pod 依赖问题,首先无论是官方英文还是中文的代理站点,的文档都过时了,有的时候你会发现,不是找不到这个依赖就是这个依赖变了,而且网上的一些解决方法或多或少都有些问题,我这里的方案是
自己写?不如直接去引RN写好的 下面是一个完整的 Podfile 文件
# Uncomment the next line to define a global platform for your project
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
// 指定部署版本 直接CV RN 脚手架 创建的 PodFIle 的官方的代码
install! 'cocoapods', :deterministic_uuids => false
platform :ios, '12.4' // 指定部署版本
target 'myrnapp' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks! :linkage => :static
# 使用静态库 连接 不要使用动态库 或者 默认的连接 ,会有问题,
# issues:具体的忘记了反正 是存在这个问题的 有些Issue 上提出的解决办法 是 把
# config.build_settings['ENABLE_BITCODE'] = 'NO' 但 这会引发另一个问题,我暂时只能这样处理了
config = use_native_modules!
flags = get_default_flags()
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
# Upcoming versions of React Native may rely on get_default_flags(), but
# we make it explicit here to aid in the React Native upgrade process.
:hermes_enabled => false,
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
# :flipper_configuration => FlipperConfiguration.enabled,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
# Pods for myrnapp
target 'myrnappTests' do
inherit! :search_paths
# Pods for testing
end
target 'myrnappUITests' do
# Pods for testing
end
post_install do |installer|
react_native_post_install(
installer,
# Set `mac_catalyst_enabled` to `true` in order to apply patches
# necessary for Mac Catalyst builds
:mac_catalyst_enabled => true
)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end
end
然后才是pod install ,(注意 pod install 之前要把 .lock 和Pod文件夹下 的东西删除掉哈)
- 关于 对 XCode 创建项目的改造 ,注意我们要把原来 SceneDelegate 去掉,当然你也可以选择不去删除它,但是我这里 就把它去掉了,主要目的和 和 RN CLI 创建的App 对齐
至于方法请看 文末尾连接
终于你的Native 代码结构变成了这样子
其中在 删除 SceneDelegate 过程中 对 AppDelegete 和 Main 的改动这里不详细的说了,需要的可以去仓库自己详细看看
- 在我们的ViewController 中 添加一个方块 ,添加一个方法, 当方块被点击的时候触发这个方法,打开这个RN 渲染的View
++++
- (**void**) viewDidLoad {
[**super** viewDidLoad];
// 加一些oc 的code 确保项目上正常的状态
UIView *view = [[UIView alloc] init];
view.backgroundColor = [UIColor redColor];
view.frame = CGRectMake(100,100, 100, 100);
[**self**.view addSubview:view];
view.userInteractionEnabled = **YES**;
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:**self** action: **@selector**(openRNView)];
[view addGestureRecognizer:tap];
// 直接开始集成
}
// 设它为 IOS2 main 模块
- (**void**)openRNView {
// 这里面就写load ios 的代码
NSLog(@"High Score Button Pressed");
NSURL *jsCodeLocation = [NSURL URLWithString:@"http://localhost:8082/index.bundle?platform=ios"];
RCTRootView *rootView =
[[RCTRootView alloc] initWithBundleURL: jsCodeLocation
moduleName: @"MyReactNativeApp"
initialProperties: **nil**
launchOptions: **nil**];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[**self** presentViewController:vc animated:**YES** completion:**nil**];
};
现在我们开始在Xcode run 一下 体验一下 dev 下是否正常运行,看起来是没有问题的,图片我就不放了。
- 关于build 的问题怎么办?
我们要从两个方便处理这个问题 一是js 端的,二是native 端的
js这边的话 ,主要还是build 的时候 需要注意我们把build 的目录设置到 项目根目录的bundle 下,然后在xcode 中通过软连接的方法连接出去 就好了,具体的操作 如下
$ yarn react-native bundle
--entry-file ./index.js
--bundle-output ./bundle/index.ios.bundle
--platform ios
--assets-dest ./bundle
--dev false
然后把 build 出来的 bundle 文件夹整个拖拽到Xcode 项目目录中
在xcode看起来就是这个样子了
最后就是去改造一下 加载器 不要从 dev 服务器加载了 直接从 本地加载,姿态资源也是自动处理的,这里我们暂时不管
- (**void**)openRNView {
NSURL *jsCodeLocation = [[NSBundle mainBundle] URLForResource:@"bundle/index.ios" withExtension:@"bundle"];
// RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL: jsCodeLocation
// moduleName: @"MyReactNativeApp"
// launchOptions: nil];
RCTRootView *rootView = [[RCTRootView alloc]
initWithBundleURL:jsCodeLocation
moduleName:@"MyReactNativeApp"
initialProperties:nil
launchOptions:nil ];
UIViewController *vc = [[UIViewController alloc] init];
vc.view = rootView;
[self presentViewController:vc animated:YES completion:nil];
}
以上就是全部IOS 不放的内容 了,哦不,还有IOS的build ,这个...哪位老板共享一个证书过来试一下?本人在公司的电脑上尝试过 但是公司是M1 的芯片 对上述的 PodFile配置不一样,....就没空去搞了
- 好像还差点什么?🧐 哦对 Native 包怎么办?
一般来说 对于 具备 自动link 的包,在yarn 之后 直接pod install 或者 pod update 之后它就能够自动添加上依赖了,你在命令行中也能看到相关的信息,但是...如果你需要手动添加的话 需要 在这改 Podfile文件
+++
config = use_native_modules!
flags = get_default_flags()
pod 'RNDeviceInfo', path: '../node_modules/react-native-device-info'
+++
嗯上述就是全部IOS 部分的内容了
总结
上述的文章中详细的描述了所有的细节以及问题,以及处理方案!总而言之,就是 如果发现有问题 跑不通, 需要分析原因 Native 就到Native IDE debug中 看看哪个环节有问题 Part1 部分到此结束解析来是 还会继续更新。Code 和项目 东西全部都做好了,只差写些文章了
创作不易 如果对你有帮助 点赞呗 🙈