上一篇文章介绍了通过UIWebView实现了OC与JS交互的可能性及实现的原理,并且简单的实现了一个小的示例DEMO,当然也有一部分遗留问题,使用原生实现过程比较繁琐,代码难以维护。这篇文章主要介绍下开源库WebViewJavascriptBridge的实现原理和使用方法,并用此开源库重写之前的示例,同样,本文的示例代码我会在文章后面给出欢迎star

我们在上一篇文章结尾处简要介绍了WebViewJavascriptBridge的实现原理也是基于UIWebView的协议拦截,通过阅读源码发现,中间有很多地方是值得学习的。涉计到消息派遣,数据序列化,异常处理等技术思想

一、WebViewJavascriptBridge的实现原理

WebViewJavascriptBridge如其名字定义,就相当于一座桥梁,两端连接了Obj-C和JavaScript。它提供了OC和JS互调的方法接口,方法在互调之前,我们需要向对方注册我们的方法列表

1. OC调用和回调JS

1
2
3
4
5
//1.1 JS注册OC的方法并实现回调OC
bridge.registerHandler('JS Echo'function(data, responseCallback) {
  console.log("JS Echo called with:", data)
  responseCallback(data)
})

  

1
2
3
4
//1.2 OC调用JS方法并实现回调函数
[self.bridge callHandler:@"JS Echo" responseCallback:^(id responseData) {
    NSLog(@"ObjC received response: %@", responseData);
}];

  

2. JS调用和回调OC

1
2
3
4
5
//2.1 OC注册JS的方法并实现回调JS
[self.bridge registerHandler:@"ObjC Echo" handler:^(id data, WVJBResponseCallback responseCallback) {
    NSLog(@"ObjC Echo called with: %@", data);
    responseCallback(data);
}];

  

1
2
3
4
//2.2 JS调用OC方法并实现回调函数
bridge.callHandler('ObjC Echo', {'key':'value'}, function responseCallback(responseData) {
  console.log("JS received response:", responseData)
})

  

首先看一张模型图(图片来自网络)

透过模型图,我们可以清楚的看到WebViewJavascriptBridge充当的桥梁是如何工作的

具体用法,参考官方的使用指南

下面我们我们来具体分析WebViewJavascriptBridge实现原理:

先看下库的文件,一共有8个文件,而我们关心的文件只有3个,因为文件5,6是兼容WKWebView,实现原理都是一样的,不需要看,所有的.h文件是声明.m中的方法列表也不需要看

1
2
3
4
5
6
7
8
1. WebViewJavascriptBridge.h  
2. WebViewJavascriptBridge.m 
3. WebViewJavascriptBridgeBase.h  
4. WebViewJavascriptBridgeBase.m 
5. WKWebViewJavascriptBridge.h  
6. WKWebViewJavascriptBridge.m  
7. WebViewJavascriptBridge_JS.h  
8. WebViewJavascriptBridge_JS.m

接下来我们需要重点关注文件2,4,8中的代码,值得一提的是文件7,8是 注入JS 相关的文件,之前JS代码是放在一个叫WebViewJavascriptBridge.js.txt的文件,换掉的原因可以在文件8的头部找到相关踪迹,因为大概是和使用者的库及其封装有影响。接下来查看源码的初始化部分:

1. 首先我们有一个bridge对象

1
@property (nonatomic, strong) WebViewJavascriptBridge *bridge;

2. 然后对齐进行初始化,初始化的过程中我们传入了当前的webView对象,bridge在初始化(文件2)的过程中,把自己作为webView的代理对象,同时设置对应平台的代理(iOS/OSX,这里兼容了两个平台),这样在webView加载的时候,bridge就可以获取到相关方法的调用和处理。至此我们完成了OC端bridge的初始化

1
self.bridge = [WebViewJavascriptBridge bridgeForWebView:self.webView];

3. bridge在初始化的时候会触发bridgeBase的初始化(文件4),实际上消息的处理和回调都是在bridgeBase中完成的,bridge的作用是:进行webView代理的传递,对应平台初始化,调用bridgeBase中消息的注册,调用和回调处理,我们来看下bridgeBase的头文件中的属性,有消息队列数组,消息处理字典,响应回调字典和具体的消息处理对象

