APP中Web容器的核心实现

APP,核心,Web容器 · 浏览次数 : 156

小编点评

# 消息队列设计模式 消息队列设计模式是用于存储和处理消息的模式。它可以用于存储消息并处理消息的顺序。 **主要特征:** * 消息队列可以存储多个消息。 * 消息队列可以处理消息的顺序。 * 消息队列可以用于存储和处理消息的顺序。 **主要类型:** * 消息队列 * 消息处理器 * 消息接收器 **主要操作:** * 存储消息 * 处理消息 * 监听消息接收器 **常用模式:** * **消息队列:**用于存储和处理消息。 * **消息处理器:**用于处理消息。 * **消息接收器:**用于监听消息接收器。

正文

 
现在的业务型APP中,采用纯原生开发策略的已经很少了,大部分都使用的混合开发。如原生,H5,ReactNative,Flutter,Weex它们之间任意的组合就构成了混合开发。
其中原生+H5是出现最早的,老牌混合方案,即使过来多年,在现在的混合开发方案中H5也是使用率非常高的。在APP中嵌入Web容器,将更新迭代快,用户交互少的页面采用H5开发,将交互性强,稳定的部分使用原生开发是Hybird开发常用的策略。
Webview通过WebKit来渲染Web页面,通过JavaScriptCore执行JS代码与用户交互,这篇文章主要讲Hybird方案中Web容器的JS与原生交互的部分。
 
JSBridge原生与Web通信
 
为什么需要JSBridge双端通信?
在Hybrid开发模式下,H5页面经常需要使用到Native的功能,比如打开二维码扫描、调用本地相册、获取用户信息等,同时Native也需要向Web端发送推送、更新状态等。
JavaScript运行环境与原生运行环境是隔离的,JavaScript是运行在单独的JS Context中(Webview容器、JSCore等)。
WebView是一个原生UI控件,可以通过加载url展示一个网页,它是运行在原生中。所以需要有一种机制实现Native端和Web端的双向通信。
JSBridge主要做两件事:
1.将Native端原生接口封装成JavaScript接口,让js调用。
2.将Web端JavaScript接口封装成原生接口,让原生调用。

JSBridge的实现方案
原生调用JS的方式
原生调用JS比较简单,直接调用WebView提供的evaluateJavaScript API就可以了。
不过在调用之前要先保证 在H5页面中 将JS方法保存在window(js的运行context)上,原生是调用的js全局上下文window上的方法
UIWebView调用方式:
NSString *jsStr = @"执行的JS代码";
[webView stringByEvaluatingJavaScriptFromString:jsStr];
WKWebView调用方式:
[webView evaluateJavaScript:@"执行的JS代码" completionHandler:^(id _Nullable response, NSError * _Nullable error) {}];

 

JS调用原生的方式
JS调用原生的常见解决方案如下:
一、UIWebView使用JavaScriptCore注入和Scheme拦截方式
二、WKWebView使用WKScriptMessageHandler注入和Scheme拦截方式
三、使用第三方框架WebViewJavascriptBridge调用
 
UIWebView使用JavaScriptCore注入和Scheme拦截方式
JavaScriptCore注入
1.注入API
将Native的相关接口注入到JS的Context(window)的对象中,一般来说这个对象内的方法名与Native相关方法名是相同的,Web端就可以直接在全局window下使用这个暴露的全局JS对象,进而调用原生端的方法。
UIWebView的JavaScriptCore注入方式:
1.原生新建类继承自NSObject(如AppJSModel)。
2.h文件中声明一个代理并遵循JSExport,代理内的方法和js定义的方法名一致。
3.m文件中实现代理中对应的方法,可以在方法内处理事件或通知代理。
 
原生类实现:
#import <Foundation/Foundation.h>
#import <JavaScriptCore/JavaScriptCore.h>

@protocol JSNativeProtocol <JSExport>

- (NSDictionary *)QRCodeScan:(NSDictionary *)param;

@end

@interface AppJSModel : NSObject <JSNativeProtocol>

@end


#import "AppJSModel.h"

