android react-native 開發(fā)小結(jié)(基于react-native 0.39)

1. 開發(fā)環(huán)境搭建

英文文檔:https://facebook.github.io/react-native/docs/getting-started.html

中文文檔:http://reactnative.cn/docs/0.39/getting-started.html

簡要說明,省略android環(huán)境搭建過程:

mac:

Homebrew(包管理)——>node(js開發(fā),包含npm命令),watchman(文件變動(dòng)檢測),flow(Flow是一個(gè)靜態(tài)的JS類型檢查工具)——>react-native-cli(react-native環(huán)境,使用npm命令安裝)

windows:

Chocolatey(包管理)——>Python 2,node(js開發(fā),包含npm命令)——>react-native-cli(react-native,使用npm命令安裝)

可選安裝:

淘寶鏡像,可加速node module下載,否則國內(nèi)需要翻墻

npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global

生成react-native應(yīng)用:

react-native init AwesomeProject(初始化項(xiàng)目,項(xiàng)目名稱AwesomeProject)
cd AwesomeProject(進(jìn)入項(xiàng)目目錄)
react-native start(開啟node服務(wù))
react-native run-android(打包生成apk并安裝)

2. 已有的原生應(yīng)用,如何結(jié)合react-native

英文文檔:https://facebook.github.io/react-native/docs/integration-with-existing-apps.html

中文文檔:http://reactnative.cn/docs/0.39/integration-with-existing-apps.html#content

項(xiàng)目結(jié)構(gòu)調(diào)整:

參照AwesomeProject目錄結(jié)構(gòu),原有的代碼需放在android目錄下:

AwesomeProject(名稱可以根據(jù)自己需要) android(名稱不要修改) app
index.android.js
ios
index.ios.js
node_modules
package.json

package.json用來記錄項(xiàng)目中使用到的node_modules的依賴,在AwesomeProject(項(xiàng)目根目錄)目錄下執(zhí)行npm install可以下載生成node_modules目錄。也就是說node_modules是通過執(zhí)行命令來生成的,不是項(xiàng)目本身的代碼。
index.android.js用來記錄react-native模塊的加載入口,可以注冊(cè)多個(gè),原生程序加載模塊時(shí),對(duì)應(yīng)模塊相應(yīng)的名稱即可。

AppRegistry.registerComponent('HomePage', () => AppointContainer);//首頁
AppRegistry.registerComponent('NewsList', () => Root); //資訊主頁面
AppRegistry.registerComponent('subInfo', () => subInfoRoot);//資訊的子頁面

其余步驟可參照文檔來。

命令解釋:

$ npm init(生成package.json文件,填寫相關(guān)信息)
$ npm install --save react react-native(下載并在項(xiàng)目中引用react-native模塊,--save表示記錄寫入package.json)
$ curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig (添加flow配置,Flow是一個(gè)靜態(tài)的JS類型檢查工具)

建議操作方式:

先使用react-native init AwesomeProject初始化一個(gè)項(xiàng)目,刪除其中的android目錄下源碼,拷貝原有原生項(xiàng)目代碼自android目錄下,修改AwesomeProject名稱(可選),由于已經(jīng)有了初始化的package.json,無需再進(jìn)行npm init操作,直接根目錄下執(zhí)行npm install。然后參照文檔配置原生項(xiàng)目,原生項(xiàng)目build.gradle加入引用

dependencies {
     ...
     compile "com.facebook.react:react-native:+" // From node_modules.
 }

后續(xù)等操作參照鏈接文檔。

Application示例:

