当前位置: 首页 > 编程日记 > 正文

iOS WebviewJavascriptBridge 源码研读笔记

这两天接近元旦,事情稍微少些,有些时间,索性写点什么,就从最擅长的iOS混合开发写起了,由于iOS开发经验不到四年吧,期间还搞了一年半的前端,有些知识可能还是积累的不足,能力不足,水平有限,可能有谬误希望各位读者发现的话及时指正,感激不尽。

至于WebviewJavascriptBridge的介绍,此处不再啰嗦了,既然能看到本文,相比对该三方库或多或少还是有所了解的吧。我在申明一点,本文中涉及的demo是直接拿的WebviewJavascriptBridge的,并未做任何修改,直接拿来研究

看了看比较流行的WebviewJavascriptBridge这个三方库的源码,发现好多js和oc部分的核心代码几乎是对称的,所以觉得最好是js和oc代码一起读,这样才更容易理解,也能发现其对称美。。。

要搞明白其调用逻辑,最好是用Safari连上调试一把哈,在网页检查其中我们用oc载入的js代码好难找啊(至少我是花了一番功夫才找到了),莫慌,是在找不到的话在搜索栏里面搜一下WebviewJavascriptBridge,然后在对应的代码出都打上断点,这下就可以研究了

至于有些同学不知道如何打开Safari的调试模式的,请移步至传送门这个方法mac 的Safari也同样受用哈

WebViewJavascriptBridge VS WKWebViewJavascriptBridge

这个框架还是有点666啊,既支持iOS又支持mac OS 但鉴于我们mac OS 用的少,就直接看iOS部分了

红线框出来的部分也就是就是WebviewJavascriptBridge框架的核心代码部分

WebViewJavascriptBridge_JS ==> js核心代码部分,负责js端消息的组装,转发 WebViewJavascriptBridgeBase ==> oc核心代码部分,负责oc端消息组装,转发 WebViewJavascriptBridge ==> 对于UIWebView的进行的封装,是基于WebViewJavascriptBridgeBase的 WKWebViewJavascriptBridge ==> 对于WKWebView的进行的封装,是基于WebViewJavascriptBridgeBase的

至于前面两个核心类会在下一小节中做详细的阐述,本小结就只做后面两个类的分析阐述 直接上图了

对比WebViewJavascriptBridgeWKWebViewJavascriptBridge两个类的头文件,看处WKWebViewJavascriptBridge多了一个reset方法,其他的方法两个类几乎一毛一样,我们继续看.m实现文件也证实了这一点,差别仅在于webview的实现,这也印证了这个框架的核心只是WebViewJavascriptBridgeBase,其核心都是通过js中去“loadUrl”(这个是本人自己习惯这么说,方便理解啊,实际上和loadUrl有点差别,不过道理是一样的)然后webview在代理方法中去拦截特殊约定好的url,然后进行消息的处理。

以下是wkwebview的代理方法截取

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {if (webView != _webView) { return; }NSURL *url = navigationAction.request.URL;//获取js “loadUrl”的url链接__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;if ([_base isWebViewJavascriptBridgeURL:url]) {//是不是WebViewJavascriptBridge约定的urlif ([_base isBridgeLoadedURL:url]) {//是不是初始化指令__bridge_loaded__//注入核心js代码[_base injectJavascriptFile];} else if ([_base isQueueMessageURL:url]) {//是不是消息指令__wvjb_queue_message__//调用WebViewJavascriptBridgeBase的API去分发消息[self WKFlushMessageQueue];} else {//未知的url[_base logUnkownMessage:url];}//取消跳转decisionHandler(WKNavigationActionPolicyCancel);return;}//能走到这里证明已经不是WebViewJavascriptBridge约定的url了,做正常跳转if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {[_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];} else {decisionHandler(WKNavigationActionPolicyAllow);}
}
复制代码

以下是UIWebView代理的方法

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {if (webView != _webView) { return YES; }//获取js “loadUrl”的url链接NSURL *url = [request URL];__strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;if ([_base isWebViewJavascriptBridgeURL:url]) {//是不是WebViewJavascriptBridge约定的urlif ([_base isBridgeLoadedURL:url]) {//是不是初始化指令__bridge_loaded__//注入核心js代码[_base injectJavascriptFile];} else if ([_base isQueueMessageURL:url]) {//是不是消息指令__wvjb_queue_message__NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];//调用WebViewJavascriptBridgeBase的API去分发消息[_base flushMessageQueue:messageQueueString];} else {//未知的url[_base logUnkownMessage:url];}//取消跳转return NO;} else if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:shouldStartLoadWithRequest:navigationType:)]) {return [strongDelegate webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];} else {return YES;}
}
复制代码

