记一次WebView填坑过程--由换行符引发的血案

2019-07-27

记一次WebView填坑过程--由换行符引发的血案

最近使用WebView掉坑了,然后艰难爬坑经历感触很深,写出来大家借鉴一下。

需求

我们有个网页需要用到很多js库,这些库比较大,而且基本上是不变的。为了提高性能,将这些网页和JS库放到本地,进行加载。变的数据从服务器获取,然后跟本地的HTML组装后显示。这种需求还是挺普遍的。

实现方式

由web的同事调试好HTML文件,他们把所有的HTML,js,css,image和资源一起打包给我们 我们把这些资源放到assets目录下(也可以专门在assets再建一个子目录来放)

采用Android自带的WebView来加载和显示这些网页。有两个方法loadUrlloadDataWithBaseUrl方法。

1.loadUrl("file:///android_asset/xxxxxx.html")这种方式适合HTML不需要改变的情况,直接加载展示,省时省力

2.loadDataWithBaseUrl方式需要先把HTML内容现在到内存,然后再展示。这种方式可以随意修改HTML里的内容,修改好再交由WebView展示,灵活性强。

遇到的坑

测试的时候,我们遇到同一个HTML文件,通过loadUrl方式加载展示,没有任何问题。但是通过loadDataWithBaseUrl加载展示,HTML的内容死活展示不出来

爬坑过程

    1. 反复确认loadDataWithBaseURL的用法有没有用对,baseUrl参数没有问题。 mWebView.loadDataWithBaseURL("file:///android_asset/", htmlContent, "text/html", "utf-8", null). 网上搜资料看看别人的用法,还有查看我们工程里其他用了loadDataWithBaseURL方法的地方,都是差不多的。然后我怀疑是htmlContent有问题,是不是读取的时候出错了。 我去看从asset里读取文件的方法.
