天天看點

Android原生項目接入React Native

發現今年React Native發展的挺好了,已經有很多公司開始使用RN來開發,我看慕課網也出了好多RN的視訊以及實戰課程。之前我也自學了RN,感覺上手入門還是挺快的,也打算繼續學習下去。下面開始切入正題:

假如你的Android原生項目已經寫了很多,接下來想使用RN實作一些頁面功能,那麼該怎麼接入呢,其實React Native中文網也有參考,但是我當時按照那篇文章來操作還是有些問題的,感覺不适合初學者來使用,接下來我打算記錄一些我自己的接入過程。希望能給一些正在搞這個的朋友作出一些參考

接入的前提條件:

  • 已有Android原生的工程
  • RN環境已經搭建好

本篇文章的所記錄的知識點有:

  • Android工程加載RN頁面
  • RN來調用原生的方法,如果需要傳回結果,則可以回調

先大緻浏覽一下,接入RN之後Android工程的樣子:

Android原生項目接入React Native

接下來開始實作接入

  • 步驟一: 用android studio打開已經建立好的Android工程,然後在Terminal中執行一下指令:
    1. npm init
    2. npm install –save react
    3. npm install –save react-native
    4. curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig
    說明,在建立 npm init的時候,需要輸入name、version等值,這裡可以随意先輸入一下,貌似在輸入name的值隻能用英文的小寫字母表示,其實這個無所謂的,可以将其他的React Native工程中的package.json檔案Copy過來,将裡面的name等值改一下就ok了,還省事。 其他3個指令,依次敲回車執行即可。然後再從React Native工程中Copy一份index.android.js,注意的是,需要将

    AppRegistry.registerComponent('RNComponent', () => RNComponent);

    第二個參數與package.json的name值保持一緻(我這裡改的是RNComponent,這個名字可以起别的也行)
  • 步驟二:這樣React Native的工作差不多完成了,然後Android端需要建立一個Activity來裝載所有的RN頁面,這裡我建立了MyReactActivity。在建立之前,需要在Android中添加一些依賴,否則用到一些類找不到,下面先開始配置Android工程
    step1. 在整個工程的build.gradle中添加 :
    allprojects {
            repositories {
    
                mavenLocal()
                jcenter()
                maven {
                    // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
                    url "$rootDir/node_modules/react-native/android"
                }
            }
        }
    
        task clean(type: Delete) {
            delete rootProject.buildDir
        }
               
    step2. 在moudle的build.gradle中添加

    compile "com.facebook.react:react-native:+"

    具體參考我的builde.gradle配置,如下所示
    android {
        compileSdkVersion 
        buildToolsVersion "25.0.2"
        defaultConfig {
            applicationId "com.reactnative"
            minSdkVersion 
            targetSdkVersion 
            versionCode 
            versionName "1.0"
            testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        }
    
        splits {
            abi {
                reset()
                enable true
                universalApk true  // If true, also generate a universal APK
                include "armeabi-v7a", "x86"
            }
        }
        buildTypes {
            release {
                minifyEnabled false
                proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            }
        }
        configurations.all {
            resolutionStrategy.force 'com.google.code.findbugs:jsr305:3.0.0'
        }
    }
    
    dependencies {
        compile fileTree(dir: 'libs', include: ['*.jar'])
        androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
            exclude group: 'com.android.support', module: 'support-annotations'
        })
        compile 'com.android.support:appcompat-v7:25.0.1'
        testCompile 'junit:junit:4.12'
    
        compile "com.facebook.react:react-native:+"  // From node_modules
    }
               
  • 緊接着上面的步驟二,開始建立MyReactActivity,代碼如下;
import com.facebook.react.ReactActivity;

public class MyReactActivity extends ReactActivity{

    @Override
    protected String getMainComponentName() {
        return "RNComponent";
    }
}
           
還有一種建立方式,就是下面這樣的,其中這兩種建立的MyReactActivity都是可以的,因為ReactActivity也是實作DefaultHardwareBackBtnHandler接口。
public class MyReactActivity extends Activity implements DefaultHardwareBackBtnHandler {
    private ReactRootView mReactRootView;
    private ReactInstanceManager mReactInstanceManager;

    @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(BuildConfig.DEBUG)
                .setInitialLifecycleState(LifecycleState.RESUMED)
                .build();


        mReactRootView.startReactApplication(mReactInstanceManager, "RNComponent", null);

        setContentView(mReactRootView);
    }

    @Override
    public void invokeDefaultOnBackPressed() {
        super.onBackPressed();
    }
}
           
  • 接下來開始建立 MainApplication,代碼如下
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()
          //将我們建立的包管理器給添加進來

      );
    }
  };

  @Override
  public ReactNativeHost getReactNativeHost() {
      return mReactNativeHost;
  }

  @Override
  public void onCreate() {
    super.onCreate();
    SoLoader.init(this, /* native exopackage */ false);
  }
}
           
  • ok,最後在AndroidManifest檔案中,添加一些權限,以及聲明MainApplication跟MyReactActivity
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.reactnative">

    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
    <uses-permission android:name="android.permission.SYSTEM_OVERLAY_WINDOW"/>

    <application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>


        <activity
            android:name=".MyReactActivity"
            android:configChanges="orientation|screenSize"
            android:theme="@style/NoActionBar"
            />

        <activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>


    </application>

</manifest>
           
這裡需要說明一下,在聲明MyReactActivity的時候,需要給它指定一個沒有ActionBar的樣式,在styles.xml中作修改:
<style name="NoActionBar" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>
           

到此為止,不出意外,一般都能正常加載RN頁面,效果如下:

Android原生項目接入React Native

