title: RN实战经验总结
前言
在草稿箱中发现了许久之前写的这篇文章,虽然不搞RN已经大半年了,但是之前写过的东西还是还出来作个纪念。如果能帮到别人那再好不过了。
Android中集成RN
这个需要说的都在这里说了,见在原有Android项目中快速集成React Native
关于RN在项目中Android端的预加载
此前曾根据网上的做法并结合最新的RN源码做了一个RN的预加载库,不过在后来发现会出现内存泄漏问题。在集成到项目Android端此着手解决了这一问题。
然而在实际的开发中,几乎有大半的页面是用RN开发,如果全部页面都使用预加载,那么对内存会有很大的压力,而且也没有这个必要。
首先说一下目前项目的页面组织结构,其实就是目前主流的主Activity(带四个Fragment)+其他Activity,主Activity在应用运行期间是一直存在的,这就为预加载提供了一个绝佳的基础。
最终使用预加载的是主Activity【我的】Fragment页面。在RN中加载Fragment并不难,在Android中加载RN,无论是在Activity还是Fragment,加载的都只是一个View而已。而给Fragment设置View,只需要Fragment的onCreateView返回RN的View即可。
具体见:在Android中预加载React Native jsBundle
优化非预加载初始化属性传递
在原本的ReactActivity中传递启动属性可以用以下方式
public class C3RNActivity extends ReactActivity {
public static final String MAIN_COMPONENT_NAME = C3RNActivity.class.getSimpleName();
protected @Nullable
String getMainComponentName() {
return MAIN_COMPONENT_NAME;
}
@Override
protected ReactActivityDelegate createReactActivityDelegate() {
return new ReactActivityDelegate(this, getMainComponentName()) {
@Nullable
@Override
protected Bundle getLaunchOptions() {
Bundle bundle=new Bundle();
//往bundle中添加启动属性键值对
bundle.putString("key","value");
return bundle;
}
};
}
}
这种方式传递是完全没问题的,但是有点局限性。查看ReactActivity的源码,createReactActivityDelegate
是在ReactActivity的构造方法调用(在OnCreate之前)。但这样一来就无法在OnCreate通过getItent获取别的Activity传递过来的参数,因此我们需要对原本的ReactActivity进行改造。将createReactActivityDelegate方法调用从ReactActivity移到onCreate方法中,但是在ReactActivityDelegate的onCreate方法之前。这样我们需要重写ReactActivity而不是直接通过继承创建满足我们要求的ReactActivity。
public class MyReactActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler, PermissionAwareActivity {
private MyReactActivityDelegate mDelegate;
/**
* Returns the name of the main component registered from JavaScript.
* This is used to schedule rendering of the component.
* e.g. "MoviesApp"
*/
protected @Nullable
String getMainComponentName() {
return null;
}
/**
* Called at construction time, override if you have a custom delegate implementation.
*/
protected MyReactActivityDelegate createReactActivityDelegate(final Intent intent) {
return new MyReactActivityDelegate(this, getMainComponentName()){
@Nullable
@Override
protected Bundle getLaunchOptions() {
Bundle bundle=new Bundle();
//在这里将intent参数放入bundle,作为RN的页面启动参数
//例如:
bundle.putString("key",intent.getStringExtra("xxx"));
return bundle;
}
};
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent=getIntent();
mDelegate = createReactActivityDelegate(intent);
mDelegate.onCreate(savedInstanceState);
}
@Override
protected void onPause() {
super.onPause();
mDelegate.onPause();
}
@Override
protected void onResume() {
super.onResume();
mDelegate.onResume();
}
@Override
protected void onDestroy() {
super.onDestroy();
mDelegate.onDestroy();
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
mDelegate.onActivityResult(requestCode, resultCode, data);
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
return mDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event);
}
@Override
public void onBackPressed() {
if (!mDelegate.onBackPressed()) {
super.onBackPressed();
}
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Override
public void onNewIntent(Intent intent) {
if (!mDelegate.onNewIntent(intent)) {
super.onNewIntent(intent);
}
}
@Override
public void requestPermissions(
String[] permissions,
int requestCode,
PermissionListener listener) {
mDelegate.requestPermissions(permissions, requestCode, listener);
}
@Override
public void onRequestPermissionsResult(
int requestCode,
String[] permissions,
int[] grantResults) {
mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
protected final ReactNativeHost getReactNativeHost() {
return mDelegate.getReactNativeHost();
}
protected final ReactInstanceManager getReactInstanceManager() {
return mDelegate.getReactInstanceManager();
}
}
其中,MyReactActivityDelegate 是直接继承ReactActivityDelegate,因为在ReactActivityDelegate中,onCreate,onPause,onDestroy等方法是protect修饰,无法在其他包中引用,所以需要对其复写,实现中只需要调用父类方法即可。
多入口
在本次项目中使用的是多注册方式实现RN的多入口,实际上通过启动属性传递需要打开的RN页面参数也是可以的。不过因为使用多注册实现多入口还是踩了一些坑。在多注册方式下,RN的全局变量在iOS客户端是无效的。也就是说,在一个根组件中给一个全局变量赋值,在另外一个根组件中读取到的全局变量值是空的。而在Android端是没有这个问题。
网络图片加载过渡
这里不得不提这是RN的一个坑,最新的RN都发布到0.5x了,在Android中依然没有支持默认占位图,默认加载错误图,以及加载进度方法。这三个都只有在iOS端有效,在Android端则需要自己手动实现。
网络状况判断
这里不得不提这又是RN的一个坑。在RN中官网推荐判断网络是否可用的方法如下:
NetInfo.isConnected.fetch().done(
(isConnected) => { this.setState({isConnected}); }
);
然而实际上在iOS端,isConnected返回的永远是false。
官方文档说上面这个方法是Android和iOS平台通用的,然而你实际使用的时候在iOS端就会发现问题,即使到了0.51,这个问题仍然存在.....
其实这个解决办法很多,
具体可见:Github issue:iOS: NetInfo.isConnected returns always false
其中一种解决办法如下:
function handleFirstConnectivityChange(isConnected) {
if (!sConnected) {
// do action
}
NetInfo.isConnected.removeEventListener('change', handleFirstConnectivityChange);
}
if (Platform.OS === 'ios') {
NetInfo.isConnected.addEventListener('change', handleFirstConnectivityChange);
} else {
NetInfo.isConnected.fetch().then(isConnected => {
if (!sConnected) {
// do action
}
}
Linking模块在Android release模式下getInitialURL返回为null
这也是一个大坑。在RN中,如果你的应用被其注册过的外部url调起,则可以在任何组件内这样获取和处理它:
componentDidMount() {
Linking.getInitialURL().then((url) => {
if (url) {
console.log('Initial url is: ' + url);
}
}).catch(err => console.error('An error occurred', err));
}
然而在Android端打成Release包时,返回的url偶尔会为空,对,是偶尔,并且概率还比较大,原因暂时未知。所以要通过外部链接和Linking模块来打开RN的话,这种做法是不靠谱的。解决办法是用Android原生的老办法,在Activity的onCreate方法中获取外部链接以及相关参数,并作为启动参数传递给RN。为此,需要重写ReactActivity和ReactActivityDelegate。具体参考:优化非预加载初始化属性传递一节。
总结:
从17年4月份开始接触RN,至今如有大半年时间,在这大半年时间里,从入门学习到实际动手写出一个完整的仿实际产品的App出来花了一个月时间,与当初学习Android相比这个时间短得太多了。到17年6月份在我们公司的天翼云iOS客户端其中一个页面试点使用RN,然后前后花了一个月时间,但实际动手接入项目中与从零开始一个RN项目有很大的不同,期间踩了好几个坑,还好都能及时解决。到17年10月份,在我们公司的产品两个客户端都接入RN并且是重度使用,大概有50%~60%页面是使用RN开发。在这一次接近2个月的开发过程中,对RN简直又爱又恨,踩了大大小小好多个坑,看到了RN的许多不足,也看到了原生与RN无法比拟的一些优势。
先来说一下切身体会的优势:
- 上手快,即使不懂JS,入门也不用太长时间,半个月时间其实就足够了。上手之后,开发效率其实可以很高。
- 跨平台,这是一个巨大的优势,虽然RN的代码不能做到100%两个端复用,但是90%还是没问题的。而自然地,可以节省一定的人力成本。
- 热更新,这一功能在Android中实现比较简单,但是因为苹果爸爸禁用了JSPatch,因此在iOS端能用的热更新方法不多了,而热更新则是其中一个。
- 更新快,这其实是一个优点也可以说是缺点,说它是优点因为勤快地更新则说明RN加了某些新特性或者修复了一些历史遗留的bug,说它是缺点则是因为更新太快,说不定某些API哪天突然就不能用了,代码的写法又不一样了,RN版本升级的时候也是一件比较痛苦的事情。
- 调试方便:可以在谷歌浏览器上面单步调试JS代码,双击R(或摇一摇或CMD+R)就能快速reload代码
不足:
- 开发过程会时不时就踩到坑,RN作为一个还没正式发布1.0版本的框架,有一些bug是必然的。
- 列表控件性能仍不能满足要求,在快速滑动时会看到一些空白项。
- 图片缓存:官方没有很好的支持,第三方库也没有找到比较满意的方案。
- 动画效果不佳:这个众所周知,动画效果需要自己做优化。