一、前言
继上次尝试了《ios 原生应用集成 react-native》之后。这次尝试 android 原生应用的集成。android 也分两种语言,kotlin 和 java。这里就尝试 java 就好了,不和 ios 一样尝试两种语言。全屏模式用 ReactRootView 组件,窗口模式使用 Fragment 组件做承载。有没有点眼熟,这个在 web 里代表空标签。vue 少见点, react 应该挺常见的。也有专门的 api,document.createDocumentFragment
。
这里也不多说,主要是记录下官网《ntegration with Existing Apps》教程里没有的。大部分还是跟着官网走就可以。老样子,也是建立在正常的 react-native android 环境能运行的前提下,才能继续。按照官网《Setting up the development environment》说的,JDK,Android Studio 该装的装好。
还是那句话,app 开发的同学应该一看就懂了,我这还是以一个只懂前端的视角记录一下。
二、全屏集成
创建按钮很显眼,就不用多说。这里说下两个模版,第一行第二个 Empty Activity
,据说是新模板,开发语言只有 kotlin。第二行第二个 Empty Views Activity
,据说是旧模板,开发语言可以选 kotlin 和 java,这里选 java。至于最下面还有个选择 build 语言的,选择第一个 Kotlin DSL(build.gradle.kts)
就可以了,反正三个我都看了都和官网示例对没完全对上。
1. MyReactActivity.java
创建一份 MyReactActivity.java 文件,如果是新项目的话,会有一份 MainActivity.java,放在一起就好了。
// 记得声明自己应用
package com.example.myapplicationrn;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.KeyEvent;
import com.facebook.react.PackageList;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.soloader.SoLoader;
import java.util.List;
public class MyReactActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
private ReactRootView mReactRootView;
private ReactInstanceManager mReactInstanceManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SoLoader.init(this, false);
mReactRootView = new ReactRootView(getApplication());
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, "reactnativeandroid", null);
setContentView(mReactRootView);
}
@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 (mReactRootView != null) {
mReactRootView.unmountReactApplication();
}
if (mReactInstanceManager != null) {
mReactInstanceManager.onHostDestroy(this);
mReactInstanceManager.destroy();
}
}
@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);
}
}
其实就是官网示例,然后整合成一份了。再把一些依赖 import 写上,不然完全新手看一堆依赖缺失还是挺不好的。
2. settings.gradle.kts
import groovy.lang.Closure
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
//dependencyResolutionManagement {
// repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
// repositories {
// google()
// mavenCentral()
// }
//}
rootProject.name = "My Application Rn"
include(":app")
apply(from="../node_modules/@react-native-community/cli-platform-android/native_modules.gradle")
val applyNativeModulesSettingsGradle: Closure<Any> = extra.get("applyNativeModulesSettingsGradle") as Closure<Any>
applyNativeModulesSettingsGradle(settings)
includeBuild("../node_modules/@react-native/gradle-plugin")
主要注意点是那段注释的,是默认新建工程带的。但是据说应该是:引入了 node_modules 里的 react-native 依赖就指定了仓库的引入,所以没有注释的话,会报大概是这样的错误:
A problem occurred configuring project ‘:app’.
Build was configured to prefer settings repositories over project repositories but repository
其它的是官网示例的语句替换成的新语法写法。对于 applyNativeModulesSettingsGradle
这个方法,是参考网上大佬给的解决方法,可能会有更合理更优雅的实现。
3. build.gradle.kts
plugins {
id("com.android.application") version "8.2.1" apply false
}
buildscript {
dependencies {
classpath("com.facebook.react:react-native-gradle-plugin")
}
}
4. app/build.gradle.kts
import groovy.lang.Closure
plugins {
id("com.android.application")
id("com.facebook.react")
}
android {
namespace = "com.example.myapplicationrn"
compileSdk = 34
defaultConfig {
applicationId = "com.example.myapplicationrn"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
dependencies {
implementation("androidx.appcompat:appcompat:1.6.1")
implementation("com.google.android.material:material:1.9.0")
implementation("androidx.constraintlayout:constraintlayout:2.1.4")
// 新增这两句
implementation("com.facebook.react:react-android")
// 新增这两句
implementation("com.facebook.react:hermes-android")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
}
apply(from="../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle")
val applyNativeModulesAppBuildGradle: Closure<Any> = extra.get("applyNativeModulesAppBuildGradle") as Closure<Any>
applyNativeModulesAppBuildGradle(project)
这个依然是按照官网的示例,替换成新语法写法。
5. AndroidManifest.xml
这个比较简单了,参考原来的增加一个 <activity android:name=".MyReactActivity">
标签就可以,就是 android:name
属性的值要是上面自己定义的 activity 类。
三、Fragment 集成
全屏倒还好,Fragment 的集成更加花费点功夫。依然官网《Integration with an Android Fragment》先行。但是果不其然,没那么顺利,Android Fragment 集成 react-native 一点就崩。
1. .MyReactApplication.java
其实是一开始不会看日志,按照官网集成之后,点击按钮弹出 Fragment,就直接闪退了。一路追踪后,有个地方要补充一下,直接看 .MyReactApplication.java
:
package com.example.myapplicationrn;
import android.app.Application;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.List;
public class MyReactApplication extends Application implements ReactApplication {
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, false);
}
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here
return packages;
}
// 注意注意注意添加这个方法
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
}
注意点:mReactNativeHost 里要添加多一个方法 getJSMainModuleName
,就是复写父类的方法。返回文件的入口,不然默认返回的是 index.android
。这就会导致服务和 app 都启动了,但是报错说服务器返回 404。如:
Unable to download JS bundle
com.facebook.react.common.DebugServerException: The development server returned response error code: 404
2. .MainActivity.java
再看 .MainActivity.java
:
package com.example.myapplicationrn;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import com.facebook.react.ReactFragment;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
public class MainActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button mButton = findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
TextView textView = findViewById(R.id.textView);
String newText = "WTF!";
textView.setText(newText);
Fragment reactNativeFragment = new ReactFragment.Builder()
.setComponentName("reactnativeandroid")
// setFabricEnabled 必须写
.setFabricEnabled(false)
// setLaunchOptions 可以省略
.setLaunchOptions(getLaunchOptions("test message"))
.build();
getSupportFragmentManager()
.beginTransaction()
.add(R.id.frameLayout, reactNativeFragment)
.commit();
}
});
}
private Bundle getLaunchOptions(String message) {
Bundle initialProperties = new Bundle();
initialProperties.putString("message", message);
return initialProperties;
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
}
注意点:setFabricEnabled
这个方法必须写,闪退的原因就是这个取值为空,直接崩了。
3. AndroidManifest.xml
最后,可能还会报错:
java.lang.ClassCastException: android.app.Application cannot be cast to com.facebook.react.ReactApplication
AndroidManifest.xml 文件,application
标签加上一句:android:name=".MyReactApplication"
就好了。当然这个值也是和上面自定义的类相对应。
总结
也没啥总结的,后面来看也改得不多,但是有时就差那么一两句,就是跑不起来。熟悉相关开发的同学看看源码估计也很容易就完成。这里就权当一个学习记录吧。
其它
关于 react-native 开发相关,可以参考《react-native ios 流水账》。
与 ios 的集成,可以参考《ios 原生应用集成 react-native》。