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下载,否则国内需要翻墙
|
|
生成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模块的加载入口,可以注册多个,原生程序加载模块时,对应模块相应的名称即可。
|
|
其余步骤可参照文档来。
命令解释:
$ 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加入引用
|
|
后续等操作参照链接文档。
Application示例:
|
|
BaseReactActivity示例:
|
|
ReactViewContainActivity示例:
该Activity可以包含任意React native的Component。
“target”中传打开React native的Component名称,String类型;“params”中传打开React native的Component所需要的参数,Bundle类型。
|
|
BaseReactFragment示例:
|
|
Fragment用法:
|
|
3. react-native、原生模块相互调用
根据具体情况分为以下两类模块:
react-native与原生互相调用,无view交互模块,继承ReactContextBaseJavaModule,添加交互方法。
原生提供view模块供react-native于js代码上调用(包含view属性设置),则继承SimpleViewManager,实现相应逻辑。
两者均需要实现ReactPackage接口,用来在初始化react组件的时候注册。
js和原生数据类型转换工具类:
com.facebook.react.bridge.Arguments
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174public 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}.*/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 t8873322throw new UnsupportedOperationException("Arrays aren't supported yet.");default:throw new IllegalArgumentException("Could not convert object with key: " + key + ".");}}return bundle;}}自定义ConversionUtil
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121/* 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
安装CodePush CLI:
npm install -g code-push-cli
注册CodePush账号:
code-push register
登录CodePush账号(注册完成会自动登录,无需此步骤):
code-push login
创建app关联账户:
code-push app add
项目集成codepush SDK(见官方文档)
两种方式:
- RNPM
- 手动集成
建议使用手动集成,rnpm的作用是自动添加相关的内容至项目中,但是如果项目名称等和模板不一样,会添加失败,到头来还是要手动修改。
注意事项:项目版本号必须为三位,例如 1.0.0,其他格式会出错。
发布app bundle:
code-push release-react
5. jenkins自动打包的坑
release打包取消勾选该选项:
否则会中断打包进程,报类似以下错误:
1234567891011121314151617 > 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>