1
2
3
4
5
@property (assign) id <WebViewJavascriptBridgeBaseDelegate> delegate;
@property (strong, nonatomicNSMutableArray* startupMessageQueue;
@property (strong, nonatomicNSMutableDictionary* responseCallbacks;
@property (strong, nonatomicNSMutableDictionary* messageHandlers;
@property (strong, nonatomic) WVJBHandler messageHandler;

4. 当webView第一次加载的时候,我们看下面的代码,首先判断是否是当前scheme,之后判断URL是否是第一次加载的URL,与消息队列有关的URL定义与第一次加载的URL定义和处理方式是不同的,所以这里要进行区分,第一次加载的时候我们会注入(文件8)中的相关JS代码,后面具体介绍(文件8)的代码。消息队列相关的URL我们需要对消息进行响应处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
    if (webView != _webView) { return YES; }
    NSURL *url = [request URL];
    __strong WVJB_WEBVIEW_DELEGATE_TYPE* strongDelegate = _webViewDelegate;
    if ([_base isCorrectProcotocolScheme:url]) {
        if ([_base isBridgeLoadedURL:url]) {
            [_base injectJavascriptFile];
        else if ([_base isQueueMessageURL:url]) {
            NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
            [_base flushMessageQueue:messageQueueString];
        else {
            [_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;
    }
}

5. 第一次加载的时候我们会注入JS代码,执行"_evaluateJavascript:"方法,实际调用的是webView中的"stringByEvaluatingJavaScriptFromString:"方法来注入JS代码,注入之后如果此时队列消息有消息的话就会进行消息的调用

1
2
3
4
5
6
7
8
9
10
11
- (void)injectJavascriptFile {
    NSString *js = WebViewJavascriptBridge_js();
    [self _evaluateJavascript:js];
    if (self.startupMessageQueue) {
        NSArray* queue = self.startupMessageQueue;
        self.startupMessageQueue = nil;
        for (id queuedMessage in queue) {
            [self _dispatchMessage:queuedMessage];
        }
    }
}

6. 接下来我们看下JS端的部分代码,注入的这部分JS代码首先为html的window增加了一个bridge对象,这个对象里面有具体的有消息队列数组,消息处理字典,响应回调字典和具体的消息处理对象,和OC端的bridge是保持一致的,到这里完成了JS部分的初始化,整个初始化也就完成了

1
2
3
4
5
6
7
8
9
10
11
12
13
if (window.WebViewJavascriptBridge) {
    return;
}
window.WebViewJavascriptBridge = {
    registerHandler: registerHandler,
    callHandler: callHandler,
    _fetchQueue: _fetchQueue,
    _handleMessageFromObjC: _handleMessageFromObjC
};
 
var messagingIframe;
var sendMessageQueue = [];
var messageHandlers = {};

我们沿着OC调用JS方法和回调这条线路,继续查看源码:

7. 前文提到了OC调用JS方法,首先需要在JS端进行方法的注册,我们来看方法的注册代码,函数定义部分是不变的,只要使用这个库,那么这一部分代码就是必须有的,调用函数传入回调部分的代码就是我们自己需要自定义的地方,这里的registerHandler中的函数名就是OC要调用JS的方法名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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 = 'wvjbscheme://__BRIDGE_LOADED__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
 
setupWebViewJavascriptBridge(function(bridge) {
     //1 注册JS的方法给OC
     bridge.registerHandler('JS Echo'function(data, responseCallback) {<br>     console.log("JS Echo called with:", data) 
        responseCallback(data)
     })
})

8. OC调用的时候callHandler的时候最终来到了bridgeBase中的下面的方法,首先对这一次调用进行封装,并在内部缓存回调方法,然后进行消息的派遣

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (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];
}

9. 拿到对应的消息之后,首先要序列化成字符串对象,因为webView执行JavaScript脚本的时候接受的是一个字符串对象,之后进行一些列的转换处理,在下面的代码我们注意到有这样的字符串"WebViewJavascriptBridge._handleMessageFromObjC",这个就是我们之前注入的JS脚本里面对OC方法的处理的函数,到这里我们成功的把OC的消息传递给了JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
- (void)_dispatchMessage:(WVJBMessage*)message {
    NSString *messageJSON = [self _serializeMessage:message pretty:NO];
    [self _log:@"SEND" json:messageJSON];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\'" withString:@"\\\'"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\n" withString:@"\\n"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\r" withString:@"\\r"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\f" withString:@"\\f"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2028" withString:@"\\u2028"];
    messageJSON = [messageJSON stringByReplacingOccurrencesOfString:@"\u2029" withString:@"\\u2029"];
     
    NSString* javascriptCommand = [NSString stringWithFormat:@"WebViewJavascriptBridge._handleMessageFromObjC('%@');", messageJSON];
    if ([[NSThread currentThread] isMainThread]) {
        [self _evaluateJavascript:javascriptCommand];
 
    else {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self _evaluateJavascript:javascriptCommand];
        });
    }
}

10. JS拿到这个消息之后,进行消息的派遣,首先要反序列化字符串得到,方法名和参数,及方法的标识符,需要一提的是,这里先要检查JS端缓存的的回调方法,通过标识符,我们可以找到对应的回调函数,如果有则调用方法,没有则直接调用方法,下面的代码就是这样的过程,至此OC调用JS完成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function _dispatchMessageFromObjC(messageJSON) {
    setTimeout(function _timeoutDispatchMessageFromObjC() {
        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({ responseId:callbackResponseId, responseData:responseData });
                };
            }
             
            var handler = messageHandlers[message.handlerName];
            try {
                handler(message.data, responseCallback);
            catch(exception) {
                console.log("WebViewJavascriptBridge: WARNING: javascript handler threw.", message, exception);
            }
            if (!handler) {
                console.log("WebViewJavascriptBridge: WARNING: no handler for message from ObjC:", message);
            }
        }
    });
}

  

接下来我们沿着JS调用OC方法和回调这条线路,查看源码:

11. 同样我们需要在OC端初始化之后,进行方法的注册(前面提到过),注册之后我们调用JS的callHandler,callHandler的时候进行回调函数的缓存,然后往消息队列放入消息,之后重定向触发OC的代码调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function callHandler(handlerName, data, responseCallback) {
    if (arguments.length == 2 && typeof data == 'function') {
        responseCallback = data;
        data = null;
    }
    _doSend({ handlerName:handlerName, data:data }, responseCallback);
}
 
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;
} 