public class MyApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
    @Override public boolean getUseDeveloperSupport() {
      return BuildConfig.DEBUG;
    }

    // 2. Override the getJSBundleFile method in order to let
    // the CodePush runtime determine where to get the JS
    // bundle location from on each app start
    @Override protected String getJSBundleFile() {
      Log.d("code-push", CodePush.getJSBundleFile());
      return CodePush.getJSBundleFile();
    }

    @Override public List<ReactPackage> getPackages() {
      // 3. Instantiate an instance of the CodePush runtime and add it to the list of
      // existing packages, specifying the right deployment key. If you don't already
      // have it, you can run "code-push deployment ls <appName> -k" to retrieve your key.

      Log.d("code-push", BuildConfig.CODEPUSH_KEY);

      CodePush codePush = new CodePush(BuildConfig.CODEPUSH_KEY, MyApplication.this,
          BuildConfig.DEBUG);
      String appversion = codePush.getAppVersion();
      String serverurl = codePush.getServerUrl();

      Log.d("code-push", "appversion->" + appversion);
      Log.d("code-push", "serverurl->" + serverurl);

      return Arrays.asList(new MainReactPackage(), new ZMCNativeBridgeReactPackage(), codePush);
    }
  };

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

BaseReactActivity示例:

public abstract class BaseReactActivity extends AppCompatActivity
    implements DefaultHardwareBackBtnHandler {
  private ReactRootView mReactRootView;
  private ReactInstanceManager mReactInstanceManager;

  // This method returns the name of our top-level component to show
  public abstract String getMainComponentName();

  public abstract Bundle getMainComponentParams();

  public abstract void parseActivityParams();

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    //用來解析bundle等參數(shù)
    parseActivityParams();

    mReactRootView = new ReactRootView(this);
    mReactInstanceManager = ((MyApplication) getApplication()).
        getReactNativeHost().getReactInstanceManager();

    mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(),
        getMainComponentParams());

    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) {
      //這里需要unmount,否則會(huì)內(nèi)存泄露
      mReactRootView.unmountReactApplication();
      mReactRootView = null;
    }

    if (mReactInstanceManager != null) {
      mReactInstanceManager.onHostDestroy(this);
      mReactInstanceManager = null;
    }
  }

  @Override public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (mReactInstanceManager != null) {
      mReactInstanceManager.onActivityResult(this, requestCode, resultCode, data);
    }
  }

  @Override public void onBackPressed() {
    if (mReactInstanceManager != null) {
      mReactInstanceManager.onBackPressed();
    } else {
      super.onBackPressed();
    }
  }
}

ReactViewContainActivity示例:

該Activity可以包含任意React native的Component。

“target”中傳打開React native的Component名稱,String類型;“params”中傳打開React native的Component所需要的參數(shù),Bundle類型。

public class ReactViewContainActivity extends BaseReactActivity {

  private String targetComponentName;
  private Bundle targetParams;

  @Override public String getMainComponentName() {
    return targetComponentName;
  }

  @Override public Bundle getMainComponentParams() {
    return targetParams;
  }

  @Override public void parseActivityParams() {
    Bundle bundle = getIntent().getExtras();
    targetComponentName = bundle.getString("target");
    targetParams = bundle.getBundle("params");
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
  }
}

BaseReactFragment示例:

public abstract class BaseReactFragment extends Fragment {
  private ReactRootView mReactRootView;
  private ReactInstanceManager mReactInstanceManager;

  // This method returns the name of our top-level component to show
  public abstract String getMainComponentName();

  public abstract Bundle getMainComponentParams();

  @Override public void onAttach(Context context) {
    super.onAttach(context);
    mReactRootView = new ReactRootView(context);
    mReactInstanceManager = ((MyApplication) getActivity().getApplication()).
        getReactNativeHost().getReactInstanceManager();
  }

  @Nullable @Override
  public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
      @Nullable Bundle savedInstanceState) {
    return mReactRootView;
  }

  @Override public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(),
        getMainComponentParams());
  }

  @Override public void onDestroy() {
    super.onDestroy();

    if (mReactRootView != null) {
      //這里需要unmount,否則會(huì)內(nèi)存泄露
      mReactRootView.unmountReactApplication();
      mReactRootView = null;
    }

    if (mReactInstanceManager != null) {
      mReactInstanceManager = null;
    }
  }

  protected ReactRootView getmReactRootView() {
    return mReactRootView;
  }
}

Fragment用法:

public class NewsReactFragment extends BaseReactFragment {

  @Override public String getMainComponentName() {
    return "NewsList";
  }

