Skip to content

前置说明

你好我是 无双-Joney ,好久没有发文了 下面发一下最近我正在干的一件事,从零开始 设计一个现代化的APP开发架构并实现它,项目仓库是 在文章后面

为了实现和探究ReactNative的分包技术,以及搭建一个 相对从性能上 和 技术上都比较ok 的项目架构,我打算用几篇文章 ,和大家分享一下,我在工作中实际使用的一个App 架构方案,它主要由 客户端(Android/IOS) + React Native + WebView 结合H5 的SSR 和 各种缓存和分包技术,构建而成。本文主要详细分享 RN 的拆包和自建的热更新相关的内容。⚠️ 可能包含一部分的Native 知识 ,你得先了解一下 Android(java) 和 IOS(Oc相关的一些语法),为了让大家能够更方便的看得懂 能吸收和内化知识,我把文章的脉络和连载如下图所示, 本文包含了下面这些内容

项目AndroidIOS
依照官方进行集成✅ 完成✅ 完成
dev是否正常运行✅ 完成✅ 完成
build 一下是否正常运行✅ 完成✅ 完成
Assets 资源加载逻辑✅ 完成✅ 完成
native版本的包管理✅ 完成✅ 完成

image.png

理论知识

Android 项目如何集成RN

具体的连接见下午,我们从这里开始开始把rn 集成到android 的原生应用中,我需要解释一下,为什么不直接使用 rn 的脚手架 构建一个 完整的rn 项目呢,原因主要还是rn的完整项目牵扯的东西太多了,也不好修改,而且在我的实践中 开发一款App native 要占有较多的成分在里面,ok我们现在开始(注意,如果你需要 kotlin 来干Android 去新原文档 看就好了链接在后文

1.第一步 构建好一个初步的完整的 项目结构

shell
$ mkdri myrnapp
$ cd myrnapp
$ mkdir android
$ mkdri ios
$ touch index.js
$ yarn init 
# 一路回车

你现在的目录应该是这个样纸

md
myrnapp
|---android/
|---ios/
|---node_modules/
|---index.js
|---package.json

2.安装相关的依赖 和修改一下启动脚本

shell
$ yarn add react-native
# 注意 这个时候你要看看 它的版本是多少,去package.json 中自己查看 ,下面安装react 依赖的时候一定要一一对应!高了不行低了也不行
$ yarn add react@16.2.0

.gitignore 文件 这个比较的简单,我直接从rn 脚手架创建的项目中的文档cv 过来就好了

md
# 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 中

JSON
"scripts": {
    "start": "react-native start --port=8082 ",
    }
  1. 在index.js中写一些内容,一会儿用它来测试
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
);
  1. 下面的内容开始去配置我们的 原生Android 应用

首先是创建一个空的应用

image.png

然后在Android stuido中进行操作和配置 gradle (强烈建议 你应该在 原生开发的IDE中写原生的代码,而不是在vscode 中....) 我们找到模块的gradle 写入下面的内容

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)
  1. 为Android项目 配置权限
xml
<!--
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>  
<!-- ... -->
  1. 写一些Native 代码 (JAVA
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 );
    }

}
  1. 启动
shell
yarn start

Android 项目,请在 Android Studio 中启动它

image.png

这就是官方对于Android 如何集成RN的所有文档了,但是它没有说资源资本png 图片等.... 也没有说 android 的keystore 和build ,不过不要着急,我在后文中有详细的说明 和验证

IOS 项目如何集成RN

IOS的项目集成也相对的比较简单没有怎么多的问题 ,但是问题也不少哈哈哈,有一个问题需要特别注意,注意这个时候请确保你有一个 比较稳定的科学上网

  1. 配置你的IOS Native 应用

在上文的基础文件夹中,构建自己的IOS程序(请使用Xcode 去创建一个空的App),然后进入这个目录下,使用cocoaPods管理依赖(如果你电脑上没有cocaPods 请去安装一个

shell
# 我习惯用 brew 当然你可以使用的别的
$ brew  install cocoapods

然后安装官方的说法就 ,还是原来的IOS项目的根目录下 执行pod init

shell
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

然后在刚才的目录下 执行

shell
# 让其去下载依赖 到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>
+++
  1. 前面都是可看做是一些前置的步骤,现在我们来看看Code 代码中如何集成

我们这里新增一个View 来承载 RN

c
#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];
}
  1. JS的代码和上述Android 一样使用同一份,然后Metro run 起来,XCode 把IOS项目Build 出来,等他跑在模拟器中,你就能看到效果了(哈哈哈,再次强调,你并不会看到效果,因为官方的文档,现在提供的方式还有问题,真正有效果的方式,我们实践中有说明具体到底如何写,所有代码 我都会放在github上

重要细节

讲道理应该是先实际操作,然后发现问题,再记录下来的,但是为了让大家少踩坑,我把我遇到的问题都先放出来,我一直认为,面对一个陌生的领域 你的学习策略应该是 像下面这样(PDCA),这样的话一定会事半功倍的!记住 时间是检验真理的的唯一标准!

image.png

实际操作

Android 项目如何集成RN

好!我们来叨叨一下 前面Android 部分的坑, 对了需要说明一下,我的Android Studio 版本,我的版本如下(🤣 没错IDE的版本也有坑 一会儿我会说到

image.png

  1. 首先是权限相关的问题,有的Android 版本和厂商 会有些魔改,导致RN的Debuger 窗口需要声明特定的权限,你一定要找到手机的App 权限管理,把这个权限开给我们的App开放就好了, 下面是完整的权限 和http 配置
xml
<?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>
++++部分配置
  1. 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'
  1. 在主View Activity 上代码上不用改动,这一部分没有坑,下面是我的commit 上的代码 有些许不一样,不过区别不大,用上面的代码大致也能运行
java

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);
    }

}
  1. 通过以上的调整你大概率是能够把整个 Android项目 在Dev 环境下RUN 起来了,如果Android Studio 还在build 的时候报错,请自觉去google 吧,世界上怎么多人 总有人与你有一样的问题

  2. 我仅仅在Dev 环境下运行,我希望build 怎么办呢?