@implementation AppJSModel
- (NSDictionary *)QRCodeScan:(NSDictionary *)param {
    NSLog(@"param: %@",param);
    return @{@"name":@"jack"};
}
@end
H5端调用:
import './App.css';
import { useState } from 'react';

function OriginalWebViewApp() {
    const[name, setName] = useState('')
 
    // 0.公共
    //原生发消息给JS,JS的回调
    window.qrResult = (res)=>{
      setName(res)
      return '-------: '+res
    }
    // scheme拦截
    const localPostion = () => {
      window.location.href = 'position://localPosition?name=jack&age=20'
    }

    // 2.UIWebView的交互
    //js发消息给原生
    const qrActionOnAppModel = () => {
      const res = window.appModel.QRCodeScan({"name":"value"})
      alert(res.name)
    }
    const showAlert = () => {
      window.showAlert()
    }
   
   
    return (
      <div className="App">
        <div>------------------公共------------------</div>
          <div><a href='position://abc?name=jack' style={{color:'white'}}>scheme拦截1:定位</a></div>
          <button onClick={localPostion}>scheme拦截2</button>
          <div>
           原生执行代码的结果:{name}
          </div>
   
          <div>------------------UIWebView------------------</div>
          <button onClick={qrActionOnAppModel}>点击扫码</button>
          <button onClick={showAlert}>弹窗</button>
      </div>
    )
}

export default OriginalWebViewApp
原生方法注入到JS上下文中:
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    JSContext *jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
    
    AppJSModel *jsModel = [AppJSModel new];
    jsContext[@"appModel"] = jsModel;
    jsContext[@"showAlert"] = ^(){
        dispatch_async(dispatch_get_main_queue(), ^{
            UIAlertController* alert = [UIAlertController alertControllerWithTitle:@"请输入支付信息" message:@"" preferredStyle:UIAlertControllerStyleAlert];
            UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
            [alert addAction:defaultAction];
            UIAlertAction* cancleAction = [UIAlertAction actionWithTitle:@"Cancle" style:UIAlertActionStyleCancel handler:nil];
            [alert addAction:cancleAction];
            
            [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
                textField.placeholder=@"请输入用户名";
            }];
            [alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
                textField.placeholder=@"请输入支付密码";
                textField.secureTextEntry=YES;
            }];
            
            [self presentViewController:alert animated:YES completion:nil];
        });
    };
}

Scheme拦截

原生方法执行完后,回调给js结果
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if ([request.URL.scheme isEqualToString:@"position"]) {
        //自定义处理定位scheme
        JSContext *jsContext = [webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
        NSString *jsCode = @"qrResult('杭州,之江')";
        [jsContext evaluateScript:jsCode];
        return NO;
    }
    
    return YES;
}

 

WKWebView使用WKScriptMessageHandler注入和Scheme拦截方式
 
WKScriptMessageHandler注入
1.首先iOS与JS约定好使用相同的方法名进行通信,如 QRCodeScan:
2.然后iOS 使用 WKUserContentController 的 -addScriptMessageHandler:name:方法监听 name 为 QRCodeScan 的消息;
3.JS通过 window.webkit.messageHandlers. QRCodeScan.postMessage() 的方式对QRCodeScan 方法发送消息;
4.iOS在-userContentController:didReceiveScriptMessage:方法中读取 name 为 QRCodeScan 的消息数据 message.body。

H5页面中JS与原生设置
下面代码为React代码实现:
import './App.css';
import { useState } from 'react';

function OriginalWebViewApp() {
    const[name, setName] = useState('')
 
    // 0.公共
    //原生发消息给JS,JS的回调
    window.qrResult = (res)=>{
      setName(res)
      return '-------: '+res
    }
    // scheme拦截
    const localPostion = () => {
      window.location.href = 'position://localPosition?name=jack&age=20'
    }
   
    // 1.WKWebView的交互
    //js发消息给原生
    const qrAction = () => {
      window.webkit.messageHandlers.QRCodeScan.postMessage({"name":"value"})
    }
   
   
    return (
      <div className="App">
        <div>------------------公共------------------</div>
          <div><a href='position://abc?name=jack' style={{color:'white'}}>scheme拦截1:定位</a></div>
          <button onClick={localPostion}>scheme拦截2</button>
          <div>
           原生执行代码的结果:{name}
          </div>
   
          <div>------------------WKWebView------------------</div>
          <button onClick={qrAction}>点击扫描</button>
      </div>
    )
}

