项目组用react-native开发app,前阵子有新需求,需要用android原生实现,将原生集成到react-native项目中调用
主要涉及到:
1)rn的js页面调用android原生(跳转到原生页面)
2)rn跳转到原生时带值过去
3)原生执行结束后,通知rn反应
1.添加原生模块
- 1)创建TestModule1.java,在这里暴露原生模块名称,提供给rn调用
public class TestModule1 extends ReactContextBaseJavaModule {
public TestModule1(ReactApplicationContext reactContext) {
super(reactContext);
}
//注意:返回的名称为JS中调用的模块名称
@Override
public String getName() {
return "TestModule1";
}
//可以在js中结合模块名调用该方法。可以在这里实现页面跳转//参数 name:目标activity路径//参数params:RN传递给原生的参数
@ReactMethod
public void startActivityFromJS(String name, String params){
try{
Activity currentActivity = getCurrentActivity();
if(null!=currentActivity){
Class toActivity = Class.forName(name);
//目标activity
Intent intent = new Intent(currentActivity,toActivity);
currentActivity.startActivity(intent);
}
}catch(Exception e){
throw new JSApplicationIllegalArgumentException("不能打开Activity : "+e.getMessage());
}
}
//注意:startActivityFromJS、dataToJS方法添加RN注解(@ReactMethod),否则该方法将不被添加到RN中}复制代码
1.2)创建TestRectPackge1.java,在这里初始化模块
public class TestRectPackge1 implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
//在这里声明module
return Arrays.<NativeModule>asList(new TestModule1(reactContext));
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}复制代码
1.3)创建TestActivity1.java,在这里使用android原生规则写页面(res资源配置)
页面有两种写法
1.3.1)在activity中调用android自带组件.
TestActivity1.java
public class TestActivity1 extends Activity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(createView());
}
//hello world页面
private View createView() {
LinearLayout ll= new LinearLayout(this);
ll.setGravity(Gravity.CENTER);
ll.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
// 设置文字
TextView mTextView = new TextView(this);
mTextView.setText("hello world");
LinearLayout.LayoutParams mLayoutParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
// 在父类布局中添加它,及布局样式
ll.addView(mTextView, mLayoutParams);
return ll;
}
@Override
public void onClick(View v) { }}复制代码
1.3.2)在res/layout目录下,声明xml文件,组件和页面写在这里,activity可以直接调用它
查到资料是:src/main/res目录下的文件夹有固定含义,放在layout目录下的xml,编译时会被自动放入R.layout中,可根据文件名引入
这里将activity_demo.xml放到res/layout目录下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="36dp"
android:layout_marginTop="36dp"
android:text="Hello World!"
android:textSize="36sp"
android:textStyle="bold" />
<Button
android:id="@+id/button_opencad"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="OpenCAD" />
</LinearLayout>复制代码
activity调用页面 TestActivity1.java
import com.mypj2.R;
//手动引入,重要
public class TestActivity1 extends Activity implements View.OnClickListener {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_demo);
View goBack = findViewById(R.id.button_opencad);
}
@Override
public void onClick(View v) { }}复制代码
在这里遇到的坑:
i.项目主路径:在src/main/AndroidManifest.xml中配置。<manifest package="com.mypj1">声明项目主路径是com.mypj1;activity声明中包含内容<activity><intent-filter><action android:name="android.intent.action.main"></intent-fiter><activity>,那么相应配置的java为项目入口文件
ii.R包找不到问题:TestActivity1.java没有放在我们项目主目录下,而是在主目录下创建子目录,放到子目录中。运行的时候编译不通过,控制台提示R包找不到,项目主目录为com.mypj1,后面通过在TestActivity1.java中import com.mypj1.R;编译通过,看起来好像是R文件默认在主目录下生成
2.配置原生模块
2.1)在项目入口java文件中添加该模块。我们项目入口文件为MainApplication.java,在入口文件getPackages方法中添加1.2)中创建的TestReactPackage1()
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new TestReactPackage1()
//添加的原生模块 );
}复制代码
2.2)在配置文件中添加用到的activity
在src/main/AndroidManifest.xml中添加
<activity android:name="com.mypj1.testPlugin1.TestActivity1" />
复制代码
2.3)在RN 的js页面中调用原生模块
在index.android.js中(可以添加到任意js页面)
import {NativeModules} from 'react-native'; //导入NativeModules模块
onPressApp(){
NativeModules.TestModule1.startActivityFromJS("com.rnpj1.testPlugin1.TestActivity1",null);
}
<TouchableOpacity onPress={this.onPressApp} >
<Text style={{color:'#50B1F8',fontSize:16}}>app打印调用</Text>
</TouchableOpacity>复制代码
配置结束可以启动项目调用
3.RN给原生传值
有时需要从RN页面带值到android页面,在之前的项目上修改
3.1)js调用原生模块时需要传参
在index.android.js中增加
let myData='rn传给原生的值'
NativeModules.PrintModule.startActivityFromJS(com.rnpj1.testPlugin2.TestActivity2",myData);
复制代码
3.2)在TestModule2.java中处理参数,在Testactivity2.java里将参数打印到页面中
TestModule2.java中,RN调用android原生得方法中:
@ReactMethod
public void startActivityFromJS(String name, String params){
try{
Activity currentActivity = getCurrentActivity();
if(null!=currentActivity){
Class toActivity = Class.forName(name);
Intent intent = new Intent(currentActivity,toActivity);
intent.putExtra("params", params);
//在module文件中,把参数放到intent里
currentActivity.startActivity(intent);
}
}catch(Exception e){
throw new JSApplicationIllegalArgumentException("不能打开Activity : "+e.getMessage());
}
}复制代码
在TestModule2.java要跳转的activity:Testactivity2.java中取值
private View createView() {
LinearLayout ll= new LinearLayout(this);
ll.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
// 设置文字
TextView myTextView = new TextView(this);
myTextView.setText(getIntent().getStringExtra("params"));
LinearLayout.LayoutParams mLayoutParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
ll.addView(myTextView, mLayoutParams);
return ll;
}复制代码
4.原生给RN通信/调用的一种方法
有时原生执行过程中希望能触发RN中的某些方法执行,比如:原生执行过程中希望传参给RN,让RN在控制台记录日志
4.1)在js页面调用可以被android原生触发的方法
在index.android.js中
import {DeviceEventEmitter} from 'react-native';
//在页面初始化时绑定触发点名称和执行方法,以下定义了consoleY,供原生触发。功能:将原生传的参数打印到控制台
componentDidMount(){
DeviceEventEmitter.addListener('consoleYS',this.eventHandler);
};
componentWillUnmount(){
DeviceEventEmitter.removeListener('consoleYS',this.eventHandler);
}
eventHandler=(message)=>{
console.log('consoleYs:')
console.log(message)
}复制代码
4.2)在原生中调用触发点 ‘consoleYS’
修改TestModule3.java文件
public class TestModule3extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactContext;
//必须定义
public TestModule3(ReactApplicationContext reactContext) {
super(reactContext);
this.reactContext=reactContext;
//必须添加
}
//自定义方法,触发RN中定义的consoleYS
public static void sendEvent(String param){
WritableMap eventValue=new WritableNativeMap();
eventValue.putString("value",param);
if(reactContext!=null){
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("consoleYS",eventValue);
}
}复制代码
4.3)在activity文件中调用
在TestActivity3.java中
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
PrintModule.sendEvent("1","123456789");
//调用RN
}复制代码
运行项目,进入TestActivity3定义的页面后,发现RN控制台打印 123456
结束。
题外话:
原生是另一个项目组完成,我们调用,所以需要一起联调。遇到的问题
i)rn的android/app目录下有自己的资源文件夹res,android项目组实现功能时也有自己的res。为了集成后结构保持清晰,在res下为rn创建资源目录,为android原生依照其功能创建目录,资源独立存放,在build.gradle文件中配置,使得编译时这两个资源目录可以合并;原生res中的参数命名时,按照功能取前缀名。如打印模块:print_***,避免参数名重复,编译报错。
ii)开发之前双方应该约定好:sdk版本一致,项目主目录一致,原生模块拥有单独目录,资源命名以功能为前缀。
iii)开发前没有约定好sdk版本,调研对android版本的支持程度,android原生引入了高版本包,集成到rn中报错。这时只能选择尝试将包版本降级