android react-native 开发小结(基于react-native 0.39)

1. 开发环境搭建

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

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

简要说明,省略android环境搭建过程:

mac:

Homebrew(包管理)——>node(js开发,包含npm命令),watchman(文件变动检测),flow(Flow是一个静态的JS类型检查工具)——>react-native-cli(react-native环境,使用npm命令安装)

windows:

Chocolatey(包管理)——>Python 2,node(js开发,包含npm命令)——>react-native-cli(react-native,使用npm命令安装)

可选安装:

淘宝镜像,可加速node module下载,否则国内需要翻墙

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

生成react-native应用:

react-native init AwesomeProject(初始化项目,项目名称AwesomeProject)
cd AwesomeProject(进入项目目录)
react-native start(开启node服务)
react-native run-android(打包生成apk并安装)

2. 已有的原生应用,如何结合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

项目结构调整:

参照AwesomeProject目录结构,原有的代码需放在android目录下:

AwesomeProject(名称可以根据自己需要) android(名称不要修改) app
index.android.js
ios
index.ios.js
node_modules
package.json

package.json用来记录项目中使用到的node_modules的依赖,在AwesomeProject(项目根目录)目录下执行npm install可以下载生成node_modules目录。也就是说node_modules是通过执行命令来生成的,不是项目本身的代码。
index.android.js用来记录react-native模块的加载入口,可以注册多个,原生程序加载模块时,对应模块相应的名称即可。

AppRegistry.registerComponent('HomePage', () => AppointContainer);//首页
AppRegistry.registerComponent('NewsList', () => Root); //资讯主页面
AppRegistry.registerComponent('subInfo', () => subInfoRoot);//资讯的子页面

其余步骤可参照文档来。

命令解释:

$ npm init(生成package.json文件,填写相关信息)
$ npm install --save react react-native(下载并在项目中引用react-native模块,--save表示记录写入package.json)
$ curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig (添加flow配置,Flow是一个静态的JS类型检查工具)

建议操作方式:

先使用react-native init AwesomeProject初始化一个项目,删除其中的android目录下源码,拷贝原有原生项目代码自android目录下,修改AwesomeProject名称(可选),由于已经有了初始化的package.json,无需再进行npm init操作,直接根目录下执行npm install。然后参照文档配置原生项目,原生项目build.gradle加入引用

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

后续等操作参照链接文档。

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等参数
    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,否则会内存泄露
      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所需要的参数,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,否则会内存泄露
      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、原生模块相互调用

根据具体情况分为以下两类模块:

  1. react-native与原生互相调用,无view交互模块,继承ReactContextBaseJavaModule,添加交互方法。

    可参照:http://www.jianshu.com/p/07b928feee3b

  2. 原生提供view模块供react-native于js代码上调用(包含view属性设置),则继承SimpleViewManager,实现相应逻辑。

    可参照:http://www.jianshu.com/p/31c4306a55ff

两者均需要实现ReactPackage接口,用来在初始化react组件的时候注册。

js和原生数据类型转换工具类:

  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. 如何发布react-native开发的相关js代码

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

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

参考文章:http://www.jianshu.com/p/9e3b4a133bcc

  1. 安装CodePush CLI:

npm install -g code-push-cli

  1. 注册CodePush账号:

code-push register

  1. 登录CodePush账号(注册完成会自动登录,无需此步骤):

code-push login

  1. 创建app关联账户:

code-push app add <appName>

  1. 项目集成codepush SDK(见官方文档)

    两种方式:

    1. RNPM
    2. 手动集成

    建议使用手动集成,rnpm的作用是自动添加相关的内容至项目中,但是如果项目名称等和模板不一样,会添加失败,到头来还是要手动修改。

    注意事项:项目版本号必须为三位,例如 1.0.0,其他格式会出错。

  2. 发布app bundle:

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

5. jenkins自动打包的坑

release打包取消勾选该选项:

否则会中断打包进程,报类似以下错误:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容