2018-07-09 06:52
本周技术文章点评分享
本周的Android Weekly上读到一篇文章React Native: A retrospective from the mobile-engineering team at Udacity,以第一人称视角介绍了Udacity的mobile团队在开发中引入和移除了React Native的经验。
在移动端整合React Native,意味着只要写一套代码(Javascript),就可以同时运行在iOS和Android上。Udacity移动端团队当初也正是看中了这一点,在一个新开发的功能上引入了React Native,以实现更好的跨平台属性和减少开发时间。但后来后续功能没有继续使用它开发,而之前用它开发的功能又被终止、从app中移除了,因此也没有必要再留着React Native相关支持代码,就全部删掉了。
从这个技术调整案例中可以总结出诸多经验。首先能够看出,作为一个小团队(开始引入React Native时,团队共有iOS工程师1人和Android工程师2人),选择技术框架的主导因素往往是工程师本身,他们的iOS和其中一位Android工程师都有Javascript开发经验,因此才有动力和能力去尝试这一框架。而随着团队扩张,iOS增加到4人,Android增加到3人,但新加入的Android工程师并不熟悉Javascript开发。虽然iOS那边4个人中倒有3个可以(考虑到他们应该都是第一个人招进来的也不奇怪),但Android这边继续使用React Native开发的能力和意愿就打了折扣(原文说的是意愿,能力是我加的)。而随着Udacity全球业务的发展,Android的重要性越来越突出(北美市场以外Android仍然是主流,其中低端机、低系统版本、较差的网络坏境所占用户比例是绝对不能忽略的),Android团队也面临着更多挑战,例如本地化支持和减小apk体积等。因此他们必须重新审视React Native对开发过程的影响。
在一个理想世界里,Android整合React Native的框架搭起来,后续只要写Javascript就可以了,甚至不需要Android工程师参与。但现实中并非如此。首先框架也需要不断维护(例如更新版本并解决随之而来的兼容性问题,因为React Native不向后兼容);其次在开发中常常会遇到平台间UI/UX的差异,需要专门为Android端去修改React Native库中的代码;同时还得考虑React Native在不同的屏幕尺寸和在低端机型(说起Android开发就绕不开这两个问题)上的表现;以及React Native的本地化也和Android自身的方法不同,需要额外搭建维护,不仅增加了维护成本(对工程师来说倒也不算什么)还增加了和本地化团队的沟通成本(工程师:NO!我不要和人类开会!)。最后他们发现,使用React Native节省的时间往往都被解决上述问题消耗掉了。
同时他们也吐槽了React Native自身的一些问题:文档缺失,性能不佳,移动端支持滞后、以及不向后兼容等等。
恰逢已开发功能被取消移除,团队适时地选择了和React Native分手。在移除全部React Native相关代码后,Android app在CI上的build时间从15分钟降到了12分钟,apk体积从28M降到了18M。
(作为Udacity iOS端用户之一,我看完此文后表示终于了解为什么他们的app提供的功能这么少了,原来团队才这么几个人啊……)
本周代码心得
本周基本完成了blog事先预想的功能。目前编辑页面可以用,可以插入各种标记,最后一键复制到剪贴板。
实现编辑按钮的功能时,使用了selectionStart
和selectionEnd
的方法获取当前选取位置,然后用正则表达式在其前后加上标记字符。例如插入一段代码或将一段文字标记为代码块的方法如下:
var onBtnCodeClick = function () {
var text = textInput.val();// textInput是一个JQuery对象
var start = textInput[0].selectionStart;
var end = textInput[0].selectionEnd;
if ((start === 0 || text.charAt(start - 1) === '\n') && (end === text.length || text.charAt(end) === '\n')) {
// Block code
var selectText = start === end ? "for (int i = 0; i < 100; i++) {\n System.out.println(i);\n}" : text.slice(start, end);
cachedText = text;
textInput.val(text.slice(0, start).concat(`\n\`\`\`\n${selectText}\n\`\`\`\n`).concat(text.slice(end, text.length)));
} else {
// Inline code
var selectText = start === end ? "inline code" : text.slice(start, end);
if (selectText.indexOf('\n') === -1) {
cachedText = text;
textInput.val(text.slice(0, start).concat(`\`\`${selectText}\`\``).concat(text.slice(end, text.length)));
}
}
}
selectionStart
有浏览器兼容问题。考虑到事实上目前唯一的用户只用Chrome,最多偶尔用一下Safari和Edge,所以问题不大。
内容展示页面那边,同样使用了正则表达式实现将一个段落之中的多处标记转化为HTML tag。代码如下
var parsePlainText = function (text) {
return text
.replace(/\*\*[^\n]*?\*\*/g, match => `<strong>${match.slice(2, -2)}</strong>`)
.replace(/\/\/[^\n]*?\/\//g, match => `<em>${match.slice(2, -2)}</em>`)
.replace(/\`\`[^\n]*?\`\`/g, match => `<code>${match.slice(2, -2)}</code>`)
.replace(/\[\[[^\n]*?\]\]/g, match => {
var [link, words] = match.slice(2, -2).split('|', 2);
return `<a href="${link}" target="_blank">${words}</a>`;
})
.replace(/\n/g, "<br>");
}
同时在url中加入了“from=”参数,用于控制文章列表从最新的第几篇文章开始显示。目前从列表中点开一篇文章相当于用新url刷新页面,此时应记录列表翻页情况写入url中,使得页面刷新后,列表仍在刷新前的那一页。