12. 前面也提到,重定向第一次是JS代码的注入。下面是关于消息队列的处理,首先检查消息队列字符串,然后反序列化字符串为json对象,这里反序列化之后的结果是一个包含数组的字典(键值对),之后就是根据消息的标识符,进行派发,如果对应的消息有回调函数的信息,那么会进行回调函数的调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
- (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);
        }
    }
}

13. 这里我们也注意到了JS事件在重定向的时候并没有在URL后面拼接相关字符串,那么上一步中的消息队列字符串是怎么来的,查看源码,执行这一步之前我们还调用了一个方法,我们又执行了一段JS脚本,这段JS脚本最终调用了fetchQueue,fetchQueue拼接了缓存在JS端的消息队列,返回了消息队列的字符串,这里和OC调JS是有区别的

1
2
3
4
5
6
NSString *messageQueueString = [self _evaluateJavascript:[_base webViewJavascriptFetchQueyCommand]];
[_base flushMessageQueue:messageQueueString];
//----------------------------------//
-(NSString *)webViewJavascriptFetchQueyCommand {
    return @"WebViewJavascriptBridge._fetchQueue();";
}
1
2
3
4
5
function _fetchQueue() {
    var messageQueueString = JSON.stringify(sendMessageQueue);
    sendMessageQueue = [];
    return messageQueueString;
}

14. 到这里这个库的基本原理已经解释的很清楚了,通过阅读源码,可以学到很多高效的编码方式,和编程思想,这里总结下整个过程:

a. OC端和JS端都有一个bridge对象,这个bridge对象处理管理者两个端的交互方法

b. 每个bridge里面都有方法的注册、调用和回调函数的缓存

c. 方法名和参数在传递的过程中会进行序列化和反序列化

d. 在调用方法的时候通过标识符来检测回调函数并调用完成了整个过程

二、使用WebViewJavascriptBridge重写