到這裡 簡單的接入已經完成了,但是正常項目中,接入RN之後,RN肯定還要調用原生的一些方法,并且希望能拿到結果。比方說,RN想要得到系統相冊某些圖檔的路徑,那麼Android端就能提供查找的方法,并且将這個結果回調給RN

接下來開始實作 互動的過程

  • 第一步,需要寫個類來繼承ReactContextBaseJavaModule
package com.reactnative;

import android.content.Context;
import android.text.TextUtils;
import android.widget.Toast;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.uimanager.IllegalViewOperationException;

public class MyNativeModule extends ReactContextBaseJavaModule {

    private Context mContext;

    public MyNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);

        mContext = reactContext;
    }

    @Override
    public String getName() {

        //傳回的這個名字是必須的,在rn代碼中需要這個名字來調用該類的方法。
        return "MyNativeModule";
    }

    //函數不能有傳回值,因為被調用的原生代碼是異步的,原生代碼執行結束之後隻能通過回調函數或者發送資訊給rn那邊。

    @ReactMethod
    public void rnCallNative(String msg) {

        Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
    }

    /**
     * 建立給js調用的方法 将網絡請求的結果以回調的方式傳遞給js
     *
     * @param url
     * @param callback
     */
    @ReactMethod
    public void getResult(String url, final Callback callback) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
//                    模拟網絡請求資料的操作
                    String result = "我是請求結果";
                    callback.invoke(true, result);

                } catch (Exception e) {

                }
            }
        }).start();
    }

    @ReactMethod
    public void tryCallBack(String name, String psw, Callback errorCallback, Callback successCallback) {
        try {
            if (TextUtils.isEmpty(name) && TextUtils.isEmpty(psw)) {
                // 失敗時回調
                errorCallback.invoke("user or psw  is empty");
            }
            // 成功時回調
            successCallback.invoke("add user success");
        } catch (IllegalViewOperationException e) {
            // 失敗時回調
            errorCallback.invoke(e.getMessage());
        }
    }

    /**
     * 回調給android端的資料
     *
     * @param callback
     */
    @ReactMethod
    public void renderAndroidData(Callback callback) {
        callback.invoke("android data");
    }
}
           
說明,這裡第一個需要注意的地方是getName()方法,傳回的這個别名,将來在RN的js中需要用到這個别名來調原生的方法。第二個地方,凡是需要RN調用的方法,都需要加

@ReactMethod

注解。第三個地方,原生提供的方法的傳回值類型都是void類型,比較簡單的如

rnCallNative(String msg)

, RN那邊調用,就是吐司一下。如果需要傳回結果就需要回調,如

renderAndroidData(Callback callback)

ok,原生方法算是寫好了,接下來需要建立一個類來實作ReactPackage,将我們建立的類添加進原生子產品清單中。如下所示:

public class MyReactPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {

        List<NativeModule> modules = new ArrayList<>();
        //将我們建立的類添加進原生子產品清單中
        modules.add(new MyNativeModule(reactContext));
        return modules;
    }


    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {

        //傳回值需要修改
        return Collections.emptyList();
    }


    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {

        //傳回值需要修改
        return Collections.emptyList();
    }
}
           

還需要在MainApplication中,将我們建立的包管理器給添加進來 ,如下所示:

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

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
    }
}
           
這樣的話,原生子產品算是寫好了,接下來RN來調用Native方法,将index.android.js中可以這樣調用:
  • 首先 import { NativeModules} from ‘react-native’;
  • 調用原生方法可以這麼去寫

    NativeModules.MyNativeModule.rnCallNative('調用原生方法的Demo');

展示 index.android.js 全部代碼;

export default class RNComponent extends Component {

    // 構造
    constructor(props) {
        super(props);
        // 初始狀态
        this.state = {
            title: '',
        };
    }

     render() {
             tryCall = () => {
                 var rnToastAndroid = NativeModules.MyNativeModule;
                 rnToastAndroid.tryCallBack("luo", "131", (errorCallback) => {
                         alert(errorCallback)
                     },
                     (successCallback) => {
                         alert(successCallback);
                     });
             };
             androidback = () => {

                 var ANdroidNative = NativeModules.MyNativeModule;
                 ANdroidNative.renderAndroidData((Callback) => {
                     alert(Callback);

                 });
             };

             return (

                 <View style={styles.container}>
                     <Text style={styles.welcome}
                           onPress={this.call_button.bind(this)}
                     >
                         React Native 調用原生方法!
                     </Text>
                     <Text style={styles.instructions}
                           onPress={()=>androidback()}
                     >
                         獲得android回調的資料
                     </Text>
                     <Text style={styles.instructions}>

                         {NativeModules.MyNativeModule.rnCallNative(this.state.title)}
                     </Text>

                     <Text style={styles.instructions} onPress={()=>tryCall()}>
                         trycallAndroid
                     </Text>
                 </View>
             );
         }

         call_button() {

             NativeModules.MyNativeModule.rnCallNative('調用原生方法的Demo');
         }

}
const styles = StyleSheet.create({
    container: {
        flex: ,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
    welcome: {
        fontSize: ,
        textAlign: 'center',
        margin: ,
    },
    instructions: {
        textAlign: 'center',
        color: '#333333',
        marginBottom: ,
    },
});

AppRegistry.registerComponent('RNComponent', () => RNComponent);

           

看看整個工程目錄:

Android原生項目接入React Native

最後運作效果:

Android原生項目接入React Native

到此為止,Native接入RN,以及與RN的互動基本上介紹完了,如果還不是很明白,可以下載下傳demo,結合本篇文章應該是可以搞定的。Demo位址github