public static String getStringFromAssets(Context context,String fileName) {
        try {
            InputStreamReader inputReader = new InputStreamReader(context.getAssets().open(fileName));
            BufferedReader bufReader = new BufferedReader(inputReader);

            String line;
            StringBuilder stringBuilder = new StringBuilder();
            while ((line = bufReader.readLine()) != null) {
                stringBuilder.append(line);
            }
            return stringBuilder.toString();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

看上去并没有什么问题呀。我搜了下这个方法在我们工程里的使用,发现还有很多使用的地方,他们使用都没问题。我不放弃,到网上再搜一下。全是同样的方法。我断点调试,并且把htmlContent打印出来,好像没有太大的问题呀,不是这里的问题?

  • 2.查看我们工程里其他用了loadDataWithBaseURL方法的地方,我的用法跟他们一致。不过最终我发现我用的HTML文件跟他们用的HTML文件有不同的地方:我使用的HTML文件中 更复杂一些,在文件里引用JS资源文件,图片文件,在HTML里本身内嵌了JS代码。我猜测是内嵌JS导致的。我网上搜资料,看看是不是这样的。终于搜到一篇文章说这个问题,但是这个帖子是很久以前的。下面是它的原话:

使用WebView的loadUrl(url),网页中的Javascript运行正常。但是获取url的html内容后,使用loadData或者loadDataWithBaseURL之后,Javascript就不起作用了!google了一圈,似乎这是普遍问题,不知道这里的高手有法子解决不?

貌似验证了我的想法。但是这是很久以前的帖子呀。我想Google改不会那么傻吧。后来我调试时在logcat里看到一些日志。加载网页时打印了类似下面的日志

[INFO:CONSOLE(1)] "Uncaught SyntaxError: Unexpected identifier "

说明网页在解析JS的时候报错了。 我看了下网页中JavaScript,按道理的这些都是正确的,因为采用loadUrl方式加载没问题,直接在电脑上浏览器打开也没有问题。我把这些js都删掉,只加了一条console.log('1111')的打印语句。再次运行加载,没报错了!!说明内嵌JavaScript代码是可行的。那就是我们原本的JavaScript有问题了??

我把那部分JS放到一个新的单独的文件里。因为我们的HTML文件本身也引用了其他的独立JS文件。一运行,正常加载显示!JS还是那个JS,换到文件里就没问题了?

所以我那时得出结论: * 内嵌JavaScript代码在WebView的解析跟独立JS文件不太一样,可能内嵌JavaScript代码的解析要严格一些?

我开始验证这个想法,去修改内嵌JavaScript代码。 我在很多地方加了console打印,看看执行到哪里报错。然后我发现貌似去掉了注释,报错就不一样了。

Uncaught SyntaxError: Unexpected token。

Uncaught SyntaxError: Unexpected end of input

最后我把注释都去掉,把该加分号的地方都加上。就能正常运行了。 我初步得出以下结论:

需要HTML文档里内嵌JavaScript代码的话,要非常注意JavaScript的语法问题。WebView对这部分的JS代码语法要求非常严,经常发生识别不了而报错的情况。 现在发现①非常注意表达式后面要加分号,不能省略分号!!②不能在JS里随便加注释。要仔细检查语法,严格遵循JS标准

我把这些结论还加到了我们代码的注释了,以免后人采坑。

到这里问题解决了,我把HTML从asset中加载出来,再HTML字符串中的一些字符串(默认数据)替换成我们从服务器取到的数据(真实用户数据),然后调用loadDataWithBaseURL方法展示。完美。

到这里有些人会说loadUrl方法不是没问题吗,直接用它不就行了,折腾那么多干嘛。确实是如此,用loadUrl方法能实现我的需求,实际上我也试用了这个方法,没问题。 不过采用loadUrl的方式,实现起来不太一样。需要修改JavaScript代码。这些HTML是有web同时提供的,他们都是已经默认数据直接调试好了,一打开就能展示。采用loadUrl的方法,你不能预先修改HTML文件,只能在加载完HTML数据之后,在WebView的webclient回调方法onPageFinished里去调用JS方法。

就是说:

  • 1.你将JS里的window.onload方法去掉,让他们先不展示
  • 2.你新增一个js方法,这方法接收一些参数,然后在这个方法里调用渲染方法(即原来的window.onload里的方法)。
  • 3.在你Java代码里,在WebView的webclient回调方法onPageFinished里去调用你新增的JS方法,把参数都传入进去。

看到没有,这里要修改的东西还蛮多,还要要求客户端开发人员懂JS代码。

采用loadDataWithBaseURL方式,理想的情况下,不需要改JS代码,直接告诉开发人员默认数据在哪里,把HTML加载进内存的时候,通过String.replace方法,把默认数据替换为真实数据,就可以了。

iOS端就是这么实现的,但是Android这边实现的时候就遇到了坑。。。。 我肯定想两端实现方式保持一致,所有优先采用loadDataWithBaseURL方式。当然,你也可以让web开发人员按你的要求写好,那你也不用改动那么多。。。

背后真相

问题解决了,版本也发了。但问题的真相真的是WebView解析内嵌JavaScript比较严格吗?从现象来看,不支持注释,不能省略表达式的分号,这明显是换行问题。 所以我想问题可能真的出现在从asset里读取文件地方。看代码就是没有明显错误,一行一行的读取进来,拼接好全部返回。网上查到的所有代码都是这么读的。

那一次性读取整个文件进来呢?所有我找了下资料,将代码改成一次性读取整个文件。

public static String getStringFromAssets(ontext context,String fileName) {
        try {
            InputStreamReader inputReader = new InputStreamReader(context.getAssets().open(fileName));
            BufferedReader bufReader = new BufferedReader(inputReader);

            StringWriter out = new StringWriter();
            copy(bufReader, out);
            return out.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    private static void copy(Reader in, Writer out) throws IOException {
        int c = -1;
        while((c = in.read()) != -1) {
            out.write(c);
        }
    }

果然什么问题都没有!看logcat打印出来的内容也是跟HTML里的内容一模一样,之前方式打印出来的没有换行并没有太过在意!

那只能说明bufReader.readLine的方法有问题,我网上搜了下,说这个方法会自动去掉换行。原来真相是这个么??原来WebView一直是个背锅侠呀,而且这个锅背了好多年😂😂。

我们来看看时间的真凶readLine方法的声明:

/**
     * Reads a line of text.  A line is considered to be terminated by any one
     * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
     * followed immediately by a linefeed.
     *
     * @return     A String containing the contents of the line, not including
     *             any line-termination characters, or null if the end of the
     *             stream has been reached
     *
     * @exception  IOException  If an I/O error occurs
     *
     * @see java.nio.file.Files#readAllLines
     */
    public String readLine() throws IOException {
        return readLine(false);
    }

注意看return的声明not includingany line-termination characters. 人家这里说得很清楚嘛,吐血了。。。

如果你对Java很熟悉,或者对readLine()方法熟悉,你就不用折腾那么多,就不会掉坑里了。

我在想为什么一直没发现呢,这个方法用了这么久,网上也这么多人用,就没人遇到问题?我猜主要原因是大部分情况下,没有换行符也没有什么影响。我们一般都是把json或HTML文件放assets里,然后读取出来使用。json文件有没有换行根本不影响!我们工程里大部分都是这种用法。如果HTML里没有内嵌JavaScript代码也没有问题!如果真有内嵌JavaScript代码了,也可以通过采用loadUrl方法来绕过,就是我之前的做法那样。

只有正面刚,才会发现这里有个坑。readLine这个方法不好,太有迷惑性了。 还有就是不要随便相信网上拷来的代码!!!这些血的教训。

最后附上修正版的正确的从assets里读取文件的方法。

 /**
     * 获取asset的文件,转化成String
     *
     * @param context
     * @param fileName
     * @return
     */
    public static String parseAssetsData(Context context, String fileName) {
        StringBuilder stringBuilder = new StringBuilder();
        InputStreamReader inputStreamReader = null;
        BufferedReader bfReader = null;
        try {
            AssetManager assetManager = context.getAssets();
            inputStreamReader = new InputStreamReader(assetManager.open(fileName));
            bfReader = new BufferedReader(inputStreamReader);
            String line;
            //注意!! readLine()返回的内容已经把换行符去掉,所以要补上
            while ((line = bfReader.readLine()) != null) {
                stringBuilder.append(line);
                stringBuilder.append("\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            closeStream(inputStreamReader);
            closeStream(bfReader);
        }
        return stringBuilder.toString();
    }

    /**
     * 关闭流
     *
     * @param io
     */
    public static void closeStream(Closeable io) {
        if (io != null) {
            try {
                io.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

如果你觉得这篇文章有用,请打赏小钱喝杯咖啡^_^ 打赏

Category: 技术 Tagged: Android开发 WebView JavaScript

comments


如何写出不太坏的代码

2019-02-28

如何写出不太坏的代码

本来想把标题定为“如何写出好代码”的,但是转念一想,觉得自己没那么牛逼,就改成了不太坏的代码。只要不要写出太糟糕的代码,不就是好代码了吗?

以下只是个人的一些总结,如有异议,请出门右转。

1.注重命名

包括文件名,变量名,方法名。一般变量名以功能来命名,看它的名字就能知道它的用途是什么 遵循一些命名规范。Java中成员变量名一般以m开头,而OC中的成员变量一般以_开头

2.遵循低耦合,高内聚的原则编写代码

具体表现遵循分层分模块原则,底层模块不能依赖上层模块,逻辑层不依赖界面,绝不要把逻辑层代码和界面代码混在一起

3.针对接口编程,而不是实现编程

当一个类要暴露一些方法给外部调用或者通信时,先定义好接口。接口定义应该简单明了,不容易引起歧义,接口尽量少。最后才考虑内部实现,而且尽量不暴露给外部,尽量不需要外部知道内部是如何实现的

4.一个类的代码量尽量不要太大,一个方法只做一件事情

一个类(或者一个方法)的代码越多,出错的概率越大。一个类三到5百行代码最佳。如果一个类的代码太多,就要考虑这个类是不是做的东西太多了 ...

Category: 技术 Tagged: iOS开发 Android开发

comments

Read More

iOS开发如何做外部测试

2019-01-05

iOS开发如何做外部测试

我们做APP开发的,开发出来的APP还未上线,又要想给别人用一下,该怎么做?实际上这是我们每个做APP开发的人都会遇到的,但是并不是每个人都清楚怎么做,有哪些方式。今天我们就来讲一讲。

在将iOS的APP如何给别人使用和测试前,先说一下安卓APP开发是怎么做的。安卓端是很简单的,你只要先生成一个证书文件keystone,然后用这个文件给apk签名,就可以将apk发给任意一个人安装使用了。这很简单,也很方便。可是iOS的就没那么容易了。我们还是从最简单的开发人员如何用真机测试开始说起。

真机调试

我们有了一个想法,并开始写代码,模拟器调试很方便,要在iPhone上看效果怎么办了。你直接将iPhone连上电脑是不行的。现在的情况至少你需要一个Apple ID,在Xcode上登录你的账号,在调试前Xcode会自动帮你初始化一些东西,然后就可以将APP安装到你的iPhone上了。在以前,你这个AppleID不能是普通的账号,必须是付了前的开发者账号才行。现在苹果放宽限制了。

这种方式的做法是你的手机必须连上你的电脑,在Xcode上选择你的iPhone来调试,Xcode会自动帮你注册设备的UDID。如果你没办法把iPhone连接到电脑上呢?或者不方便把iPhone连接到电脑上,毕竟如果是老板的手机的话,不方便那种做的。那我们需要一种把安装包提供给别人安装,或者一个地址给别人安装的方式。

adhoc 方式

我们将项目archive后,在导出(现在Xcode叫distribution)的时候,有个选项是adhoc ...

Category: 技术 Tagged: iOS开发 beta测试 TestFlight 公开链接

comments

Read More

SVG图片在移动端的应用解决方案

2018-11-03

SVG图片在移动端的应用解决方案

近几年来SVG使用得越来越多,就连Android的官方库也加入VectorDrawable的支持。这个类就是用来支持向量图的。SVG图片在web端使用非常广泛,我第一次接触这个也是在做react-native的项目中使用的。当时我们要做一些动画,需要从一个形状变换成另一个形状,这种一般都是用矢量图来做的。当时设计师就给了我一些矢量图,于是我就开始研究这个东西。

在react-native中,有专门一个库叫react-native-svg来处理这个。不过当时要做两个SVG形状的动画变化,并不是任何一个形状都可以的,需要遵循一定的标准。设计师给我的两个SVG文件并不能转换。后来是我自己根据文件里的一些关键参数自己在代码里直接画出来后,再做转换动画。

最近我在做的native项目中,也遇到了要用SVG的图片。我们的项目里要从服务器下载SVG图片来展示。我们要实现的这些文件是需要服务器动态配置的,也就是说我们不能预先打包进我们的APP里。所以我们这里的要提供一个解决方案,跟图片JPG图片一样显示,缓存。

这个需求跟之前我遇到的那个需求是很不一样的,之前的是设计师已经定义好图片,我们工程师直接拿到文件在程序里展示,不需要考虑下载和缓存之类的。这种需求其实很简单,我们实际上大部分的需求就是这种需求,网上有很多库可以完成这种需求。把SVG图片跟JPG等普通图片一样使用,网上的方案还真不多,特别是iOS方面。。。。

要像普通图片一样使用,就要考虑下载,本地缓存,内存缓存。像这种需求,我们移动端都会使用专门的图片框架,像安卓端的glide,UIL等,iOS端的SDWebImage等。但是这种库它是默认都不会考虑SVG图片。但是我们最好还是像使用这种框架来处理SVG图片 ...

Category: 技术 Tagged: 算法 SVG androidsvg glide Macaw

comments

Read More

多家公司的人证比对算法体验和开发感想

2018-07-13

多家公司的人证比对算法体验和开发感想

最近在开发一个声纹采集一体机系统,这个系统里用到了人证比对功能,即刷身份证后拍照,然后将身份证里面的照片跟拍照比对,验证是否是同一个人。我们不是做人脸识别的公司,所以人脸识别功能当然是接入别人的SDK,这里我们找了好多家的人脸算法,对接过程中也遇到很多问题。经过这次的开发,我了解到这个行业的一些现状。

我们的系统主要运行在Windows电脑上,而且需要无网络也能使用,所以人脸算法必须是用离线版的。人脸算法厂家一般对外提供两种方式:在线版和离线版。一般在线版都可以提供免费试用,离线版需要授权使用。

云端方案

在线版的人脸识别我试用过依图科技的和中科视拓的SDK。在我们的应用声临其境APP上有个声纹购物的场景,需要人脸验证。这两个厂家提供的API都差不多,依图的API安全性考虑更全面,错误码定义的比较详细。还有他们的API共同点是,图片传输都是将图片数据进行base64编码后传输的。至于准确率,感觉都差不多,没有特别明显的差距。

离线方案

离线版的人脸算法用得比对多。因为它是用在我们要卖的机器上,而在线版的我们只是做试验。离线版的集成和使用要比在线版麻烦很多。由于离线版要给出算法库,处于安全和容易被拷贝的原因,都是要先授权的。授权的方式一般有两种:在设备上接入加密狗USB key或者绑定设备机器码的方式。加密狗方式比较方便,我们一般采用这种方式。

我们接入了很多厂家,有阅面科技,中控,杰锐达,商汤,我还把开源的dlib人脸识别也接入了 ...

Category: 技术 Tagged: 人脸检测 人证比对 dlib electron

comments

Read More

Flutter框架研究和与RN对比

2018-03-16

Flutter是什么

现在技术更新迭代真的很快,每隔几年就会出现一些新的技术。当然,Flutter出现有有一点时间了,只不过还未真是发布,但是已经有一些人在使用了。这篇文章主要内容来自我在公司内部的一次分享会,所以大部分内容都是提炼。

  • Flutter 是由 Google 的工程师团队打造的,用于创建高性能、跨平台的移动应用的框架。
  • Flutter 针对当下以及未来的移动设备进行优化,专注于 Android and iOS 低延迟的输入和高帧率
  • Flutter的设计跟react-native很像,但是比RN进了一步
  • Flutter的开发语言是Dart

移动端跨平台开发技术演进

现在主流的移动开发平台是Android和iOS,之前还有过windows phone,每个平台的开发技术都不太一样。大家都是针对每个平台开发应用。自然有人就会觉得这样效率低下,想进行跨平台开发。从最开始的Hybrid混合开发技术,到RN的桥接技术,到现在新兴的Flutter技术,跨平台开发技术一直在演进。

以往最早的Hybrid开发,主要依赖于WebView。但是WebView是一个很重的控件,很容易产生内存问题,而且复杂的UI在WebView上显示的性能不好。react-native技术抛开了WebView,利用JavaScriptCore来做桥接,将js调用转为native调用,只牺牲了小部分性能获取的跨平台开发,这是一大进步。所以现在react-native很流行的原因。

react-native原理图

上图react-native框架原理

Flutter实现跨平台采用了更为彻底的方案 ...

Category: 技术 Tagged: ReactNative Flutter

comments

Read More

Jenkins slave ReactNative项目自动打包

2018-03-08

Jenkins slave ReactNative项目自动打包

这次我想在谈谈Jenkins自动打包的问题。针对react-native项目。 一般情况下,我们都是在一台电脑上安装Jenkins,然后在该电脑上打开Jenkins网页进行打包配置。如果我们有很多项目,分布在不同的电脑,而且运行的系统也不同,这时候我们需要一个master来统一管理这些电脑,然后统一在一个网页上进行打包配置。这就是Jenkins的master-slave打包模式。这种模式跟单机模式稍有不同,这里主要讲怎么配置slave。

slave节点配置

  • 首先slave上也同样要安装Jenkins和相关的打包所依赖的环境,一个都不能少。配置好之后,我们并不需要在本机上打开Jenkins网页来创建项目,配置项目等,而是要到主机上去配置。在去主机的Jenkins管理页面上新建节点之前,从机还需要做一些其他配置。
  • 打开远程登录。在系统设置-->共享,然后选中远程登录,允许所有用户访问。
  • 记住本机IP地址。在系统设置-->网络,可以看到当前网络的IP地址。为了以后方便,可以将IP地址设置为一个固定IP地址。不然当该电脑重启之后,IP地址可能会变,然后主机再按照之前配置的旧IP连接从机的时候,就连不上了。Mac电脑配置固定IP也有注意的地方。我第一次配置就是没有成功,上不了网。选择手动方式,然后填上一个没有被占用的IP地址,然后,子网掩码也要注意,如果跟服务器是跨网段访问的,需要注意255.255 ...

Category: 技术 Tagged: ReactNative Jenkins iOS

comments

Read More

react-native框架源码学习(iOS)(下)

2018-03-02

react-native框架源码学习(iOS)(下)

如果没有看过上篇,请先看react-native框架源码学习(iOS)(上)

JSCExecutor相关初始化

在上篇中说到Instance的初始化方法initializeBridge里,最主要是创建了NativeToJsBridge的实例。而NativeToJsBridge的构造函数里,主要是调用了executorFactory来创建一个JSCExecutor。NativeToJsBridge的构造函数还包含更多的东西。

NativeToJsBridge::NativeToJsBridge(
    JSExecutorFactory* jsExecutorFactory,
    std::shared_ptr<ModuleRegistry> registry,
    std::shared_ptr<MessageQueueThread> jsQueue,
    std::shared_ptr<InstanceCallback> callback)
    : m_destroyed(std::make_shared<bool>(false))
    , m_delegate(std::make_shared<JsToNativeBridge>(registry, callback))
    , m_executor(jsExecutorFactory->createJSExecutor(m_delegate, jsQueue))
    , m_executorMessageQueueThread(std::move(jsQueue ...

Category: 技术 Tagged: ReactNative 源码 iOS

comments

Read More

react-native框架源码学习(iOS)(上)

2018-02-26

react-native框架源码学习(iOS)(上)

注意事项

要学习react-native框架iOS端源码,先要了解一下注意的事项。

  • 一、 首先需要非常熟悉objective-c语言,对OC的runtime机制更是必不可少。源码里大量用了runtime特性。然后也要熟悉c++语言。除了OC语言,里面还有大量的c++代码,OC与c++混合代码。对c++的template和std库也要熟悉。我本人对c++也熟悉,但是不够深入,对一些高级特性不是很了解,所以相关的一些代码看起来很吃力。最后当然也熟悉js语言。我大学时学习过js,但是那还真是远古时代了,js近年来发展很快,我对很多新特性不是很了解,这也对我学习有一定的阻碍。另外还有要比较了解iOS的JavaScriptCore框架,这个是js与OC通信的基础。
  • 二、 要对react-native的版本了解。查看网上的资料时也要注意他们所说的RN版本。现在网上很多研究资料,他们所研究的版本都是很早之前的版本。新的版本跟老版本有很多细节和流程改变了。如果你发现有些文章里说的东西跟你所看到的对不上,那就是研究的版本不一致。
  • 三、如果看不懂,多看看别人的分析文章。如果对某种语言或者框架库不熟悉,建议先把该补的补上。

我所研究的RN版本是0.49,而最新版是0.51 ...

Category: 技术 Tagged: ReactNative 源码 iOS

comments

Read More

浅谈ReactNative技术的优劣

2018-02-06

浅谈ReactNative技术的优劣

去年大概10月份开始使用ReactNative技术来开发项目,现在也已经有几个月时间了。也上线了三个ReactNative开发的项目。一直想谈谈使用ReactNative的开发感受。

  • 使用react-native确实可以提高界面开发效率,因为两个平台,只需要编写一套代码。react-native在这方面做了很多工作,让界面开发跟web开发基本一致。web开发人员可以很方便的写react-native界面,不需要二次学习。而且在react-native里面的像素是逻辑像素,可以比较好的适配界面。
  • react-native界面开发方式跟web开发是基本一致的,这跟native开发界面有很大不同。react-native界面开发是声明式开发,而且是继承于react框架,而纯native开发人员根本不了解这个框架,需要学习成本。它的View布局层次跟原生的布局层次不太一样。我自己也搞了挺久才搞清楚。
  • react-native很适合那些纯http数据交互的应用。即那些数据和内容都是从服务器拉取,APP只是展示和消费的场景应用。这样不但界面可以react-native来写,HTTP请求也可以很方便的用ReactNative来写。
  • react-native不适合用于那些需要复杂的通信方式的应用,也不适用于那些强多媒体资源的应用,例如要做相册,音视频播放的应用。因为react-native上不方便用socket的那些通信方法,需要自己封装。然后内存问题也不太好把控。对于相册,多媒体播放,ReactNative也没有封装。
  • react-native对于web转native开发是很有用,对纯native开发转react-native开发不是很友好,主要是思维方式发生比较大的变化。
  • react-native不能隔绝native开发的知识。要做APP开发,最终还是需懂得native开发的一些知识,react-native并不能完全屏蔽这些。一个纯web开发想要转react-native开发,还是需要懂native开发的人来帮助和指导,或者自己需要先学习native相关知识。

react-native开发会是未来的方向吗

这个我也不敢说。不过ReactNative开发未来会代替掉一些native发 ...

Category: 技术 Tagged: react-native

comments

Read More
Page 1 of 3

Next »