了解了整个开源库的原理写起代码来可谓是顺风顺水。这里用WebViewJavascriptBridge重写了之前的那个示例,实现过程很简单,下面贴出主要代码

OC端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
//1 注册OC的方法给JS
[self.bridge registerHandler:@"showMobile" handler:^(id data, WVJBResponseCallback responseCallback) {
    [self showMsg:@"我是下面的小红 手机号是:18870707070"];
}];
[self.bridge registerHandler:@"showName" handler:^(id data, WVJBResponseCallback responseCallback) {
     
    NSString *info = [NSString stringWithFormat:@"你好 %@, 很高兴见到你",data];
    [self showMsg:info];
}];
[self.bridge registerHandler:@"showSendMsg" handler:^(id data, WVJBResponseCallback responseCallback) {
     
    NSDictionary *dict = (NSDictionary *)data;
    NSString *info = [NSString stringWithFormat:@"这是我的手机号: %@, %@ !!",dict[@"mobile"],dict[@"events"]];
     
    [self showMsg:info];
}];
 
//2 调用JS注册给OC的方法
- (IBAction)btnClick:(UIButton *)sender {
    if (sender.tag == 123) {
        [self.bridge callHandler:@"alertMobile"];
    }
     
    if (sender.tag == 234) {
        [self.bridge callHandler:@"alertName" data:@"小红"];
    }
     
    if (sender.tag == 345) {
        [self.bridge callHandler:@"alertSendMsg" data:@{@"mobile":@"18870707070",@"events":@"周末爬山真是件愉快的事情"}];
    }
}

JS端:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 = 'wvjbscheme://__BRIDGE_LOADED__';
    document.documentElement.appendChild(WVJBIframe);
    setTimeout(function() { document.documentElement.removeChild(WVJBIframe) }, 0)
}
 
setupWebViewJavascriptBridge(function(bridge) {
     //1 注册JS的方法给OC
     bridge.registerHandler('alertMobile'function(data, responseCallback) {
         alert('我是上面的小黄 手机号是:13300001111')
     })
     bridge.registerHandler('alertName'function(data, responseCallback) {
        alert('你好 ' + data + ', 我也很高兴见到你')
            
     })
     bridge.registerHandler('alertSendMsg'function(data, responseCallback) {
         alert('这是我的手机号:' + data['mobile'] + ',' + data['events'] + '!!')
     })
})
 
//2 调用OC注册给的方法JS
function btnClick1() {
    window.WebViewJavascriptBridge.callHandler('showMobile')
}
function btnClick2() {
    window.WebViewJavascriptBridge.callHandler('showMobile''小黄')
     
}
function btnClick3() {
    window.WebViewJavascriptBridge.callHandler('showSendMsg', {'mobile''13300001111''events':'周末一起去爬山'})
}

这里实现的是简单的单向交互,因为之前设计的就比较简单,重写只完成了对应的功能,没有回调模块。回调的示例官方给的示例代码就有,具体可以参考官方示例代码

三、后记

iOS7之后,苹果针对JavaScript出了一个官方的库JavaScriptCore,是Objective-C封装了WebKit的JavaScript引擎,使我们可以脱离WebView执行JS代码。所以在iOS7之后想要实现交互,采用JavaScriptCore也是一种不错的选择,前提是你的项目不需要兼容到iOS7之前,我们将在下一篇文章介绍JavaScriptCore的组成及使用

感谢分享