  @Override public Bundle getMainComponentParams() {
    return null;
  }
}

3. react-native、原生模塊相互調(diào)用

根據(jù)具體情況分為以下兩類模塊:

  1. react-native與原生互相調(diào)用,無view交互模塊,繼承ReactContextBaseJavaModule,添加交互方法。

    可參照:http://m.itdecent.cn/p/07b928feee3b

  2. 原生提供view模塊供react-native于js代碼上調(diào)用(包含view屬性設(shè)置),則繼承SimpleViewManager,實(shí)現(xiàn)相應(yīng)邏輯。

    可參照:http://m.itdecent.cn/p/31c4306a55ff

兩者均需要實(shí)現(xiàn)ReactPackage接口,用來在初始化react組件的時(shí)候注冊(cè)。

js和原生數(shù)據(jù)類型轉(zhuǎn)換工具類:

  1. com.facebook.react.bridge.Arguments

   public class Arguments {

     /**
      * This method should be used when you need to stub out creating NativeArrays in unit tests.
      */
     public static WritableArray createArray() {
       return new WritableNativeArray();
     }

     /**
      * This method should be used when you need to stub out creating NativeMaps in unit tests.
      */
     public static WritableMap createMap() {
       return new WritableNativeMap();
     }

     public static WritableNativeArray fromJavaArgs(Object[] args) {
       WritableNativeArray arguments = new WritableNativeArray();
       for (int i = 0; i < args.length; i++) {
         Object argument = args[i];
         if (argument == null) {
           arguments.pushNull();
           continue;
         }

         Class argumentClass = argument.getClass();
         if (argumentClass == Boolean.class) {
           arguments.pushBoolean(((Boolean) argument).booleanValue());
         } else if (argumentClass == Integer.class) {
           arguments.pushDouble(((Integer) argument).doubleValue());
         } else if (argumentClass == Double.class) {
           arguments.pushDouble(((Double) argument).doubleValue());
         } else if (argumentClass == Float.class) {
           arguments.pushDouble(((Float) argument).doubleValue());
         } else if (argumentClass == String.class) {
           arguments.pushString(argument.toString());
         } else if (argumentClass == WritableNativeMap.class) {
           arguments.pushMap((WritableNativeMap) argument);
         } else if (argumentClass == WritableNativeArray.class) {
           arguments.pushArray((WritableNativeArray) argument);
         } else {
           throw new RuntimeException("Cannot convert argument of type " + argumentClass);
         }
       }
       return arguments;
     }

     /**
      * Convert an array to a {@link WritableArray}.
      *
      * @param array the array to convert. Supported types are: {@code String[]}, {@code Bundle[]},
      * {@code int[]}, {@code float[]}, {@code double[]}, {@code boolean[]}.
      *
      * @return the converted {@link WritableArray}
      * @throws IllegalArgumentException if the passed object is none of the above types
      */
     public static WritableArray fromArray(Object array) {
       WritableArray catalystArray = createArray();
       if (array instanceof String[]) {
         for (String v: (String[]) array) {
           catalystArray.pushString(v);
         }
       } else if (array instanceof Bundle[]) {
         for (Bundle v: (Bundle[]) array) {
           catalystArray.pushMap(fromBundle(v));
         }
       } else if (array instanceof int[]) {
         for (int v: (int[]) array) {
           catalystArray.pushInt(v);
         }
       } else if (array instanceof float[]) {
         for (float v: (float[]) array) {
           catalystArray.pushDouble(v);
         }
       } else if (array instanceof double[]) {
         for (double v: (double[]) array) {
           catalystArray.pushDouble(v);
         }
       } else if (array instanceof boolean[]) {
         for (boolean v: (boolean[]) array) {
           catalystArray.pushBoolean(v);
         }
       } else {
         throw new IllegalArgumentException("Unknown array type " + array.getClass());
       }
       return catalystArray;
     }

     /**
      * Convert a {@link Bundle} to a {@link WritableMap}. Supported key types in the bundle
      * are:
      *
      * <ul>
      *   <li>primitive types: int, float, double, boolean</li>
      *   <li>arrays supported by {@link #fromArray(Object)}</li>
      *   <li>{@link Bundle} objects that are recursively converted to maps</li>
      * </ul>
      *
      * @param bundle the {@link Bundle} to convert
      * @return the converted {@link WritableMap}
      * @throws IllegalArgumentException if there are keys of unsupported types
      */
     public static WritableMap fromBundle(Bundle bundle) {
       WritableMap map = createMap();
       for (String key: bundle.keySet()) {
         Object value = bundle.get(key);
         if (value == null) {
           map.putNull(key);
         } else if (value.getClass().isArray()) {
           map.putArray(key, fromArray(value));
         } else if (value instanceof String) {
           map.putString(key, (String) value);
         } else if (value instanceof Number) {
           if (value instanceof Integer) {
             map.putInt(key, (Integer) value);
           } else {
             map.putDouble(key, ((Number) value).doubleValue());
           }
         } else if (value instanceof Boolean) {
           map.putBoolean(key, (Boolean) value);
         } else if (value instanceof Bundle) {
           map.putMap(key, fromBundle((Bundle) value));
         } else {
           throw new IllegalArgumentException("Could not convert " + value.getClass());
         }
       }
       return map;
     }

     /**
      * Convert a {@link WritableMap} to a {@link Bundle}.
      * @param readableMap the {@link WritableMap} to convert.
      * @return the converted {@link Bundle}.
      */
     @Nullable
     public static Bundle toBundle(@Nullable ReadableMap readableMap) {
       if (readableMap == null) {
         return null;
       }

       ReadableMapKeySetIterator iterator = readableMap.keySetIterator();

       Bundle bundle = new Bundle();
       while (iterator.hasNextKey()) {
         String key = iterator.nextKey();
         ReadableType readableType = readableMap.getType(key);
         switch (readableType) {
           case Null:
             bundle.putString(key, null);
             break;
           case Boolean:
             bundle.putBoolean(key, readableMap.getBoolean(key));
             break;
           case Number:
             // Can be int or double.
             bundle.putDouble(key, readableMap.getDouble(key));
             break;
           case String:
             bundle.putString(key, readableMap.getString(key));
             break;
           case Map:
             bundle.putBundle(key, toBundle(readableMap.getMap(key)));
             break;
           case Array:
             // TODO t8873322
             throw new UnsupportedOperationException("Arrays aren't supported yet.");
           default:
             throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
         }
       }

       return bundle;
     }
   }
  1. 自定義ConversionUtil