js 调用oc

//jsbridge.callHandler('testObjcCallback', {'foo': 'bar'}, function(response) {log('JS got response', response)})//oc
[_bridge registerHandler:@"testObjcCallback" handler:^(id data, WVJBResponseCallback responseCallback) {NSLog(@"testObjcCallback called: %@", data);responseCallback(@"Response from testObjcCallback");}];
复制代码

UIWebView里面还看到了mac OS平台的处理,其实质跟这个也是一样的,有兴趣的同学可以自行研究啊。

由于UIWebView和WKWeb到WebViewJavascriptBridgeBase层的实现原理什么的基本上是一致的,我这里就以WKWebView精心给讲解了

WebViewJavascriptBridgeBase的实现分析

前文已经说过,该框架里面有好多地方oc和js是相对称的,有很多类似的实现,现在就先引用几个对比一下

这个是注册handler的方法

//js
bridge.registerHandler('testJavascriptHandler', function(data, responseCallback) {log('ObjC called testJavascriptHandler with', data)var responseData = { 'Javascript Says':'Right back atcha!' }log('JS responding with', responseData)responseCallback(responseData)})//oc		
id data = @{ @"greetingFromObjC": @"Hi there, JS!" };
[_bridge callHandler:@"testJavascriptHandler" data:data responseCallback:^(id response) {NSLog(@"testJavascriptHandler responded: %@", response);}];
复制代码

js调用oc和oc调用js时候都各自维护了一套消息对列队列,回调

var messageHandlers = {}; //消息
var responseCallbacks = {}; //回调@property (strong, nonatomic) NSMutableDictionary* responseCallbacks;
@property (strong, nonatomic) NSMutableDictionary* messageHandlers;
复制代码

相互交互的消息内容

//这是js发给oc的
{callbackId = "cb_1_1514520891115";data =     {foo = bar;};handlerName = testObjcCallback;
}//这是oc发给js的
{callbackId = "objc_cb_1";data =     {greetingFromObjC = "Hi there, JS!";};handlerName = testJavascriptHandler;
}复制代码

send方法也跟双胞胎似的,傻傻的不清楚啊

//jsfunction _doSend(message, responseCallback) {if (responseCallback) {var callbackId = 'cb_'+(uniqueId++)+'_'+new Date().getTime();responseCallbacks[callbackId] = responseCallback;message['callbackId'] = callbackId;}sendMessageQueue.push(message);messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}//oc
- (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];
}复制代码

下面来看一看我们大Objective-c 调用JavaScript部分的实现过程

1、原生按钮回调bridge的方法- (void)callHandler:(NSString *)handlerName data:(id)data responseCallback:(WVJBResponseCallback)responseCallback将消息发出去

- (void)callHandler:(id)sender {id data = @{ @"greetingFromObjC": @"Hi there, JS!" };[_bridge callHandler:@"testJavascriptHandler" data:data responseCallback:^(id response) {NSLog(@"testJavascriptHandler responded: %@", response);}];
}
复制代码

2、调用WebViewJavascriptBridgeBase的方法- (void)sendData:(id)data responseCallback:(WVJBResponseCallback)responseCallback handlerName:(NSString*)handlerName去组一波数据

- (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];
}
复制代码

send 方法将oc传个js的数据组装成特定的json格式,如下所示:

{callbackId = "objc_cb_1";data =     {greetingFromObjC = "Hi there, JS!";};handlerName = testJavascriptHandler;
}
复制代码

3、将组好的数据向下传递调用方法- (void)_queueMessage:(WVJBMessage*)message

- (void)_queueMessage:(WVJBMessage*)message {//self.startupMessageQueue这个是初始化的消息队列,一般没有自定义初始化消息队列的话这个就是nil,直接就走到else里面去了if (self.startupMessageQueue) {[self.startupMessageQueue addObject:message];} else {[self _dispatchMessage:message];}
}
复制代码