export default OriginalWebViewApp
iOS原生页面设置
override func viewDidLoad() {
    super.viewDidLoad()
    
    // WKWebViewConfiguration: 用于配置WKWebView的属性和行为, 常见的操作有
    let webViewConfiguration = WKWebViewConfiguration()
    
    //1.配置WKUserContentController,管理WKUserScript(cookie脚本)和WKScriptMessageHandler原生与JS的交互
    let userContentController = WKUserContentController()
    webViewConfiguration.userContentController = userContentController
    //添加WKScriptMessageHandler脚本处理
    userContentController.add(self, name: "QRCodeScan")
    //添加WKUserScript,injectionTime注入时机为atDocumentStart页面加载时在,forMainFrameOnly不只在主框架中注入,所有的框架都注入。
    let cookieScript = WKUserScript(source: "document.cookie = 'cookieName=cookieValue; domain=example.com; path=/';", injectionTime: .atDocumentStart, forMainFrameOnly: false)
    userContentController.addUserScript(cookieScript)
    
    //2.自定义处理网络,处理Scheme为position的定位网络操作
    webViewConfiguration.setURLSchemeHandler(self, forURLScheme: "position")
    
    //3.偏好配置WKPreferences,设置网页缩放,字体
    let preferences = WKPreferences()
    preferences.minimumFontSize = 10
    if #available(iOS 14, *) {
        let webpagePreferences = WKWebpagePreferences()
        webpagePreferences.allowsContentJavaScript = true
        webViewConfiguration.defaultWebpagePreferences = webpagePreferences
    } else {
        preferences.javaScriptEnabled = true
    }
    preferences.javaScriptCanOpenWindowsAutomatically = true
    webViewConfiguration.preferences = preferences
    
    //4.多媒体设置,设置视频自动播放,画中画,逐步渲染
    webViewConfiguration.allowsInlineMediaPlayback = true
    webViewConfiguration.allowsPictureInPictureMediaPlayback = true
    webViewConfiguration.allowsAirPlayForMediaPlayback = true
    webViewConfiguration.suppressesIncrementalRendering = true
    
    
    //5.cookie设置
    //WKWebView中HTTPCookieStorage.shared单例默认管理着所有的cookie,一般无需我们做额外的操作,如果想单独添加一个cookie,可以把创建的cookie放置到HTTPCookieStorage.shared中即可。
    //创建cookie对象
    let properties = [
        HTTPCookiePropertyKey.name: "cookieName",
        HTTPCookiePropertyKey.value: "cookieValue",
        HTTPCookiePropertyKey.domain: "example.com",
        HTTPCookiePropertyKey.path: "/",
        HTTPCookiePropertyKey.expires: NSDate(timeIntervalSinceNow: 31556926)
    ] as [HTTPCookiePropertyKey : Any]
    let cookie = HTTPCookie(properties: properties)!
    // 将cookie添加到cookie storage中
    HTTPCookieStorage.shared.setCookie(cookie)

    
    webView = WKWebView(frame: .zero, configuration: webViewConfiguration)
    webView.uiDelegate = self
    webView.navigationDelegate = self
    self.view.addSubview(webView)
    
    loadURL(urlString: "http://localhost:3000/")
}
JS与原生交互代理
//WKScriptMessageHandler
extension H5WKWebViewContainerController {
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        if message.name == "QRCodeScan" {
            print(message)
            
            //JS回调,原生处理完后,通知JS结果
            //原生给js的回调事件 会通过”原生调用js“方式放入到js执行环境的messageQueue中
            let script = "qrResult('jack')"
            message.webView?.evaluateJavaScript(script,completionHandler: { res, _ in
                print(res)
            })
            
        }
    }
}
Scheme拦截
// 自定义处理网络请求Scheme
// WKURLSchemeHandler 的 Delegate
extension H5WKWebViewContainerController {
    func webView(_ webView: WKWebView, start urlSchemeTask: WKURLSchemeTask) {
        if urlSchemeTask.request.url?.scheme == "position" {
            //自定义处理定位scheme
            webView.evaluateJavaScript("qrResult('杭州,之江')")
        }
        print(webView)
    }
    