/* package */ final class ConversionUtil {
  /**
   * toObject extracts a value from a {@link ReadableMap} by its key,
   * and returns a POJO representing that object.
   *
   * @param readableMap The Map to containing the value to be converted
   * @param key The key for the value to be converted
   * @return The converted POJO
   */
  public static Object toObject(@Nullable ReadableMap readableMap, String key) {
    if (readableMap == null) {
      return null;
    }

    Object result;

    ReadableType readableType = readableMap.getType(key);
    switch (readableType) {
      case Null:
        result = key;
        break;
      case Boolean:
        result = readableMap.getBoolean(key);
        break;
      case Number:
        // Can be int or double.
        double tmp = readableMap.getDouble(key);
        if (tmp == (int) tmp) {
          result = (int) tmp;
        } else {
          result = tmp;
        }
        break;
      case String:
        result = readableMap.getString(key);
        break;
      case Map:
        result = toMap(readableMap.getMap(key));
        break;
      case Array:
        result = toList(readableMap.getArray(key));
        break;
      default:
        throw new IllegalArgumentException("Could not convert object with key: " + key + ".");
    }

    return result;
  }

  /**
   * toMap converts a {@link ReadableMap} into a HashMap.
   *
   * @param readableMap The ReadableMap to be conveted.
   * @return A HashMap containing the data that was in the ReadableMap.
   */
  public static Map<String, Object> toMap(@Nullable ReadableMap readableMap) {
    if (readableMap == null) {
      return null;
    }

    ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
    if (!iterator.hasNextKey()) {
      return null;
    }

    Map<String, Object> result = new HashMap<>();
    while (iterator.hasNextKey()) {
      String key = iterator.nextKey();
      result.put(key, toObject(readableMap, key));
    }

    return result;
  }

  /**
   * toList converts a {@link ReadableArray} into an ArrayList.
   *
   * @param readableArray The ReadableArray to be conveted.
   * @return An ArrayList containing the data that was in the ReadableArray.
   */
  public static List<Object> toList(@Nullable ReadableArray readableArray) {
    if (readableArray == null) {
      return null;
    }

    List<Object> result = new ArrayList<>(readableArray.size());
    for (int index = 0; index < readableArray.size(); index++) {
      ReadableType readableType = readableArray.getType(index);
      switch (readableType) {
        case Null:
          result.add(String.valueOf(index));
          break;
        case Boolean:
          result.add(readableArray.getBoolean(index));
          break;
        case Number:
          // Can be int or double.
          double tmp = readableArray.getDouble(index);
          if (tmp == (int) tmp) {
            result.add((int) tmp);
          } else {
            result.add(tmp);
          }
          break;
        case String:
          result.add(readableArray.getString(index));
          break;
        case Map:
          result.add(toMap(readableArray.getMap(index)));
          break;
        case Array:
          result = toList(readableArray.getArray(index));
          break;
        default:
          throw new IllegalArgumentException("Could not convert object with index: " + index + ".");
      }
    }

    return result;
  }
}