WebViewJavascriptBridge的更多相关文章

  1. Android混合开发之WebViewJavascriptBridge实现JS与java安全交互

    前言: 为了加快开发效率,目前公司一些功能使用H5开发,这里难免会用到Js与Java函数互相调用的问题,这个Android是提供了原生支持的,不过存在安全隐患,今天我们来学习一种安全方式来满足Js与j ...

  2. objC与js通信实现--WebViewJavascriptBridge

    场景   在移动端开发中,最为流行的开发模式就是hybmid开发,在这种native和h5的杂糅下,既能在某些需求中保证足够的性能,也可以在某些列表详情的需求下采用h5的样式控制来丰富内容.但是在大型 ...

  3. WebViewJavascriptBridge源码探究--看OC和JS交互过程

    今天把实现OC代码和JS代码交互的第三方库WebViewJavascriptBridge源码看了下,oc调用js方法我们是知道的,系统提供了stringByEvaluatingJavaScriptFr ...

  4. WebViewJavascriptBridge使用说明(iOS)

    由于现在很多产品都是有安卓版跟ios版,就意味着同一样东西要出两套,由两组人去完成,不仅增加了开发成本,也大大加剧了维护成本.聪明的coder想出了跨平台的思路,用html写页面,分别用webview ...

  5. WebViewJavascriptBridge详细使用(转载)

    WebViewJavascriptBridge是支持到iOS6之前的版本的,用于支持native的iOS与javascript交互.如果需要支持到iOS6之前的app,使用它是很不错的.本篇讲讲Web ...

  6. WebViewJavascriptBridge的暂时理解

    直接从项目里复制了一份关于WebViewJavascriptBridge使用的代码,注释部分是自己暂时的理解.孟哥说,callHandler类似于jq里的trigger, registerHandle ...

  7. 通过WebViewJavascriptBridge实现OC与JS交互

      在.m方法当中,申明一个WebViewJavascriptBridge属性: @interface ExampleAppViewController () @property WebViewJav ...

  8. WebViewJavascriptBridge-Obj-C和JavaScript互通消息的桥梁

    转载至:http://www.cocoachina.com/ios/20150629/12248.html 译者:@coderyi9 本文翻译自Marcus Westin的开源框架WebViewJav ...

  9. iOS-JS交互 (WebViewJavascriptBridge)

    , , , );     messageButton.titleLabel.font = font;     messageButton.backgroundColor = [UIColor colo ...

  10. 李洪强iOS开发之 - WebViewJavascriptBridge

    李洪强iOS开发之 - WebViewJavascriptBridge 01 - JS端:   02 - iOS端 01 遵守代理协议 02 申明属性 03 开启日志 04 给哪个webview建立J ...

随机推荐

  1. 深入理解JavaScript系列+ 深入理解javascript之执行上下文

    http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html http://blog.csdn.net/hi_kevin/article/d ...

  2. android 换肤模式总结

    由于Android的设置中并没有夜间模式的选项,对于喜欢睡前玩手机的用户,只能简单的调节手机屏幕亮度来改善体验.目前越来越多的应用开始把夜间模式加到自家应用中,没准不久google也会把这项功能添加到 ...

  3. iOS NSDatePicker

    1.NSDate类 1>NSDate是系统一个日期,时间类 2>就是返回当前的日期,时间 3>+(id)date; 4>返回未来secs秒后的日期,时间 5>+(id)d ...

  4. JavaScript无限极菜单

    <!DOCTYPE html> <html> <head> <title> New Document </title> <meta c ...

  5. struts2拦截器的实现原理

    拦截器(interceptor)是Struts2最强大的特性之一,也可以说是struts2的核心,拦截器可以让你在Action和result被执行之前或之后进行一些处理.同时,拦截器也可以让你将通用的 ...

  6. day-4

    /* 早上黑板上的倒计时变成了120小时 嗯 很快就要结束了 上午考试 据老师说很简单 老师 :"我就说说~"..... 下午改题 T3好辣脑子 感觉智商不够了 T2dp写丑了 然 ...

  7. Java类加载及实例化的调用顺序

    标题起得略拗口,大概意思就是说在一个Java类中,域和构造方法的调用顺序. 1. 没有继承的情况 单独一个类的场景下,初始化顺序为依次为 静态数据,继承的基类的构造函数,成员变量,被调用的构造函数. ...

  8. angularjs + springmvc 上传和下载

    jsp: <form ng-submit="uploadFile()" class="form-horizontal" enctype="mul ...

  9. iOS-开发日志-UITextView介绍

    UITextView 属性 1.     text: 设置textView中文本 _textView.text = @"Now is the time for all good develo ...

  10. 《程序员的思维修炼》摘抄start:2014年9月27日19:27:07

    程序员的思维修炼:摘抄:考虑到社会中各个相关团体的复杂交互影响和社会的持续变化,在我看来当前最重要的两项技能就是: ▪沟通能力: ▪学习和思考能力.软件行业正在逐步提高沟通能力.特别是敏捷方法(见注解 ...