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ù)具體情況分為以下兩類模塊:
-
react-native與原生互相調(diào)用,無view交互模塊,繼承ReactContextBaseJavaModule,添加交互方法。
-
原生提供view模塊供react-native于js代碼上調(diào)用(包含view屬性設(shè)置),則繼承SimpleViewManager,實(shí)現(xiàn)相應(yīng)邏輯。
兩者均需要實(shí)現(xiàn)ReactPackage接口,用來在初始化react組件的時(shí)候注冊(cè)。
js和原生數(shù)據(jù)類型轉(zhuǎn)換工具類:
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;
}
}
- 自定義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
- 安裝CodePush CLI:
npm install -g code-push-cli
- 注冊(cè)CodePush賬號(hào):
code-push register
- 登錄CodePush賬號(hào)(注冊(cè)完成會(huì)自動(dòng)登錄,無需此步驟):
code-push login
- 創(chuàng)建app關(guān)聯(lián)賬戶:
code-push app add <appName>
-
項(xiàng)目集成codepush SDK(見官方文檔)
兩種方式:
- RNPM
- 手動(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ò)。
發(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