    func webView(_ webView: WKWebView, stop urlSchemeTask: WKURLSchemeTask) {
        print(webView)
    }
}

 

使用第三方框架WebViewJavascriptBridge调用

WebViewJavascriptBridge是一个原生与JS交互的工具,使用简单,设计巧妙,是一个进行Hybird开发必不可少的工具。
WebViewJavascriptBridge的使用方式很简单,首先创建一个WKWebView变量,然后根据WKWebView变量创建WebViewJavascriptBridge属性。
然后就可以使用WebViewJavascriptBridge属性提供的API进行与H5的交互了。
在JS上下文中注册OC原生方法:
- (void)registerHandler:(NSString *)handlerName handler:(WVJBHandler)handler;
调用JS上下文中已经存在的方法:
- (void)callHandler:(NSString *)handlerName data:(id)data
- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    WKWebView *wkWebView = [[WKWebView alloc] initWithFrame:self.view.frame];
    wkWebView.navigationDelegate = self;
    [self.view addSubview:wkWebView];
    
    [WebViewJavascriptBridge enableLogging];
    self.bridge = [WebViewJavascriptBridge bridgeForWebView:wkWebView];
    
    // 在JS上下文中注册callOC方法
    [self.bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {
        NSLog(@"收到了JS的调用");
        responseCallback(@"Object-C Received");
    }];
    
    // iOS调用JS
    [self.bridge callHandler:@"testJavascriptHandler" data:@{@"state":@"before ready"}];
    
    NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://localhost:3000/"]];
    [wkWebView loadRequest:req];
}
h5中,在JS上下文中注册处理方法
import React from "react"

function setupWebViewJavascriptBridge(callback) {
  if (window.WebViewJavascriptBridge) { return callback(window.WebViewJavascriptBridge); }
  if (window.WVJBCallbacks) { return window.WVJBCallbacks.push(callback); }
  window.WVJBCallbacks = [callback];
  var WVJBIframe = document.createElement('iframe');
  WVJBIframe.style.display = 'none';
  WVJBIframe.src = 'https://__bridge_loaded__';
  document.documentElement.appendChild(WVJBIframe);
  setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}


function WebViewJavaScriptBridgeApp() {

    return (
        <div className="WebViewJavaScriptBridgeApp">
          <div>---------WebViewJavaScript---------</div>
          <div id="buttons"></div>
          <div id="log"></div>
          <div>
          {
              setupWebViewJavascriptBridge(function(bridge) {
                var uniqueId = 1
                function log(message, data) {
                  var log = document.getElementById('log')
                  var el = document.createElement('div')
                  el.className = 'logLine'
                  el.innerHTML = uniqueId++ + '. ' + message + ':<br/>' + JSON.stringify(data)
                  if (log.children.length) { log.insertBefore(el, log.children[0]) }
                  else { log.appendChild(el) }
                }
            
                bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {
                  log('ObjC called testJavascriptHandler with', data)
                  var responseData = { 'Javascript Says':'Right back atcha!' }
                  log('JS responding with', responseData)
                  if (responseCallback !== undefined) {
                     responseCallback(responseData)
                  }
                })
            
                document.body.appendChild(document.createElement('br'))
                if (document.getElementById('buttons') === null) {
                  setTimeout(function() {
                    document.getElementById('buttons').innerHTML = ""
                    var callbackButton = document.getElementById('buttons').appendChild(document.createElement('button'))
                    callbackButton.innerHTML = 'js 调用 OC方法'
                    callbackButton.onclick = function(e) {
                      e.preventDefault()
                      log('JS calling handler "testObjcCallback"')
                      bridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {
                        log('JS got response', response)
                      })
                    }
                  },0)
                }
                
              })  
          }
          </div>
        </div>
      )
}

export default WebViewJavaScriptBridgeApp

 

WKWebViewJavascriptBridge核心知识点分析

