Hybird App 应用开发中5个必备知识点复习
前言
我们大前端团队内部 ?每周一练 的知识复习计划还在继续,本周主题是 《Hybird APP 混合应用专题》 ,这期内容比较多,篇幅也相对较长,每个知识点内容也比较多。
之前分享的每周内容,我都整理到掘金收藏集 ?《EFT每周一练》 上啦,欢迎点赞收藏咯??。
注:本文整理资料来源网络,有些图片/段落找不到原文出处,如有侵权,联系删除。
一、什么是 Hybird App,与 Native App 及 Web App 有什么区别
参考文章:
《Web App Hybrid App和 Native App的区别》
《Hybrid APP基础篇(二) -> Native、Hybrid、React Native、Web App方案的分析比较》
1.1 主流应用类型
随着现在移动互联网的快速发展,市面上目前主流移动应用程序主要分三类:Web App、 Native App 和 Hybrid App。
三者大致关系如下:
1.2 Web App
Web App,即移动端网站,一般指的是基于 Web 的应用,基于浏览器运行,无需下载安装,基本上可以说是触屏版的网页应用。这类应用基本上是一个网页或一系列网页,旨在在移动屏幕上工作。
Web 网站一般分为两种:
MPA(Multi-page Application)
SPA(Single-page Application)
一般的 Web App 是指 SPA 形式开发的网站。
优点:
- 开发和维护成本低,可以跨平台,调试方便;
前端人员开发的代码,可应用于各大主流浏览器(特殊情况可以代码进行下兼容),没有新的学习成本,而且可以直接在浏览器中调试。
- 更新最为快速;
由于web app资源是直接部署在服务器端的,所以只需替换服务器端文件,用户访问是就已经更新了(当然需要解决一些缓存问题)。
- 无需安装App,不会占用手机内存;
通过浏览器即可访问,无需安装,用户使用成本更低。
缺点:
- 性能低,用户体验差;
由于是直接通过的浏览器访问,所以无法使用原生的API,操作体验不好。
- 依赖于网络,页面访问速度慢,耗费流量;
Web App每次访问都必须依赖网络,从服务端加载资源,当网速慢时访问速度很不理想,特别是在移动端,对网站性能优化要求比较高。
- 功能受限,大量功能无法实现;
只能使用 HTML5 的一些特殊 API ,无法调用原生 API ,所以很多功能存在无法实现情况。
- 临时性入口,用户留存率低;
这既是它的优点,也是缺点,优点是无需安装,确定是用完后有时候很难再找到,或者说很难专门为某个web app留存一个入口,导致用户很难再次使用。
1.3 Native App
Native APP 指的是原生程序,需要用户下载安装使用,一般依托于操作系统,有很强的交互,是一个完整的App,可拓展性强,能发布应用商店。
目前市面上主流的平台有:Android 和 iOS。
优点:
直接依托于操作系统,用户体验好,操作流畅,性能稳定;
用户留存率高;
功能最为强大,特别是在与系统交互中,几乎所有功能都能实现;
由于 Native APP 是直接依托于系统,所以可以直接调用官方提供的API,功能最为全面(比如本地资源操作,通知,动画等)。
缺点:
- 开发和维护成本高,无法跨平台,需要各平台各自独立开发;
Android 上基于 Java 开发,iOS 上基 OC 或 Swift 开发,相互之间独立,必须要有各自的开发人员。
- 门槛较高,原生人员有一定的入门门槛,人才较少;
原生的一个很大特点就是独立,所以不太容易入门,而且 Android, iOS都需要独立学习。
- 分发成本高,更新缓慢,特别是发布应用商店后,需要等到审核周期;
原生应用更新是一个很大的问题, Android中还能直接下载整包APK进行更新,但是 iOS中,如果是发布 AppStore ,必须通过 AppStore地址更新,而每次更新都需要审核,所以无法达到及时更新。
1.4 Hybrid App
Hybrid App 指的是混合开发,也就是半原生半 Web 的开发模式,有跨平台效果,当然了,实质最终发布的仍然是独立的原生APP(各种的平台有各种的SDK)。
优点:
- 学习和开发成本较低,可以跨平台,调试方便;
Hybrid 开发模式下,由原生提供统一的 API 给 JS 调用,实际的主要逻辑由 HTML 和 JS 完成,最终放在 webview 中显示,这样只需要写一套代码即可,达到跨平台效果,另外也可以直接在浏览器中调试,很方便。
一般 Hybrid 中的跨平台最少可以跨三个平台: Android App ,iOS App ,普通 webkit 浏览器。
需要前端人员关注一些原生提供的API,具体的实现无需关心,没有新的学习内容。
- 维护成本低,功能可复用,并且更容易更新;
虽然没有 web app 更新那么快速,但是 Hybrid 中也可以通过原生提供 api ,进行资源主动下载,达到只更新资源文件,不更新 apk(ipa) 的效果。
- 功能更加完善,性能和体验要比起 web app 好太多;
因为可以调用原生api,所以很多功能只要原生提供出就可以实现,另外性能也比较接近原生。
- 部分性能要求的页面可用原生实现;
这种模式是原生混合 web ,所以我们完全可以将交互强,性能要求高的页面用原生写,然后一些其它页面用 JS 写,嵌入 webview 中,达到最佳体验。
缺点:
- 相比原生,性能仍然有较大损耗;
这种模式受限于 webview 的性能,相比原生而言有不少损耗,体验无法和原生相比。
- 不适用于交互性较强的app;
这种模式的主要适用:一些新闻阅读类,信息展示类的 app ,不适用于一些交互较强或者性能要求较高的 app (比如动画较多就不适合)。
1.5 三者区别
三者使用场景对比:
三者技术特征对比:
另外增加 ReactNative 一起放入作对比。
NativeApp | WebApp | HybridApp | ReactNativeApp | |
---|---|---|---|---|
原生功能体验 | 优秀 | 差 | 良好 | 接近优秀 |
渲染性能 | 非常快 | 慢 | 接近快 | 快 |
是否支持设备底层访问 | 支持 | 不支持 | 支持 | 支持 |
网络要求 | 支持离线 | 依赖网络 | 支持离线(资源存本地情况) | 支持离线 |
更新复杂度 | 高(几乎总是通过应用商店更新) | 低(服务器端直接更新) | 较低(可以进行资源包更新) | 较低(可以进行资源包更新) |
编程语言 | Android(Java),iOS(OC/Swift) | js+html+css3 | js+html+css3 | 主要使用JS编写,语法规则JSX |
社区资源 | 丰富(Android,iOS单独学习) | 丰富(大量前端资源) | 有局限(不同的Hybrid相互独立) | 丰富(统一的活跃社区) |
上手难度 | 难(不同平台需要单独学习) | 简单(写一次,支持不同平台访问) | 简单(写一次,运行任何平台) | 中等(学习一次,写任何平台) |
开发周期 | 长 | 短 | 较短 | 中等 |
开发成本 | 昂贵 | 便宜 | 较为便宜 | 中等 |
跨平台 | 不跨平台 | 所有H5浏览器 | Android,iOS,h5浏览器 | Android,iOS |
APP发布 | AppStore | Web服务器 | AppStore | AppStore |
1.6 三者如何选择
这里简单介绍几种情况,具体还是要以实际项目技术评估结果为主。
- 选择纯 Native App 模式的情况:
性能要求极高,体验要求极好,不追求开发效率。
- 选择 Web App 模式的情况:
不追求用户体验和性能,对离线访问没要求,正常来说,如果追求性能和体验,都不会选用web app。
- 选择 Hybrid App 模式的情况
大部分情况下的App都推荐采用这种模式,这种模式可以用原生来实现要求高的界面,对于一些比较通用型,展示型的页面完全可以用web来实现,达到跨平台效果,提升效率。一般好一点的Hybrid方案,都会把资源放在本地的,可以减少网络流量消耗。
- 选择React Native App模式的情况
追求性能,体验,同时追求开发效率,而且有一定的技术资本,舍得前期投入。
React Native这种模式学习成本较高,所以需要前期投入不少时间才能达到较好水平,但是有了一定水准后,开发起来它的优势就体现出来了,性能不逊色原生,而且开发速度也很快
二、什么是 Cordova,它的优缺点是什么
参考文章: 《浅谈Cordova框架》
2.1 Cordova 简介
Cordova 是一个用基于 HTML、CSS 和 JavaScript 的,用于创建跨平台移动应用程序的快速开发平台。它使开发者能够利用iPhone、Android、Palm、Symbian、WP7、Bada和Blackberry等智能手机的核心功能——包括地理定位、加速器、联系人、声音和振动等,此外 Cordova 拥有丰富的插件,可以调用。
也可以用来开发原生和WebView组件之间的插件接口。
来源:
Cordova 是 PhoneGap 贡献给 Apache 后的开源项目,是从 PhoneGap 中抽出的核心代码,是驱动 PhoneGap 的核心引擎。可以把它们的关系想象成类似于 Webkit 和 Google Chrome 的关系。
2.2 Cordova 架构图
架构图介绍:
- Web App
用于存放我们程序的代码,包括业务逻辑,还有一些运行需要的资源(如:CSS,JavaScript,图片,媒体文件等)。
应用的实现是通过 web 页面,默认的本地文件名称是 index.html
,应用执行在原生应用包装的 WebView 中,这个原生应用是你分发到应用商店中的。
- WebView
Cordova 用的 WebView 可以给应用提供完整用户访问界面,使得应用混合了 Webview 和原生的应用组件。
- Cordova Plugins
插件是 Cordova 生态系统的重要组成部分。它提供了 Cordova 和原生组件相互通信的接口,并绑定到了标准的设备API上,这使你能够通过 JavaScript 调用原生代码。
2.3 优缺点
优点:
- 跨平台,开发简单,学习成本低;
- 框架多,插件多,可自定义插件;
- 发展最早,社区资源丰富;
缺点:
- WebView性能低下时,用户体验差,反应慢;
- 中文文档资源少;
- 调试不方便,既不像原生那么好调试,也不像纯web那种调试;
三、Cordova 插件的原理是什么
Cordova 插件就是一些附加代码用来提供原生组件的 JavaScript 接口,它允许你的 App 可以使用原生设备的能力,超越了纯粹的 Web App。
Cordova 在 iOS 上的实现原理:
3.1 工作流程
- Cordova 发起对原生的请求:
cordova.exec(successCallback, failCallback, service, action, actionArgs);
// successCallback: 成功回调方法
// failCallback: 失败回调方法
// server: 所要请求的服务名字
// action: 所要请求的服务具体操作
// actionArgs: 请求操作所带的参数
这五个参数并不是直接传给原生,Cordova JS 端会做以下处理:
- 为每个请求生成一个唯一标识(
callbackId
),并传给原生端,原生端处理完后,会把callbackId
连同处理结果一起返回给 JS 端; - 以
callbackId
为key
,{success:successCallback, fail:failCallback}
为value
,把这个键值对保存在 JS 端的字典里,successCallback
与failCallback
这两个参数不需要传给原生,原生返回结果时带上callbackId
,JS 端就可以根据callbackId
找到回调方法; - 每次 JS 请求,最后发到原生的数据包括:
callbackId
,service
,action
,actionArgs
;
- 为每个请求生成一个唯一标识(
原生代码拿到
callbackId
、service
、action
及actionArgs
后,会做以下处理:- 根据
service
参数找到对应插件类; - 根据
action
参数找到插件类中对应的处理方法,并把actionArgs
作为处理方法请求参数的一部分传给处理方法; - 处理完成后,把处理结果及
callbackId
返回给 JS 端,JS 端收到后会根据callbackId
找到回调方法,并把处理结果传给回调方法;
- 根据
- JS 端根据
callbackId
回调cordova.js
// 根据 callbackId 及是否成功标识,找到回调方法,并把处理结果传给回调方法
callbackFromNative: function(callbackId, success, status, args, keepCallback) {var callback = cordova.callbacks[callbackId];if (callback) {if (success && status == cordova.callbackStatus.OK) {callback.success && callback.success.apply(null, args);} else if (!success) {callback.fail && callback.fail.apply(null, args);}// Clear callback if not expecting any more resultsif (!keepCallback) {delete cordova.callbacks[callbackId];}}
}
四、什么是 JS Bridge,它的作用是什么
参考文章:《JSBridge的原理》
4.1 JS Bridge 介绍
JSBridge 简单来讲,主要是 给 JavaScript 提供调用 Native 功能的接口,让混合开发中的前端部分可以方便地使用地址位置、摄像头甚至支付等 Native 功能。
JSBridge 就像其名称中的 “Bridge” 的意义一样,是 Native 和非 Native 之间的桥梁,它的核心是 构建 Native 和非 Native 间消息通信的通道,而且是 双向通信的通道。
JSBridge 另一个叫法及大家熟知的 Hybrid app 技术。
所谓 双向通信的通道:
- JS 向 Native 发送消息 :
调用相关功能、通知 Native 当前 JS 的相关状态等。
- Native 向 JS 发送消息 :
回溯调用结果、消息推送、通知 JS 当前 Native 的状态等。
4.2. JS Bridge 实现原理
参考文章:《Hybrid APP基础篇(四)->JSBridge的原理》
Android 和 iOS 的 JSBridge 实现方式:
4.2.1 基本流程
- H5 页面通过某种方式触发一个
url scheme
; - Native 捕获到
url scheme
,并进行分析和处理; - Native 调用 H5 的 JSBridge 对象传递回调;
原生的 WebView/UIWebView 控件已经能够和 JS 实现数据通信了,那为什么还要 JSBridge呢?
其实使用JSBridge有很多方面的考虑:
- Android4.2以下,
addJavascriptInterface
方式有安全漏掉。 - iOS7以下,JS 无法调用 Native。
url scheme
交互方式是一套现有的成熟方案,可以完美兼容各种版本,对以前老版本技术的兼容。
4.2.1 实现流程(Android 为例)
- 拟定协议,参考 http 制定的协议为:
jsbridge://className:port/methodName?jsonObj
;
className // Android端实现暴露给前端的类名
port // Android返回结果给前端的端口
methodName // 前端需要调用的函数
jsonObj // 前端给Android传递的参数
- 新建 HTML 文件命名为
index.html
, 编写一个button
绑定click
事件;
<button onclick="JSBridge.call('bridge','showToast',{'msg':'Hello JSBridge'},function(res){alert(JSON.stringify(res))}
)">测试showToast
</button>
- 新建 JS 文件命名为
JSBridge.js
, 第2步中的JSBridge.call
即为调用JSBridge.js
中的call
方法,后面带了四个参数;
call: function (obj, method, params, callback) {console.log(obj+" "+method+" "+params+" "+callback);var port = Util.getPort();console.log(port);this.callbacks[port] = callback;var uri=Util.getUri(obj,method,params,port);console.log(uri);window.prompt(uri, "");
},
JSBridge.js
中的 call
方法,最后调用了window.prompt
方法,这个方法就是触发 Android 端 webChromeClient
的回调函数用的。
window.prompt
触发了WebChromeClient
(这个需要使用函数WebView.setWebChromeClient
(new WebChromeClietn()
)进行设定);
类中的如下回调 onJsPrompt
。这时就完成了前端与 Android端 的通信了,因为前端的信息都顺利通过这个函数传递给Android了。
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {result.confirm(JSBridge.callJava(view,message));return true;
}
- Android 中会定义一个类
JSBridge.java
来管理暴露给前端使用的函数;
这个类有两个功能:
- 暴露给前端的函数的动态注册功能。
- 解析前端信息,调用了 Android 端对应的函数,这个示例中是:
showToast
函数。
解析前端的信息,获取前端调用的函数名:
Uri uri = Uri.parse(uriString);
className = uri.getHost();
param = uri.getQuery();
port = uri.getPort() + "";
String path = uri.getPath();
HashMap< String, Method> methodHashMap = exposedMethod.get(className);
Method method = methodHashMap.get(methodName);
通过获取的函数名,这里是 showToast
,调用 Android 端的 showToast
函数。
method.invoke(null,webView,new JSONObject(param),new Callback(webView,port));
- 定义类
BridgeImpl.java
来具体的实现暴露给前端的所有函数。这里的showToast
函数如下:
public static void showToast(WebView webView, JSONObject param, final JSBridge.Callback callback){String message = param.optString("msg");Toast.makeText(webView.getContext(),message,Toast.LENGTH_LONG).show();if(null != callback){try {JSONObject object = new JSONObject();object.put("key","value");object.put("key1","vaule1");callback.apply(getJSONObject(0,"ok",object));}catch (Exception e){e.printStackTrace();}}
}
五、请列举 Android 与 iOS 平台下 JS Bridge 的实现方式
这边代码比较多,我使用图片来展示,大家可以放大来查看。
5.1 Android 实现方式
5.1.1 Android 调用 JS 的 2 种方式
- 通过
WebView
的loadUrl()
:
JS 代码调用一定要在 onPageFinished()
回调之后才能调用,否则不会调用。
Web 端代码:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>前端代码</title><script>// Android需要调用的方法function callJS(){alert("Android调用了JS的callJS方法");}</script></head>
</html>
Android 端代码:
- 通过
WebView
的evaluateJavascript()
:
// 只需要将第一种方法的loadUrl()换成下面该方法即可
mWebView.evaluateJavascript("javascript:callJS()", new ValueCallback<String>() {@Overridepublic void onReceiveValue(String value) {//此处为 js 返回的结果}});
}
5.1.2 JS 调用 Android 的 3 种方式
- 通过
WebView
的addJavascriptInterface()
进行对象映射:
Android 映射:
// 继承自Object类
public class AndroidtoJs extends Object {// 定义JS需要调用的方法// 被JS调用的方法必须加入@JavascriptInterface注解@JavascriptInterfacepublic void hello(String msg) {System.out.println("JS调用了Android的hello方法");}
}
Web:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>前端代码</title> <script>function callAndroid(){// 由于对象映射,所以调用test对象等于调用Android映射的对象test.hello("js调用了android中的hello方法");}</script></head><body>//点击按钮则调用callAndroid函数<button type="button" id="button1" "callAndroid()"></button></body>
</html>
Android 端:
- 通过
WebViewClient
的shouldOverrideUrlLoading ()
方法回调拦截url
:
Web 端:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>前端代码</title><script>function callAndroid(){/*约定的url协议为:js://webview?arg1=111&arg2=222*/document.location = "js://webview?arg1=111&arg2=222";}</script></head><!-- 点击按钮则调用callAndroid()方法 --><body><button type="button" id="button1" onclick="callAndroid()">点击调用Android代码</button></body>
</html>
Android 端:
- 通过 WebChromeClient 的方法回调拦截JS对话框方法:
通过 WebChromeClient 的 onJsAlert()
、onJsConfirm()
、onJsPrompt()
方法回调拦截JS对话框 alert()
、confirm()
、prompt()
消息。
Web 端:
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>前端代码</title><script>function clickprompt(){// 调用prompt()var result=prompt("js://demo?arg1=111&arg2=222");alert("demo " + result);}</script></head><!-- 点击按钮则调用clickprompt() --><body><button type="button" id="button1" onclick="clickprompt()">点击调用Android代码</button></body>
</html>
Android 端:
5.2 iOS 实现方式
5.2.1 JS 调用 iOS 的 2 种方式
- 使用
XMLHttpRequest
发起请求的方式:
Web 端:
XMLHttpRequest bridge:
JS 端使用 XMLHttpRequest
发起了一个请求:execXhr.open('HEAD', "/!gap_exec?" + (+new Date()), true);
,请求的地址是 /!gap_exec
;并把请求的数据放在了请求的 header 里面,见这句代码:execXhr.setRequestHeader('cmds', iOSExec.nativeFetchMessages());
。
而在 Objective-C 端使用一个 NSURLProtocol
的子类来检查每个请求,如果地址是 /!gap_exec
的话,则认为是 Cordova 通信的请求,直接拦截,拦截后就可以通过分析请求的数据,分发到不同的插件类(CDVPlugin 类的子类)的方法中:
Cordova 中优先使用这种方式,Cordova.js
中的注释有提及为什么优先使用 XMLHttpRequest
的方式,及为什么保留第二种 iframe bridge
的通信方式:
// XHR mode does not work on iOS 4.2, so default to IFRAME_NAV for such devices.
// XHR mode’s main advantage is working around a bug in -webkit-scroll, which
// doesn’t exist in 4.X devices anyways123
iframe bridge:
在 JS 端创建一个透明的 iframe
,设置这个 ifame
的 src
为自定义的协议,而 ifame
的 src
更改时,UIWebView
会先回调其 delegate
的 webView:shouldStartLoadWithRequest:navigationType:
方法,关键代码如下:
- 通过设置透明的
iframe
的src
属性:
5.2.2 iOS 调用 JS 的方式
UIWebView
有一个这样的方法 stringByEvaluatingJavaScriptFromString:
,这个方法可以让一个 UIWebView
对象执行一段 JS 代码,这样就可以达到 Objective-C 跟 JS 通信的效果,在 Cordova 的代码中多处用到了这个方法,其中最重要的两处如下:
- 获取 JS 的请求数据:
- 把 JS 请求的结果返回给 JS 端:
结语
对于初入混合应用开发的小伙伴,这些会有点难度,但是好好理解下那几张流程图,再理一理思路,相信会有帮助?
给大家加加油~~
关于我
本文首发在 pingan8787个人博客,如需转载请保留个人介绍
Author | 王平安 |
---|---|
pingan8787@qq.com | |
博 客 | www.pingan8787.com |
微 信 | pingan8787 |
每日文章推荐 | https://github.com/pingan8787/Leo_Reading/issues |
ES小册 | js.pingan8787.com |
微信公众号
Hybird App 应用开发中5个必备知识点复习相关推荐
- 直播app软件开发中的小游戏和直播如何实现兼顾
直播app软件的更新优化速度非常快,而互动小游戏也是现在主流直播app中的常见功能.当然单独拿出某一个小游戏,我们都可以将它看做一个个体,如果与视频直播结合,就可以为直播软件增光添彩了.那么,直播ap ...
- 嵌入式开发中的C语言知识点
嵌入式开发中的C语言知识点 1.关键字 2.数据类型 3.内存管理和存储架构 4.指针和数组 5.结构类型和对齐 6.预处理机制 1.关键字 几乎每一门语言中都有关键字,具有特殊功能,C语言也不例外, ...
- Android开发中的一些小知识点记录(101-120)
编写不易,如有转载,请声明出处:http://blog.csdn.net/zxc514257857/article/details/118554522 Android开发中的一些小知识点记录(1-20 ...
- APP外包开发中的第三方工具
APP外包开发过程中需要用到各种的第三方服务平台和工具,这些平台和工具可以提高开发效率和更规范的管理项目,今天和大家分享常见的一些平台和工具,从UI设计.开发.测试到上线覆盖APP开发的整个流程,希望 ...
- 后台开发与APP配合开发中的一些拙见
一. APP需要有token,对应后台的sessionId,这个架构实现可以采用shiro的sessionManage组件完成 二.APP与后台的规范协议,也就是我们通常说的URL,最好采用restf ...
- Hybird APP混合开发升级方法
hybrid app开发的优势优点不用说了,但在这里,我们将重点关注混合系统中模块包的升级过程. 服务器维护压缩包的版本列表 Apppid 表示每个模块的唯一 ID 版本表示相应模块的版本号,递增 U ...
- app混合式开发中HbuilderX连接手机真机调试
苹果手机连接HbuilderX 1.电脑上下载iTools 4版本 2.连接数据线,打开HbuilderX,选中我们的项目,点击导航栏中的运行>运行到手机或模拟器>会出现我们的手机 安卓手 ...
- 面试中Handler这些必备知识点你都知道吗?面试真题解析
前言 前几天去参加了一场面试.面试的题目大多很基础,有一道关于埋点的问题,面试官问我如果不用第三方SDK进行埋点,自己埋点的话,如何减少埋点对业务代码的入侵. 当时没想太多,就说创建一个 BaseVi ...
- PHP开发自己的框架必备知识点
一.PHP常用的四种数据结构 简介:spl是php的一个标准库. 官方文档:http://php.net/manual/zh/book.spl.php <?php//spl(php标准库)数据结 ...
- 【iOS - 周总结】开发中遇到的小知识点(2018.12.10-2018.12.15)
1.WKWebview加载html文本图片过大,没有自适应屏幕宽高. 在用Webview加载html文本有时候会遇到加载的图片过大,不能自适应屏幕宽高的问题.那么如何解决这个问题?如何使图片自适应屏幕 ...
最新文章
- 本科生顶刊发封面文章!他,是能成就导师的学生
- android 8.0 三星,这些三星手机竟到2019年才能升级安卓8.0:等到头发都白了
- C++中容器的使用(二)
- MyBatis的XML配置文件(三)
- java循环隔行变色_c:foreach标签详解----(隔行换背景颜色的问题)
- OFFICE技术讲座:T2B-NE/L2R-WN部分中文标点需要旋转
- Spatial-Temporal时间序列预测建模方法汇总
- lmdb数据库的读取与转换(二) —— 数据集操作
- uni-app弹窗多选样式分享
- Python AutoCAD 修改
- 关于SFM的材质vmt科普
- Labview 版本控制
- 一节双曲型方程基于MATLAB的求解,双曲方程基于matlab的数值解法
- linux基础知识总结(上)
- 查询数据库重复记录方法
- 关于html5外文翻译三千字,论文外文文献翻译3000字左右.pdf
- 由Maleimide参与的Biotin-C5-Mal试剂具有荧光猝灭能力
- python os.path.exists 已存在_详解python os.path.exists判断文件或文件夹是否存在
- 学习在UE中导入 Morph Targets 资源
- VSG中的虚拟调速器和无功-电压调节