4. 如何發(fā)布react-native開發(fā)的相關(guān)js代碼

使用codepush:https://www.codeproject.com/

官方文檔:https://microsoft.github.io/code-push/docs/getting-started.html

參考文章:http://m.itdecent.cn/p/9e3b4a133bcc

  1. 安裝CodePush CLI:

npm install -g code-push-cli

  1. 注冊(cè)CodePush賬號(hào):

code-push register

  1. 登錄CodePush賬號(hào)(注冊(cè)完成會(huì)自動(dòng)登錄,無需此步驟):

code-push login

  1. 創(chuàng)建app關(guān)聯(lián)賬戶:

code-push app add <appName>

  1. 項(xiàng)目集成codepush SDK(見官方文檔)

    兩種方式:

    1. RNPM
    2. 手動(dòng)集成

    建議使用手動(dòng)集成,rnpm的作用是自動(dòng)添加相關(guān)的內(nèi)容至項(xiàng)目中,但是如果項(xiàng)目名稱等和模板不一樣,會(huì)添加失敗,到頭來還是要手動(dòng)修改。

    注意事項(xiàng):項(xiàng)目版本號(hào)必須為三位,例如 1.0.0,其他格式會(huì)出錯(cuò)。

  2. 發(fā)布app bundle:

code-push release-react <appName> <platform>

5. jenkins自動(dòng)打包的坑

release打包取消勾選該選項(xiàng):

否則會(huì)中斷打包進(jìn)程,報(bào)類似以下錯(cuò)誤:

19:08:12 :app:bundleZmlearnReleaseJsAndAssets
19:08:12 FAILURE: Build failed with an exception.
19:08:12 
19:08:12 * What went wrong:
19:08:12 Failed to capture snapshot of input files for task 'bundleZmlearnReleaseJsAndAssets' during up-to-date check.
19:08:12 > Failed to create MD5 hash for file E:\jenkins\workspace\zmlearn_android_am_release\caches\2.14.1\classAnalysis\cache.properties.lock.
19:08:12 
19:08:12 * Try:
19:08:12 Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output.
19:08:12 
19:08:12 
19:08:12 BUILD FAILED
19:08:12 
19:08:12 Total time: 4 mins 39.797 secs
19:08:12 Build step 'Invoke Gradle script' changed build result to FAILURE
19:08:12 Build step 'Invoke Gradle script' marked build as failure

原因可參見:https://discuss.gradle.org/t/build-failure-with-failed-to-capture-snapshot-of-input-files-for-task-war-during-up-to-date-check/9132

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容