4、调用方法- (void)_dispatchMessage:(WVJBMessage*)message序列化消息,并在主线程中转发 序列化后的样板啊 {\"callbackId\":\"objc_cb_1\",\"data\":{\"greetingFromObjC\":\"Hi there, JS!\"},\"handlerName\":\"testJavascriptHandler\"} 然后会调用_evaluateJavascript方法,实质上这个地方是通过代理去调用不同的webview的执行js的方法 UIwebview会调用 - (nullable NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

WKWebView会调用 - (void)evaluateJavaScript:(NSString *)javaScriptString completionHandler:(void (^ _Nullable)(_Nullable id, NSError * _Nullable error))completionHandler;

5、此举可以用oc来调用js方法,此处就调到了js的方法WebViewJavascriptBridge._handleMessageFromObjC() 看看它的代码啊

function _dispatchMessageFromObjC(messageJSON) {if (dispatchMessagesWithTimeoutSafety) {setTimeout(_doDispatchMessageFromObjC);} else {_doDispatchMessageFromObjC();}function _doDispatchMessageFromObjC() {//将json字符串转换成json对象(可以理解为oc中的字典对象)var message = JSON.parse(messageJSON);var messageHandler;var responseCallback;if (message.responseId) {//交互完成后的回调函数会调用这里responseCallback = responseCallbacks[message.responseId];if (!responseCallback) {return;}responseCallback(message.responseData);delete responseCallbacks[message.responseId];} else {//直接交互调用时走到这个方法if (message.callbackId) {var callbackResponseId = message.callbackId;responseCallback = function(responseData) {_doSend({ handlerName:message.handlerName, responseId:callbackResponseId, responseData:responseData });};}//查找js中注册过的方法,若没有js注册此方法则报错,反之取出储存的该方法,并调用之var handler = messageHandlers[message.handlerName];if (!handler) {console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);} else {handler(message.data, responseCallback);}}}}
复制代码

此时执行方法_doDispatchMessageFromObjC时会走到else这一步,如果oc调的这个方法需要回调,则message.callbackId不会为undefined,则js会调用_doSend方法回调oc,完成之后调用回调函数

6、上面方法完成最后一步是send方法了 先看看这个回调函数实现

function _doSend(message, responseCallback) {if (responseCallback) {var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();responseCallbacks[callbackId] = responseCallback;message['callbackId'] = callbackId;}sendMessageQueue.push(message);messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;
}
复制代码

此处由于本身就是oc调用js的回调,没有再js调用oc后回调js,则responseCallback为undefined,直接将其加入消息队列中,并调用messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE来调用原生,这个调法感觉有些奇怪,但从现象和我的理解来看就是给iframe加了一个src,类似于load了一个特殊的url 即https://__wvjb_queue_message__/

7、WKWebview的代理方法- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler会拦截到这个url

- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {if (webView != _webView) { return; }NSURL *url = navigationAction.request.URL;//获取js “loadUrl”的url链接__strong typeof(_webViewDelegate) strongDelegate = _webViewDelegate;if ([_base isWebViewJavascriptBridgeURL:url]) {//是不是WebViewJavascriptBridge约定的urlif ([_base isBridgeLoadedURL:url]) {//是不是初始化指令__bridge_loaded__//注入核心js代码[_base injectJavascriptFile];} else if ([_base isQueueMessageURL:url]) {//是不是消息指令__wvjb_queue_message__//调用WebViewJavascriptBridgeBase的API去分发消息[self WKFlushMessageQueue];} else {//未知的url[_base logUnkownMessage:url];}//取消跳转decisionHandler(WKNavigationActionPolicyCancel);return;}//能走到这里证明已经不是WebViewJavascriptBridge约定的url了,做正常跳转if (strongDelegate && [strongDelegate respondsToSelector:@selector(webView:decidePolicyForNavigationAction:decisionHandler:)]) {[_webViewDelegate webView:webView decidePolicyForNavigationAction:navigationAction decisionHandler:decisionHandler];} else {decisionHandler(WKNavigationActionPolicyAllow);}
}
复制代码

这里截取到的url是__wvjb_queue_message__则会调用方法- (void)WKFlushMessageQueue

8、分发消息

- (void)WKFlushMessageQueue {//该方法首先会调用webViewJavascriptFetchQueyCommand 方法,这个方法是在js中 调用_fetchQueue()这个方法用来获取queue中消息sendMessageQueue
webViewJavascriptFetchQueyCommand
[_webView evaluateJavaScript:[_base webViewJavascriptFetchQueyCommand] completionHandler:^(NSString* result, NSError* error) {if (error != nil) {NSLog(@"WebViewJavascriptBridge: WARNING: Error when trying to fetch data from WKWebView: %@", error);}[_base flushMessageQueue:result];}];
}
复制代码

sendMessageQueue是一个在js中维护的消息队列,是一个数组sendMessageQueue拿给oc然后将数据清空,在上面的这个oc函数evaluateJavaScript中result就是该js方法的返回值,即消息队列[{"handlerName":"testJavascriptHandler","responseId":"objc_cb_4","responseData":{"Javascript Says":"Right back atcha!"}}] 9、查找js中维护的消息对,只有匹配上了才能调用上

function _fetchQueue() {var messageQueueString = JSON.stringify(sendMessageQueue);sendMessageQueue = [];return messageQueueString;
}
复制代码

oc一旦取到了js给返回的值,就会调用方法- (void)flushMessageQueue:(NSString *)messageQueueString

- (void)flushMessageQueue:(NSString *)messageQueueString{if (messageQueueString == nil || messageQueueString.length == 0) {NSLog(@"WebViewJavascriptBridge: WARNING: ObjC got nil while fetching the message queue JSON from webview. This can happen if the WebViewJavascriptBridge JS is not currently present in the webview, e.g if the webview just loaded a new page.");return;}id messages = [self _deserializeMessageJSON:messageQueueString];for (WVJBMessage* message in messages) {if (![message isKindOfClass:[WVJBMessage class]]) {NSLog(@"WebViewJavascriptBridge: WARNING: Invalid %@ received: %@", [message class], message);continue;}[self _log:@"RCVD" json:message];NSString* responseId = message[@"responseId"];if (responseId) {WVJBResponseCallback responseCallback = _responseCallbacks[responseId];responseCallback(message[@"responseData"]);[self.responseCallbacks removeObjectForKey:responseId];} else {WVJBResponseCallback responseCallback = NULL;NSString* callbackId = message[@"callbackId"];if (callbackId) {//看有没有回调,有些时候我们是不需要回调函数的,所以这里做一波判断responseCallback = ^(id responseData) {if (responseData == nil) {responseData = [NSNull null];}WVJBMessage* msg = @{ @"responseId":callbackId, @"responseData":responseData };[self _queueMessage:msg];};} else {responseCallback = ^(id ignoreResponseData) {// Do nothing};}//在这里匹配一波,要是取到了就搞起啊WVJBHandler handler = self.messageHandlers[message[@"handlerName"]];if (!handler) {NSLog(@"WVJBNoHandlerException, No handler for message from JS: %@", message);continue;}handler(message[@"data"], responseCallback);}}
}复制代码

有responseId则证明是回调方法返回,然后就是在oc中的_responseCallbacks返回回调方法中找到该回调block,并回调相应的方法

这就是oc调用js的流程:大概总结如下 oc 告诉js我要发交互发消息了 ==> js 获取到通知,并主动去“load” __wvjb_queue_message__ 告诉oc把消息的内容传过来

oc 得知js已经知道要传递消息了,主动调用js中的方法WebViewJavascriptBridge._handleMessageFromObjC()并在这个方法里面将消息以字符串的形式传过去 ==> js拿到消息内容后进行解析,在js上下文中保存的消息名中进行匹配,得到js的调用方法,并调用该方法

JavaScript调用objective-c的方法

看完oc调用js的整个流程以后,再来看js调用oc的流程就明晰了很多,现在作如下讲解:

1、js中的按钮首先会触发器onclick事件,然后调用bridge的方法callHandler,

function callHandler(handlerName, data, responseCallback) {if (arguments.length == 2 && typeof data == 'function') {responseCallback = data;data = null;}_doSend({handlerName: handlerName,data: data}, responseCallback);}
复制代码

2、callHandler在做了简单的参数处理后转而调用核心函数_doSend方法

function _doSend(message, responseCallback) {if (responseCallback) {var callbackId = 'cb_' + (uniqueId++) + '_' + new Date().getTime();responseCallbacks[callbackId] = responseCallback;message['callbackId'] = callbackId;}sendMessageQueue.push(message);messagingIframe.src = CUSTOM_PROTOCOL_SCHEME + '://' + QUEUE_HAS_MESSAGE;}
复制代码

_doSend方法负责组装参数,并保存到上下文中,然后就“loadUrl”了

3、接下来就是wkwebview代理方式发光的时候了,- (void)webView:(WKWebView *)webView decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler拦截到约定好的url https://__wvjb_queue_message__/

4、是时候调用一波原生方法- (void)WKFlushMessageQueue来获取消息队列了

5、调用方法- (void)flushMessageQueue:(NSString *)messageQueueString该方法中处理序列化的字符串变成数组,遍历消息队列,查找到oc中已经注册好的对应的方法,匹配成功后调用该方法,则会调到注册处的回调方法

完成相应处理,并回调其callback

6、此时处理回调的msg,包装好后插入到oc需要处理的消息队列

7、处理消息,将字典转换成json字符串,调用方法WebViewJavascriptBridge._handleMessageFromObjCjs方法将oc的数据传递给js

8、紧接着就是js方法调用_handleMessageFromObjC() ==> _handleMessageFromObjC() ==> _doDispatchMessageFromObjC()

9、然后就是找到注册过的回调方法,回调相关的函数

这便是js调用oc并获取回调的流程。是不是觉得oc ==> js ==> oc 和 js==> oc ==> js 两个流程很相似,可以说是完美对称了?这也就是开头所说的对称美啊!

WebViewJavascriptBridge初始化过程

html代码里面可是必备的哈,熟悉使用WebViewJavascriptBridge框架的痛惜应该是比较熟悉的了

function setupWebViewJavascriptBridge(callback) {if (window.WebViewJavascriptBridge) { return callback(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)
}
复制代码

html已加载后就会一把它,它会“loadUrl” https://__bridge_loaded__/load了这个后,

这时,我们会injectJavascriptFile,将WebViewJavascriptBridge_JS.m中的js注入到web运行的上下文中,然后检查startupMessageQueue,看有没有初始化时候需要调用什么方法(我理解应该是这样的,方便自定义一些什么初始化方法什么的),默认这个是nil,也就不会执行下面的内容

注入js后,紧接着就是执行js脚本了,来断点一波

我们看到也就是一波初始化了,然后就是注册方法_disableJavascriptAlertBoxSafetyTimeout这个东西,暂时木有用过啊

这就是我研读WebViewJavascriptBridge框架源码的笔记了,大神看了勿喷啊。以后还有我在公司项目中的关于wkwebview开发的一下心得,近期会总结一波,谢谢亲的耐心阅读啊,哪里有问题的可以私信我了~

转载于:https://juejin.im/post/5a45eb105188251fbd33f148

相关文章:

Debian上网的问题小结

装完系统或装系统中很重要的部分就是配置网络的问题了,记得刚安装Debian的时候还不知道dhclient eth0真是杯具。 关于怎么设置识别网卡su->输入root密码->nano /etc/network/interface然后分情况,如果你是有线的且只有一个网卡,就叫eth…

【组队学习】【28期】青少年编程(Scratch 一级)

青少年编程(Scratch 一级) 论坛版块: http://datawhale.club/c/team-learning/34-category/34 开源内容: https://github.com/datawhalechina/team-learning-program/tree/master/Scratch 学习目标 全国青少年软件编程等级考…

java培训教程分享:Java中怎样将数据对象序列化和反序列化?

本期为大家介绍的java培训教程是关于“Java中怎样将数据对象序列化和反序列化?”的内容,相信大家都知道,程序在运行过程中,可能需要将一些数据永久地保存到磁盘上,而数据在Java中都是保存在对象当中的。那么我们要怎样将对象中的…

C#实现的几种委托

//普通委托DeleteShow ds new DeleteShow(ShowName);Console.WriteLine("----------------------");Console.WriteLine("普通委托----请输入用户名:");string Name Console.ReadLine();Console.WriteLine(ds(Name));Console.WriteLine("-----------…

AFN框架使用整理

1.AFN框架基本使用 1 AFN内部结构 AFN结构体 NSURLConnection(iOS9.0废弃了) AFURLConnectionOperationAFHTTPRequestOperationAFHTTPRequestOperationManager(封装了常用的 HTTP 方法) 属性 baseURL :AFN建议开发者针对 AFHTTPRequestOperationManager 自定义个一个单例子类&a…

【青少年编程】【一级】小镇一日游

「青少年编程竞赛交流群」已成立(适合6至18周岁的青少年),公众号后台回复【Scratch】或【Python】,即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料(视频、代码、文档&…

参加java培训后,就业方向有哪些

参加java培训后,就业方向有哪些?很多正在参加java培训的同学对这个问题都比较关注,那么下面小编就针对这个问题为大家做下详细的介绍,希望能够帮助到大家。 参加java培训后,就业方向有哪些?首先我们来了解一下Java的应用领域&am…

访问本地文件403

电脑不知道怎么设置的根目录下看不到所有文件夹 而且打开一个新的文件夹显示 Forbidden You dont have permission to access /atina/ on this server. 最后没办法只能手动把文件夹加上权限 chmod -R 777 atina //-R 递归修改,“/文件夹/“以下所有内容&#xff08…

iOS微信支付集成 SDK1 7 5

最近做了一个新项目,涉及到支付宝和微信支付,支付宝和微信都是业界的老大哥,相信大家都有所觉得文档、SDK都是各种坑吧(纯粹吐槽而已),这是继上篇支付宝支付集成后接着的微信支付集成。 1.微信商户申请步骤…

【青少年编程(第26周)】一下子多了很多事!

2021年08月15日(周日)晚20:00我们在青少年编程竞赛交流群开展了第二十六次直播活动。我们直播活动的主要内容如下: 首先,我们奖励了上周测试超过60分的小朋友。 其次,介绍了Datawhale青少年组队学习活动的规划&#x…

Python培训教程分享:10款超好用的Python开发工具

学会Python技术后,我们在参加工作的时候如果能有辅助工具的话,那么会很大程度的提高我们的工作效率,那么Python都有哪些好用的开发工具呢?下面小编就为大家详细的介绍一下10款超好用的Python开发工具! Python培训教程分享:10款超…

pfSense book之2.4安装指南

pfSense商店的硬件产品都预装了pfSense软件。要重新安装pfSense软件或将其安装到其他硬件,请按照本章所述下载安装程序映像。警告预先从pfSense商店或授权合作伙伴以外的商业供应商预装pfSense软件的硬件不值得信任。 第三方可能已经对软件进行了未经授权的、未知的…

css中绝对定位中的left和top属性

<html><head><title>Absolute Position</title><style type "text/css">/*body具有红色边框&#xff0c;宽度300px&#xff0c;高度300px*/body {border: 1px solid red;width: 300px;height: 300px;}/*被绝对定位,颜色为silver*/div…

教育部办公厅关于2020-2021学年面向中小学生的全国性竞赛活动名单的公示

来源&#xff1a; http://www.moe.gov.cn/jyb_xxgk/s5743/s5745/202007/t20200701_469571.html 教育部办公厅关于2020-2021学年面向中小学生的全国性竞赛活动名单的公示 根据《教育部办公厅印发<关于面向中小学生的全国性竞赛活动管理办法&#xff08;试行&#xff09;&g…

UI设计培训分享:UI设计师如何准备面试?

很多参加完UI培训的同学后期都是需要面临找工作的&#xff0c;那么面试环节是必须要经历的&#xff0c;今天小编为大家介绍的内容就是关于UI设计师如何准备面试?希望下面的内容能够帮助到正在找工作的同学们。 UI设计培训分享&#xff1a;UI设计师如何准备面试? 一、 简历要重…

【CLR的执行模型:将源代码编译成托管模块】

CLR:由多种不同编程语言使用的运行库 托管模块&#xff1a;中间语言和元数据 非托管语言&#xff1a;C/C&#xff0c;不过此为特殊&#xff0c;可以写成托管代码 托管语言&#xff1a;C# 托管模块 1,PE32/PE32头:PE32运行在win32位系统中,win32运行在64位系统 2,CLR头,包括CLR版…

【组队学习】【28期】数据采集从入门到精通

数据采集从入门到精通 论坛版块&#xff1a; http://datawhale.club/c/team-learning/38-category/38 开源内容&#xff1a; https://github.com/datawhalechina/team-learning-program/tree/master/CollectData 学习目标 随着数字化的不断推进&#xff0c;数据采集在数据…

url传递数据类型

php中传递数据&#xff0c;get或post方式为啥用字符串传递&#xff0c;为什么不能直接用数组形式&#xff0c;用的话可以吗 转载于:https://www.cnblogs.com/guoyinglichong/p/8184723.html

零基础参加java培训哪家机构好

零基础想要学会java技术&#xff0c;那么参加java培训机构是非常明智的选择&#xff0c;为什么这么说呢?因为如果选择自学&#xff0c;那么不仅学到的技术杂乱无章&#xff0c;后期工作也会有很大阻碍&#xff0c;系统的学习就会好很多&#xff0c;那么如今零基础参加java培训…

如何通过一行代码下载B站视频?

文章目录如何通过一行代码下载B站视频&#xff1f;Step1&#xff1a;安装you-getStep2&#xff1a;下载B站视频总结如何通过一行代码下载B站视频&#xff1f; You-Get 是一个基于 Python 3 的下载工具。使用 You-Get 可以很轻松的下载网络上的视频、图片及音乐。 下面我们介绍…

软件架构是软件的组织形式

软件架构是软件的组织形式 软件架构是软件的逻辑组织形式&#xff1b; 软件框架是软件的物理组织形式。

linux创建用户并授予sudo权限

1. 新建用户 rootubuntu:~# adduser hadoop rootubuntu:~# passwd hadoop #为hadoop用户设置密码 2. 为hadoop用户添加sudo权限 rootubuntu:~# sudo gedit /etc/sudoers 把root那行复制后改成hadoop&#xff0c;hadoop就有了sudo权限&#xff0c;需要sudo密码则为ALL&#xff0…

学java培训开发需要多少钱

学java培训开发需要多少钱?这个问题对于想要学习java技术的小伙伴们来说是非常重要的&#xff0c;如今市面上的java培训机构非常多&#xff0c;收费标准都是多少呢?那么来看看下面的详细介绍吧。 学java培训开发需要多少钱?不少学员会直接咨询费用的问题&#xff0c;确实&am…

Scratch青少年编程能力等级测试模拟题(一级)

青少年编程竞赛交流群已成立&#xff08;适合6至18周岁的青少年&#xff09;&#xff0c;公众号后台回复【Scratch】或【Python】&#xff0c;即可进入。如果加入了之前的社群不需要重复加入。 微信后台回复“资料下载”可获取以往学习的材料&#xff08;视频、代码、文档&…

ODBC、OLE连接各种数据库的连接字符串

简介 我们在使用数据库的时候&#xff0c;首先需要打开这个数据库。我们可以找到集中类型的数据库&#xff0c;每个都使用不同的连接方式。下面列举了一下主要的数据库的连接字符串A&#xff1a;使用ODBC方式1:dBASE连接字符串strConnection _T("Driver{Microsoft dBASE …

opencv3 图像处理(一)图像缩放( python与c++ 实现)

opencv3 图像处理 之 图像缩放&#xff08; python与c实现 &#xff09; 一. 主要函数介绍 1) 图像大小变换 Resize () 原型&#xff1a; void Resize(const CvArr* src,CvArr* dst,intinterpolationCV_INTER_LINEAR); 说明&#xff1a; src 表示输入图像。 dst表示输出图像。 …

web前端培训要学多久

​ 近几年web前端在互联网行业的快速发展&#xff0c;很多人都对web前端是非常感兴趣的&#xff0c;学习web前端技术的人越来越多&#xff0c;那么参加web前端培训要学多久呢?相信大家都想知道这个答案&#xff0c;我们来看看下面的详细介绍。 web前端培训要学多久?首先对于前…

【Arduino】开发入门教程【一】什么是Arduino

Arduino Arduino 是一款便捷灵活、方便上手的开源电子原型平台&#xff0c;包含硬件&#xff08;各种型号的arduino板&#xff09;和软件&#xff08;arduino IDE)。它适用于艺术家、设计师、爱好者和对于“互动”有兴趣的朋友们。Arduino&#xff0c;是一个基于开放原始码的软…

【云计算的1024种玩法】回忆经典,用虚拟主机重建复古DZ和无心宠物

前言 在移动互联网和大型网游没有普及前&#xff0c;很多地方论坛或者专业性论坛都会有一个 —— “无心宠物” 插件来引流和吸引用户&#xff0c;可以说是当下网页游戏的老前辈了。还记得我以前就是为了玩这个游戏&#xff0c;就在各个论坛疯狂发帖赚经验&#xff0c;想来还是…

谢文睿:西瓜书 + 南瓜书 吃瓜系列 9. 集成学习(上)

Datawhale南瓜书是经典机器学习教材《机器学习》&#xff08;西瓜书&#xff09;的公式推导解析指南&#xff0c;旨在让在学习西瓜书的过程中&#xff0c;再也没有难推的公式&#xff0c;学好机器学习。 航路开辟者&#xff1a;谢文睿、秦州开源内容&#xff1a;https://githu…