WebViewJavascriptBridge对象结构
WebViewJavascriptBridge对象如下:
window.WebViewJavascriptBridge = {
    // 保存js注册的处理函数:messageHandlers[handlerName] = handler;
    registerHandler: registerHandler,
    //JS调用OC方法
    callHandler: callHandler,
    disableJavscriptAlertBoxSafetyTimeout: disableJavscriptAlertBoxSafetyTimeout,
    //JS调用OC的消息队列
    _fetchQueue: _fetchQueue,
    //JS处理OC过来的方法调用。
    _handleMessageFromObjC: _handleMessageFromObjC
};
它的内部结构中有一个队列:JS调用OC的队列_fetchQueue,不过_fetchQueue是一个函数变量,这个函数的返回值才是真正的js调用OC的消息队列。
function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;
}
消息队列的设计模式是与JS运行机制有关的,JS引擎执行的关键是消息队列和事件循环,详情可以阅读文章:
[JS引擎中的线程,事件循环,上下文] https://www.cnblogs.com/zhou--fei/p/17452687.html

消息队列中保存的message结构
消息队列中保存的是一组message字典序列化后的字符串,message消息中保存着方法名handlerName,参数data,回调方法callbackId
一个消息的创建方式如下:
NSMutableDictionary* message = [NSMutableDictionary dictionary];
message[@"data"] = data;
    
NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
self.responseCallbacks[callbackId] = [responseCallback copy];
message[@"callbackId"] = callbackId;
message[@"handlerName"] = handlerName;
在原生与JS的交互中,当发送完消息时只会把对应的回调方法和回调方法ID在各自全局变量中以key,value的方式保存。
原生调用JS时保存如下:
@interface WebViewJavascriptBridgeBase : NSObject
// 在成员变量中定义字段responseCallbacks
@property (strong, nonatomic) NSMutableDictionary* responseCallbacks;
@end


//发送消息时,保存回调ID:回调函数键值对。
- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName {
    NSMutableDictionary* message = [NSMutableDictionary dictionary];
    
    if (data) {
        message[@"data"] = data;
    }
    
    if (responseCallback) {
        NSString* callbackId = [NSString stringWithFormat:@"objc_cb_%ld", ++_uniqueId];
        self.responseCallbacks[callbackId] = [responseCallback copy];
        message[@"callbackId"] = callbackId;
    }
    
    if (handlerName) {
        message[@"handlerName"] = handlerName;
    }
    [self _queueMessage:message];
}
JS对原生发送消息时,保存如下:
// 在JS全局上下文中定义对象responseCallbacks
var responseCallbacks = {};
function _doSend(message, responseCallback) {
    if (responseCallback) {
        var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();
        //保存回调id:回调方法,键值对
        responseCallbacks[callbackId] = responseCallback;
        message['callbackId'] = callbackId;
    }
    sendMessageQueue.push(message);
    messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
WebViewJavascriptBridge的Scheme拦截
WebViewJavascriptBridge对象是原生向JS注入的,而WebViewJavascriptBridge对象向JS注入采用的方式是Scheme拦截。
当h5中发送"__bridge_loaded__"Scheme跳转时,则会在原生触发WebViewJavascriptBridge对象相关数据的注入[_base injectJavascriptFile]。
- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
    if (webView != _webView) { return; }
    NSURL *url = navigationAction.request.URL;
    __strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;

    
    if ([_base isWebViewJavascriptBridgeURL:url]) {
        if ([_base isBridgeLoadedURL:url]) {
            //iOS原生进行js交互环境注入
            [_base injectJavascriptFile];
        } else if ([_base isQueueMessageURL:url]) {
            [self WKFlushMessageQueue];
        } else {
            [_base logUnkownMessage:url];
        }
        decisionHandler(WKNavigationActionPolicyCancel);
        return;
    }
    
    if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {
        [_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];
    } else {
        decisionHandler(WKNavigationActionPolicyAllow);
    }
}
另外其他与消息发送无关的调用也采用的Scheme拦截,如消息队列的刷新。
 

另外

