美文网首页
RN调用原生之Android篇

RN调用原生之Android篇

作者: 哪吒闹海全靠浪 | 来源:发表于2017-05-12 14:12 被阅读0次

    同样,跟着官方的例子走一遍,看有没坑。

    1.首先,创建一个原生模块(以Toast为例)

    public class RNToastModule extends ReactContextBaseJavaModule {
    
        private static final String DURAION_SHORT_KEY = "SHORT";
        private static final String DURAION_LONG_KEY = "LONG";
    
        public RNToastModule(ReactApplicationContext reactApplicationContext) {
            super(reactApplicationContext);
    
        }
        /*
            这是定义JS端要调用的模块名,比如"RNToastAndroid"
            则到时候就用React.NativeModules.ToastAndroid调用这个模块
         */
        @Override
        public String getName() {
            return "RNToastAndroid";
        }
    
        /*
            一个可选的方法getContants返回了需要导出给JavaScript使用的常量。
            它并不一定需要实现,但在定义一些可以被JavaScript同步访问到的预定义的值时非常有用。
        */
        @Override
        public Map<String, Object> getConstants() {
            final Map<String, Object> constants = new HashMap<>();
            constants.put(DURAION_SHORT_KEY, Toast.LENGTH_SHORT);
            constants.put(DURAION_LONG_KEY, Toast.LENGTH_LONG);
            return constants;
        }
        /*
            这是用作导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod
            所以到时候JS调用就是React.NativeModules.ToastAndroid.show("xx",100)
         */
        @ReactMethod
        public void show(String message, int duration) {
            Toast.makeText(getReactApplicationContext(), message, duration).show();
    
        }
    }
    

    2.注册模块

    要使JavaScript端调用到原生模块还需注册这个原生模块。需实现一个类实现ReactPackage接口,并实现其中的抽象方法。

    public class RNJavaReactPackage implements ReactPackage {
        @Override
        public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
            List<NativeModule> modules = new ArrayList<>();
            modules.add(new RNToastModule(reactContext));
            return modules;
        }
    
        @Override
        public List<Class<? extends JavaScriptModule>> createJSModules() {
            return Collections.emptyList();
        }
    
        @Override
        public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
            return new ArrayList<>();
        }
    
    }
    

    除了上面的步骤外,还需在MainApplication.java文件中的getPackages方法中,实例化上面的注册类。

    注意:如果是RN项目,是自动生成这个文件的,在这个位置:“android/app/src/main/java/com/your-app-name/MainApplication.java”,但由于我们是原生应用插入RN的,没有生成,所以需要手动添加,添加后记得在AndroidManifest.xml加上相应引用。

    public class MainApplication extends Application implements ReactApplication {
    
        private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
            @Override
            public boolean getUseDeveloperSupport() {
                return BuildConfig.DEBUG;
            }
    
            @Override
            protected List<ReactPackage> getPackages() {
                return Arrays.<ReactPackage>asList(
                        new MainReactPackage(),
                        new RNJavaReactPackage()//<-- 添加这一行,类名替换成你的Package类的名字.
                );
            }
        };
    
        @Override
        public ReactNativeHost getReactNativeHost() {
            return mReactNativeHost;
        }
        @Override
        public void onCreate() {
            super.onCreate();
            SoLoader.init(this, /* native exopackage */ false);
        }
    }
    

    3.JS调用Android原生方法

    import React, { Component } from 'react';
    import {
        AppRegistry,
        StyleSheet,
        Text,
        View,
        NativeModules,
    } from 'react-native';
    
    export default class AwesomeProject extends Component {
        render() {
            return (
                <View style={styles.container}>
                    <Text style={styles.welcome}>
                        Welcome to React Native!
                    </Text>
                    <Text style={styles.instructions}>
                        To get started, edit index.android.js
                    </Text>
                    <Text style={styles.instructions}>
                        Double tap R on your keyboard to reload,{'\n'}
                        Shake or press menu button for dev menu
                    </Text>
                    <Text style={styles.instructions} onPress={() => this.showToast()}>
                        点我调用原生
                    </Text>
                </View>
            );
        }
        showToast () {
            //调用原生
            NativeModules.RNToastAndroid.show('from native',100);
        }
    }
    
    const styles = StyleSheet.create({
        container: {
            flex: 1,
            justifyContent: 'center',
            alignItems: 'center',
            backgroundColor: '#F5FCFF',
        },
        welcome: {
            fontSize: 20,
            textAlign: 'center',
            margin: 10,
        },
        instructions: {
            textAlign: 'center',
            color: '#333333',
            marginBottom: 5,
        },
    });
    
    AppRegistry.registerComponent('rnandnative', () => AwesomeProject);
    

    4.遇到问题

    照着官网的步骤,到这里应该就可以了,但我跑起来,却报错了:


    很明显,就是客户端觉得你根本没注册这个模块,于是开始寻找问题处在哪:
    1.首先想到的是,我们是原生集成RN的模式,官方的例子是RN项目的模式,看了下RN初始化的MainActivity

    public class MainActivity extends ReactActivity {
    
        /**
         * Returns the name of the main component registered from JavaScript.
         * This is used to schedule rendering of the component.
         */
        @Override
        protected String getMainComponentName() {
            return "rnandnative";
        }
    }
    

    它这里继承了ReactActivity,而我们集成RN的Activity继承的是Activity,问题有可能出在这里,于是把首页跳转的指向换成官方的这个MainActivity,果然可以了。
    2.然后就在想,有什么办法在我们集成RN的Activity也继承ReactActivity,于是就开始看ReactActivity里到底干了些什么,原来ReactActivity也是继承Activity,只是比我们多实现了一个PermissionAwareActivity。于是我就照ReactActivity源码,在我的Activity多实现了一个PermissionAwareActivity方法,但依然不行。
    3.然后接着看ReactActivity源码是怎么load一个RN的,在ReactActivityDelegate里有个loadApp方法

    protected void loadApp(String appKey) {
        if (mReactRootView != null) {
          throw new IllegalStateException("Cannot loadApp while app is already running.");
        }
        mReactRootView = createRootView();
        mReactRootView.startReactApplication(
          getReactNativeHost().getReactInstanceManager(),
          appKey,
          getLaunchOptions());
        getPlainActivity().setContentView(mReactRootView);
      }
    

    这个和我们Activity里的代码很像

      @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
    
            mReactRootView = new ReactRootView(this);
            mReactInstanceManager = ReactInstanceManager.builder()
                    .setApplication(getApplication())
                    //.setBundleAssetName("index.android.bundle")
                    .setJSMainModuleName("index.android")
                    .addPackage(new MainReactPackage())
                    .setUseDeveloperSupport(true)
                    .setInitialLifecycleState(LifecycleState.RESUMED)
                    .build();
            mReactRootView.startReactApplication(mReactInstanceManager, "rnandnative", null);
    
            setContentView(mReactRootView);
        }
    

    差别好像就是ReactInstance这个东东,我们用的是一个自定义的,源码里是直接调用getReactNativeHost().getReactInstanceManager(),里面做了什么不知道,但我就先把我的mReactInstanceManager替换成了getReactNativeHost().getReactInstanceManager(),跑起来居然可以了。
    4.然后就上网找getReactNativeHost().getReactInstanceManager()到底干了什么,发现其实就和我们自定义的没两样,一个是默认值罢了。这样问题一定就出在我们自定义的值了。
    5.于是就把ReactInstanceManager实现的那几方法一一查看到底是干嘛的,最终终于找到根源是这个addPackage,之前我们在步骤二里的MainApplication添加过

          protected List<ReactPackage> getPackages() {
                return Arrays.<ReactPackage>asList(
                        new MainReactPackage(),
                        new RNJavaReactPackage()//<-- 添加这一行,类名替换成你的Package类的名字.
                );
           }
    

    但这里其实是把那覆盖了,所以加上一句.addPackage(new RNJavaReactPackage())就大功告成了

                    mReactInstanceManager = ReactInstanceManager.builder()
                    .setApplication(getApplication())
                    //.setBundleAssetName("index.android.bundle")
                    .setJSMainModuleName("index.android")
                    .addPackage(new MainReactPackage())
                    .addPackage(new RNJavaReactPackage())
                    .setUseDeveloperSupport(true)
                    .setInitialLifecycleState(LifecycleState.RESUMED)
                    .build();
    

    6.这个问题排查了大半天,查资料的过程中还收获到不少知识,比如白屏的bundle预处理,jsbundle的加载机制,为做热更新打下不少基础,也算不亏。

    相关文章

      网友评论

          本文标题:RN调用原生之Android篇

          本文链接:https://www.haomeiwen.com/subject/wscatxtx.html