这个问题我们需要分两个部分去处理,第一个部分,先build js部分 ,上述官方告诉你的方式 文件路径不对哈,你要自己调整到android 项目的 assets 文件夹中 ,我的js build 脚本是这样的, 你需要确保你的这个js bundle 能正常的打出来,并且放在对的文件夹中哈。

shell
$ 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

shell
$ /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. 如果你要查看 证书的内容,可以使用下面的命令

shell
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 都会重新 载入一下 看看会不会报错,这是一个好习惯

image.png

开始你的build release 版本吧,点击这里,然后切换版本 build 出APK 就好了

image.png

  1. 如何处理 原生第三方模块呢?比如我需要 使用 react-native-device-info

很简单 首先js 这边先安装,如果这个lib 比较的nice 那么它是自动link 的你就不用关注了 ,直接在android 项目中 重新载入gradle 就能看到它了

image.png

🤨 如果不幸它不能自动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中

java
packages = new PackageList(this).getPackages();
packages.add(new RNDeviceModule(reactContext)); // 依据不同的库有不同的要求,比如它需要传上下文进行初始化
  1. 如果你需要 放置一些图片怎么办?比如一些 @2x @3x 的图

首先我们在js 中以dev 环境看看是否能够正常的加载这些static的资源 image.png 注意 Metro build 的时候会 自动区分2x 3x 的图片来适配不同的屏幕

/依然是上文的那个组件,我们改一下它加入一张图片

js

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 的集成就没有什么其他的问题了 🥳

image.png

IOS 项目如何集成RN

好!我们来叨叨一下 前面IOS 部分的坑,先说一下我的IDE 版本和 模拟器配置 这些代码有的涉及到 IOS OC 和UIKit 的代码 ,请自行去学习相关的内容

image.png

  1. 最大的一个坑就是IOS 的pod 依赖问题,首先无论是官方英文还是中文的代理站点,的文档都过时了,有的时候你会发现,不是找不到这个依赖就是这个依赖变了,而且网上的一些解决方法或多或少都有些问题,我这里的方案是

自己写?不如直接去引RN写好的 下面是一个完整的 Podfile 文件

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文件夹下 的东西删除掉哈)

  1. 关于 对 XCode 创建项目的改造 ,注意我们要把原来 SceneDelegate 去掉,当然你也可以选择不去删除它,但是我这里 就把它去掉了,主要目的和 和 RN CLI 创建的App 对齐

至于方法请看 文末尾连接

终于你的Native 代码结构变成了这样子 image.png

其中在 删除 SceneDelegate 过程中 对 AppDelegete 和 Main 的改动这里不详细的说了,需要的可以去仓库自己详细看看

  1. 在我们的ViewController 中 添加一个方块 ,添加一个方法, 当方块被点击的时候触发这个方法,打开这个RN 渲染的View
C++
++++
- (**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 下是否正常运行,看起来是没有问题的,图片我就不放了。

  1. 关于build 的问题怎么办?

我们要从两个方便处理这个问题 一是js 端的,二是native 端的

js这边的话 ,主要还是build 的时候 需要注意我们把build 的目录设置到 项目根目录的bundle 下,然后在xcode 中通过软连接的方法连接出去 就好了,具体的操作 如下

shell
$ yarn react-native bundle 
    --entry-file ./index.js 
    --bundle-output ./bundle/index.ios.bundle 
    --platform ios 
    --assets-dest ./bundle
    --dev false

然后把 build 出来的 bundle 文件夹整个拖拽到Xcode 项目目录中

image.png

在xcode看起来就是这个样子了

image.png

最后就是去改造一下 加载器 不要从 dev 服务器加载了 直接从 本地加载,姿态资源也是自动处理的,这里我们暂时不管

c++
- (**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配置不一样,....就没空去搞了

  1. 好像还差点什么?🧐 哦对 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 和项目 东西全部都做好了,只差写些文章了

创作不易 如果对你有帮助 点赞呗 🙈

参考和相关连接