iOS代码Demo地址:https://github.com/zhfei/MixContainer
H5代码Demo地址:https://github.com/zhfei/ReactBasicKnowledge
H5页面使用的是React框架生成的Demo,下载后执行:
cd h5-demo
npm install
npm start
就可以在线调试了
 

参考文章:
https://juejin.cn/post/7097845525587689486
https://blog.csdn.net/ws1836300/article/details/119182057
 
 


与APP中Web容器的核心实现相似的内容:

APP中Web容器的核心实现

现在的业务型APP中,采用纯原生开发策略的已经很少了,大部分都使用的混合开发。如原生,H5,ReactNative,Flutter,Weex它们之间任意的组合就构成了混合开发。 其中原生+H5是出现最早的,老牌混合方案,即使过来多年,在现在的混合开发方案中H5也是使用率非常高的。在APP中嵌入Web

【Azure 应用服务】Web App Service 中的 应用程序配置(Application Setting) 怎么获取key vault中的值

问题描述 App Service中,如何通过 Application Setting 来配置 Key Vault中的值呢? 问题解答 首先,App Service服务可以直接通过引用的方式,无需代码的情况下,为Application Setting中的Key配置Key Vault中保存的值。参考官方

【Azure 应用服务】在App Service中新建WebJob时候遇见错误,不能成功创建新的工作任务

问题描述 在Azure App Service界面上,添加新的Web Job(工作任务)时,一直添加失败。无详细错误提示,在App Service的Activity Logs(活动日志)中,根本没有添加失败的任何操作记录,这是什么情况呢? Adding WebJob: Failed to add t

python flask 提供web的get/post开发

转载请注明出处: 使用python flask框架编写web api中的get与post接口,代码编写与调试示例如下: from flask import Flask, request, jsonify app = Flask(__name__) @app.route('/api/get', met

【Azure 应用服务】Azure Web App 服务默认支持一些 Weak TLS Ciphers Suite,是否有办法自定义修改呢?

问题描述 当 Azure Web App 进行安全扫描后,发现依旧支持很多弱TLS加密套件(Weak TLS Ciphers Suite),那么是否有办法来关闭这些弱的加密套件呢? 在Windows IIS环境中,可以通过修改注册表修改 For Microsoft IIS, you should m

Vue第三方库与插件实战手册

这篇文章介绍了如何在Vue框架中实现数据的高效验证与处理,以及如何集成ECharts、D3.js、Chart.js等图表库优化数据可视化效果。同时,探讨了Progressive Web App(PWA)的接入与优化策略,以提升Web应用的用户体验与加载速度。

APP中RN页面热更新流程-ReactNative源码分析

平时使用WebStorm或VSCode对RN工程中的文件修改后,在键盘上按一下快捷cmd+s进行文件保存,此时当前调试的RN页面就会自动进行刷新,这是RN开发相比于原生开发一个很大的优点:热更新。 那么,从按一下快捷cmd+s到RN页面展示出最新的JS页面,这个过程是怎样发生的呢?下面根据时间顺序来

APP中RN页面渲染流程-ReactNative源码分析

在APP启动后,RN框架开始启动。等RN框架启动后,就开始进行RN页面渲染了。 RN页面原生侧页面渲染的主要逻辑实现是在RCTUIManager和RCTShadowView完成的。 通过看UIMananger的源码可以看到,UIMananger导出给JS端的API接口在对UI的操作上,基本都会同时对

在原生APP中集成Unity容器

随着技术的发展,越来越多的APP期望拥有3D,AR的能力。要达到这个目标可以选择使用原生开发,也可以使用Unity成熟的3D开发技术链,通过嵌入的方式将Unity容器嵌入到APP中。这里介绍的是通过嵌入Unity容器的方式来实现APP的3D,AR能力的。 Unity集成到iOS应用的本质是将Unit

flutter系列之:在flutter中使用相机拍摄照片

简介 在app中使用相机肯定是再平常不过的一项事情了,相机肯定涉及到了底层原生代码的调用,那么在flutter中如何快速简单的使用上相机的功能呢? 一起来看看吧。 使用相机前的准备工作 flutter中为使用camera提供了一个叫做camera的插件,我们首先需要安装这个插件。 安装插件的步骤很简