写在前面给大家推荐一个不错的站点 点击打开链接

本文測试样例源代码下载地址

近期看AFNetworking2的源代码。学习这个知名网络框架的实现。顺便梳理写下文章。

AFNetworking的代码还在不断更新中,我看的是AFNetworking2.3.1

 
本篇先看看AFURLConnectionOperation,AFURLConnectionOperation继承自NSOperation,是一个封装好的任务单元,在这里构建了NSURLConnection,作为NSURLConnection的delegate处理请求回调,做好状态切换,线程管理,能够说是AFNetworking最核心的类,以下分几部分说下看源代码时注意的点,最后放上代码的凝视。

 
0.Tricks
AFNetworking代码中有一些经常使用技巧,先说明一下。
 
A.clang warning
  1. #pragma clang diagnostic push
  2. #pragma clang diagnostic ignored "-Wgnu"
  3. //code
  4. #pragma clang diagnostic pop
表示在这个区间里忽略一些特定的clang的编译警告,由于AFNetworking作为一个库被其它项目引用,所以不能全局忽略clang的一些警告,仅仅能在有须要的时候局部这样做,作者喜欢用?

:符号,所以常常见忽略-Wgnu警告的写法。详见这里

 
B.dispatch_once
为保证线程安全。全部单例都用dispatch_once生成。保证仅仅运行一次。这也是iOS开发经常使用的技巧。比如:
  1. static dispatch_queue_t url_request_operation_completion_queue() {
  2. static dispatch_queue_t af_url_request_operation_completion_queue;
  3. static dispatch_once_t onceToken;
  4. dispatch_once(&onceToken, ^{
  5. af_url_request_operation_completion_queue = dispatch_queue_create("com.alamofire.networking.operation.queue",   DISPATCH_QUEUE_CONCURRENT );
  6. });
  7. return af_url_request_operation_completion_queue;
  8. }
C.weak & strong self
常看到一个 block 要使用 self。会处理成在外部声明一个 weak 变量指向 self。在 block 里又声明一个 strong 变量指向 weakSelf:
  1. __weak __typeof(self)weakSelf = self;
  2. self.backgroundTaskIdentifier = [application beginBackgroundTaskWithExpirationHandler:^{
  3. __strong __typeof(weakSelf)strongSelf = weakSelf;
  4. }];
weakSelf是为了block不持有self。避免循环引用,而再声明一个strongSelf是由于一旦进入block运行,就不同意self在这个运行过程中释放。block运行完后这个strongSelf会自己主动释放,没有循环引用问题。


1.线程
先来看看 NSURLConnection 发送请求时的线程情况。NSURLConnection 是被设计成异步发送的,调用了start方法后。NSURLConnection 会新建一些线程用底层的 CFSocket 去发送和接收请求。在发送和接收的一些事件发生后通知原来线程的Runloop去回调事件。
 
NSURLConnection 的同步方法 sendSynchronousRequest 方法也是基于异步的,相同要在其它线程去处理请求的发送和接收,仅仅是同步方法会手动block住线程,发送状态的通知也不是通过 RunLoop 进行。
 
使用NSURLConnection有几种选择:
 
A.在主线程调异步接口
若直接在主线程调用异步接口,会有个Runloop相关的问题:
 
当在主线程调用 [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES] 时。请求发出,侦听任务会增加到主线程的 Runloop 下。RunloopMode 会默觉得 NSDefaultRunLoopMode。

这表明仅仅有当前线程的Runloop 处于 NSDefaultRunLoopMode 时,这个任务才会被运行。

但当用户滚动 tableview 或 scrollview 时。主线程的 Runloop
是处于 NSEventTrackingRunLoopMode 模式下的,不会运行 NSDefaultRunLoopMode 的任务。所以会出现一个问题,请求发出后。假设用户一直在操作UI上下滑动屏幕,那在滑动结束前是不会运行回调函数的,仅仅有在滑动结束。RunloopMode 切回 NSDefaultRunLoopMode,才会运行回调函数。苹果一直把动画效果性能放在第一位,预计这也是苹果提升UI动画性能的手段之中的一个。

 
所以若要在主线程使用 NSURLConnection 异步接口,须要手动把 RunloopMode 设为 NSRunLoopCommonModes。这个 mode 意思是不管当前 Runloop 处于什么状态,都运行这个任务。

  1. NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
  2. [connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
  3. [connection start];
若在子线程调用同步接口,一条线程仅仅能处理一个请求,由于请求一发出去线程就堵塞住等待回调,须要给每一个请求新建一个线程,这是非常浪费的,这样的方式唯一的优点应该是易于控制请求并发的数量。

 
C.在子线程调异步接口
子线程调用异步接口,子线程须要有 Runloop 去接收异步回调事件。这里也能够每一个请求都新建一条带有 Runloop 的线程去侦听回调。但这一点优点都没有,既然是异步回调,除了处理回调内容。其它时间线程都是空暇可利用的,全部请求共用一个响应的线程就够了。

 
AFNetworking 用的就是第三种方式。创建了一条常驻线程专门处理全部请求的回调事件,这个模型跟 nodejs 有点类似。网络请求回调处理完。组装好数据后再给上层调用者回调,这时候回调是抛回主线程的,由于主线程是最安全的,使用者可能会在回调中更新UI。在子线程更新UI会导致各种问题,一般使用者也能够不须要关心线程问题。
 
下面是相关线程大致的关系。实际上多个 NSURLConnection 会共用一个 NSURLConnectionLoader 线程,这里就不细化了。除了处理 socket 的 CFSocket 线程,另一些 Javascript:Core 的线程。眼下不清楚作用,归为 NSURLConnection里的其它线程。由于 NSURLConnection 是系统控件,每一个iOS版本号可能都有不一样,能够先把 NSURLConnection 当成一个黑盒,仅仅管它的 start 和 callback 即可了。假设使用
AFHttpRequestOperationManager 的接口发送请求,这些请求会统一在一个 NSOperationQueue 里去发,所以多了上面 NSOperationQueue 的一个线程。
 

 
相关代码:-networkRequestThread:, -start:, -operationDidStart:。
2.状态机
继承 NSOperation 有个非常麻烦的东西要处理,就是改变状态时须要发 KVO 通知。否则这个类增加 NSOperationQueue 不可用了。 NSOperationQueue 是用 KVO 方式侦听 NSOperation 状态的改变。以推断这个任务当前是否已完毕。完毕的任务须要在队列中除去并释放。
 
AFURLConnectionOperation 对此做了个状态机,统一搞定状态切换以及发 KVO 通知的问题,内部要改变状态时,就仅仅须要类似 self.state = AFOperationReadyState 的调用而不须要做其它了,状态改变的 KVO 通知在 setState 里发出。
总的来说状态管理相关代码就三部分,一是限制一个状态能够切换到其它哪些状态。避免状态切换混乱,二是状态 Enum值 与 NSOperation 四个状态方法的相应。三是在 setState 时统一发 KVO 通知。详见代码凝视。
 
相关代码:AFKeyPathFromOperationState, AFStateTransitionIsValid, -setState:, -isPaused:, -isReady:, -isExecuting:, -isFinished:.


3.NSURLConnectionDelegate
处理 NSURLConnection Delegate 的内容不多,代码也是按请求回调的顺序排列下去,十分易读。主要流程就是接收到响应的时候打开 outputStream,接着有数据过来就往 outputStream 写,在上传/接收数据过程中会回调上层传进来的对应的callback。在请求完毕回调到 connectionDidFinishLoading 时。关闭 outputStream。用 outputStream 组装 responseData 作为接收到的数据。把 NSOperation 状态设为 finished。表示任务完毕,NSOperation
会自己主动调用 completeBlock,再回调到上层。
 
4.setCompleteBlock
NSOperation 在 iOS4.0 以后提供了个接口 setCompletionBlock。能够传入一个 block 作为任务运行完毕时(state状态机变为finished时)的回调。AFNetworking直接用了这个接口,并通过重写加了几个功能:
 
A.消除循环引用
在 NSOperation 的实现里,completionBlock 是 NSOperation 对象的一个成员,NSOperation 对象持有着 completionBlock,若传进来的 block 用到了 NSOperation 对象,或者 block 用到的对象持有了这个 NSOperation 对象,就会造成循环引用。这里运行完 block 后调用 [strongSelf setCompletionBlock:nil] 把 completionBlock 设成 nil。手动释放 self(NSOperation对象)
持有的 completionBlock 对象,打破循环引用。
 
能够理解成对外保证传进来的block一定会被释放。解决外部使用使非常easy出现的因对象关系复杂导致循环引用的问题,让使用者不知道循环引用这个概念都能正确使用。http://www.joblai.com/news/news-show-18.htm


B.dispatch_group
这里同意用户让全部 operation 的 completionBlock 在一个 group 里运行,但我没看出这样做的作用,若想组装一组请求(见以下的batchOfRequestOperations)也不须要再让completionBlock在group里运行。求解。


C.”The Deallocation Problem”
作者在凝视里说这里重写的setCompletionBlock方法攻克了”The Deallocation Problem”,实际上并没有。”The
Deallocation Problem
”简单来说就是不要让UIKit的东西在子线程释放。

 
这里假设传进来的block持有了外部的UIViewController或其它UIKit对象(以下临时称为A对象),而且在请求完毕之前其它全部对这个A对象的引用都已经释放了,那么这个completionBlock就是最后一个持有这个A对象的,这个block释放时A对象也会释放。这个block在什么线程释放。A对象就会在什么线程释放。我们看到block释放的地方是url_request_operation_completion_queue(),这是AFNetworking特意生成的子线程,所以按理说A对象是会在子线程释放的,会导致UIKit对象在子线程释放。会有问题。
 
但AFNetworking实际用起来却没问题,想了非常久不得其解,后来做了实验。发现iOS5以后苹果对UIKit对象的释放做了特殊处理。仅仅要发如今子线程释放这些对象,就自己主动转到主线程去释放,断点出来是由一个叫_objc_deallocOnMainThreadHelper 的方法做的。假设不是UIKit对象就不会跳到主线程释放。AFNetworking2.0仅仅支持iOS6+,所以没问题。

 

 
这里额外提供了一个便捷接口,能够传入一组请求。在全部请求完毕后回调 complionBlock。在每个请求完毕时回调 progressBlock 通知外面有多少个请求已完毕。详情參见代码凝视,这里须要说明下 dispatch_group_enter 和dispatch_group_leave 的使用。这两个方法用于把一个异步任务增加 group 里。
 
一般我们要把一个任务增加一个group里是这样:
  1. dispatch_group_async(group, queue, ^{
  2. block();
  3. });
这个写法等价于
  1. dispatch_async(queue, ^{
  2. dispatch_group_enter(group);
  3. block()
  4. dispatch_group_leave(group);
  5. });
假设要把一个异步任务增加group,这样即可不通了:
  1. dispatch_group_async(group, queue, ^{
  2. [self performBlock:^(){
  3. block();
  4. }];
  5. //未运行到block() group任务就已经完毕了
  6. });
这时须要这样写:
  1. dispatch_group_enter(group);
  2. [self performBlock:^(){
  3. block();
  4. dispatch_group_leave(group);
  5. }];
异步任务回调后才算这个group任务完毕。对batchOfRequest的实现来说就是请求完毕并回调后。才算这个任务完毕。
 
事实上这跟retain/release差点儿相同,都是计数,dispatch_group_enter时任务数+1,dispatch_group_leave时任务数-1,任务数为0时运行dispatch_group_notify的内容。

 
相关代码:-batchOfRequestOperations:progressBlock:completionBlock:
 
6.其它
A.锁
AFURLConnectionOperation 有一把递归锁,在全部会訪问/改动成员变量的对外接口都加了锁,由于这些对外的接口用户是能够在随意线程调用的。对于訪问和改动成员变量的接口。必须用锁保证线程安全。

 
AFNetworking 的多数类都支持序列化。但实现的是 NSSecureCoding 的接口,而不是 NSCoding,差别在于解数据时要指定 Class。用 -decodeObjectOfClass:forKey: 方法取代了 -decodeObjectForKey: 。

这样做更安全,由于序列化后的数据有可能被篡改。若不指定 Class,-decode 出来的对象可能不是原来的对象,有潜在风险。另外,NSSecureCoding 是 iOS 6 以上才有的。

详见这里

 
这里在序列化时保存了当前任务状态,接收的数据等,但回调block是保存不了的。须要在取出来发送时又一次设置。能够像以下这样持久化保存和取出任务:
  1. AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
  2. NSData *data = [NSKeyedArchiver archivedDataWithRootObject:operation];
  3. AFHTTPRequestOperation *operationFromDB = [NSKeyedUnarchiver unarchiveObjectWithData:data];
  4. [operationFromDB start];
这里提供了setShouldExecuteAsBackgroundTaskWithExpirationHandler 接口,决定APP进入后台后是否继续发送接收请求。并在后台执行时间超时后取消全部请求。

在 dealloc 里须要调用 [application endBackgroundTask:] ,告诉系统这个后台任务已经完毕,不然系统会一直让你的APP执行在后台。直到超时。

 
相关代码:-setShouldExecuteAsBackgroundTaskWithExpirationHandler:, -dealloc:


7.AFHTTPRequestOperation
AFHTTPRequestOperation 继承了 AFURLConnectionOperation,把它放一起说是由于它没做多少事情。主要多了responseSerializer,暂停下载断点续传。以及提供接口请求成功失败的回调接口 -setCompletionBlockWithSuccess:failure:。详见源代码凝视。


8.源代码凝视
  1. AFURLConnectionOperation.m
 
  1. AFHTTPRequestOperation.m
本篇我们继续来看看AFNetworking的下一个模块 — AFURLRequestSerialization

 
AFURLRequestSerialization用于帮助构建NSURLRequest,主要做了两个事情:
 
1.构建普通请求:格式化请求參数。生成HTTP Header。
2.构建multipart请求。
 
分别看看它在这两点详细做了什么。怎么做的。
 
 
A.格式化请求參数
一般我们请求都会按key=value的方式带上各种參数,GET方法參数直接加在URL上,POST方法放在body上。NSURLRequest没有封装好这个參数的解析,仅仅能我们自己拼好字符串。

AFNetworking提供了接口,让參数能够是NSDictionary, NSArray, NSSet这些类型。再由内部解析成字符串后赋给NSURLRequest。

 
转化过程大致是这种:
  1. @{
  2. @"name" : @"bang",
  3. @"phone": @{@"mobile": @"xx", @"home": @"xx"},
  4. @"families": @[@"father", @"mother"],
  5. @"nums": [NSSet setWithObjects:@"1", @"2", nil]
  6. }
  7. ->
  8. @[
  9. field: @"name", value: @"bang",
  10. field: @"phone[mobile]", value: @"xx",
  11. field: @"phone[home]", value: @"xx",
  12. field: @"families[]", value: @"father",
  13. field: @"families[]", value: @"mother",
  14. field: @"nums", value: @"1",
  15. field: @"nums", value: @"2",
  16. ]
  17. ->
  18. name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2
第一部分是用户传进来的数据。支持包括NSArray,NSDictionary,NSSet这三种数据结构。

 
第二部分是转换成AFNetworking内自己的数据结构。每个key-value对都用一个对象AFQueryStringPair表示。作用是最后能够依据不同的字符串编码生成各自的key=value字符串。主要函数是AFQueryStringPairsFromKeyAndValue,详见源代码凝视。
 
第三部分是最后生成NSURLRequest可用的字符串数据,而且对參数进行url编码,在AFQueryStringFromParametersWithEncoding这个函数里。
 
最后在把数据赋给NSURLRequest时依据不同的HTTP方法分别处理,对于GET/HEAD/DELETE方法,把參数加到URL后面。对于其它如POST/PUT方法,把数据加到body上,并设好HTTP头,告诉服务端字符串的编码。

 
AFNetworking帮你组装好了一些HTTP请求头。包含语言Accept-Language,依据 [NSLocale preferredLanguages] 方法读取本地语言。快速服务端自己能接受的语言。还有构建 User-Agent,以及提供Basic
Auth 认证接口,帮你把usernamepassword做 base64 编码后放入 HTTP 请求头。

详见源代码凝视。

 
C.其它格式化方式
HTTP请求參数不一定是要key=value形式,能够是不论什么形式的数据。能够是json格式,苹果的plist格式,二进制protobuf格式等,AFNetworking提供了方法能够非常easy扩展支持这些格式。默认就实现了json和plist格式。详见源代码的类AFJSONRequestSerializer和AFPropertyListRequestSerializer。

 
2.构建multipart请求
构建Multipart请求是占篇幅非常大的一个功能。AFURLRequestSerialization里2/3的代码都是在做这个事。
 
A.Multipart协议介绍
Multipart是HTTP协议为web表单新增的上传文件的协议,协议文档是rfc1867,它基于HTTP的POST方法,数据相同是放在body上,跟普通POST方法的差别是数据不是key=value形式,key=value形式难以表示文件实体,为此Multipart协议加入了分隔符,有自己的格式结构。大致例如以下:
—AaB03x
content-disposition: form-data; name=“name"
bang
--AaB03x
content-disposition: form-data; name="pic"; filename=“content.txt"
Content-Type: text/plain
... contents of bang.txt ...
--AaB03x--
 
以上表示数据name=bang以及一个文件,content.txt是文件名称,… contents of bang.txt …是文件实体内容。分隔符—AaB03x是能够自己定义的,写在HTTP头部里:
 
Content-type: multipart/form-data, boundary=AaB03x
 
每个部分都有自己的头部。表明这部分的数据类型以及其它一些參数,比如文件名称,普通字段的key。最后一个分隔符会多加两横,表示数据已经结束:—AaB03x—。
 
B.实现
接下来说说如何构造Multipart里的数据,最简单的方式就是直接拼数据,要发送一个文件。就直接把文件全部内容读取出来。再按上述协议加上头部和分隔符。拼接好数据后扔给NSURLRequest的body就能够发送了,非常easy。但这样做是不可用的,由于文件可能非常大,这样拼数据把整个文件读进内存,非常可能把内存撑爆了。

 
另外一种方法是不把文件读出来。不在内存拼,而是新建一个暂时文件。在这个文件上拼接数据。再把文件地址扔给NSURLRequest的bodyStream,这样上传的时候是分片读取这个文件,不会撑爆内存,但这样每次上传都须要新建个暂时文件,对这个暂时文件的管理也挺麻烦的。

 
第三种方法是构建自己的数据结构,仅仅保存要上传的文件地址,边上传边拼数据,上传是分片的,拼数据也是分片的。拼到文件实体部分时直接从原来的文件分片读取。这方法没上述两种的问题,仅仅是实现起来也没上述两种简单,AFNetworking就是实现这第三种方法。并且还更进一步,除了文件,还能够加入多个其它不同类型的数据。包含NSData,和InputStream。

 
AFNetworking 里 multipart 请求的使用方式是这样:
  1. AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
  2. NSDictionary *parameters = @{@"foo": @"bar"};
  3. NSURL *filePath = [NSURL fileURLWithPath:@"file://path/to/image.png"];
  4. [manager POST:@"http://example.com/resources.json" parameters:parameters constructingBodyWithBlock:^(id formData) {
  5. [formData appendPartWithFileURL:filePath name:@"image" error:nil];
  6. } success:^(AFHTTPRequestOperation *operation, id responseObject) {
  7. NSLog(@"Success: %@", responseObject);
  8. } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
  9. NSLog(@"Error: %@", error);
  10. }];
这里通过constructingBodyWithBlock向使用者提供了一个AFStreamingMultipartFormData对象,调这个对象的几种append方法就能够加入不同类型的数据,包含FileURL/NSData/NSInputStream,AFStreamingMultipartFormData内部把这些append的数据转成不同类型的 AFHTTPBodyPart,加入到自己定义的 AFMultipartBodyStream
里。

最后把 AFMultipartBodyStream 赋给原来 NSMutableURLRequest的bodyStream。NSURLConnection 发送请求时会读取这个 bodyStream,在读取数据时会调用这个 bodyStream 的 -read:maxLength: 方法。AFMultipartBodyStream 重写了这种方法,不断读取之前 append进来的 AFHTTPBodyPart 数据直到读完。

 
AFHTTPBodyPart 封装了各部分数据的组装和读取,一个 AFHTTPBodyPart 就是一个数据块。实际上三种类型 (FileURL/NSData/NSInputStream) 的数据在 AFHTTPBodyPart 都转成 NSInputStream,读取数据时仅仅需读这个 inputStream。inputStream 仅仅保存了数据的实体,没有包含分隔符和头部,AFHTTPBodyPart 是边读取变拼接数据,用一个状态机确定如今数据读取到哪一部份。以及保存这个状态下已被读取的字节数,以此定位要读的数据位置,详见
AFHTTPBodyPart 的-read:maxLength:方法。

 
AFMultipartBodyStream封装了整个multipart数据的读取,主要是依据读取的位置确定如今要读哪一个AFHTTPBodyPart。AFStreamingMultipartFormData对外提供友好的append接口。并把构造好的AFMultipartBodyStream赋回给NSMutableURLRequest。关系大致例如以下图:

C.NSInputStream子类
NSURLRequest 的 setHTTPBodyStream 接受的是一个 NSInputStream* 參数,那我们要自己定义inputStream的话,创建一个 NSInputStream 的子类传给它是不是就能够了?实际上不行。这样做后用NSURLRequest
发出请求会导致 crash,提示 [xx _scheduleInCFRunLoop:forMode:]: unrecognized selector。
 
这是由于NSURLRequest实际上接受的不是 NSInputStream 对象,而是 CoreFoundation 的 CFReadStreamRef 对象,由于 CFReadStreamRef 和 NSInputStream 是 toll-free bridged,能够自由转换,但CFReadStreamRef 会用到 CFStreamScheduleWithRunLoop 这种方法,当它调用到这种方法时,object-c 的 toll-free
bridging 机制会调用 object-c 对象 NSInputStream 的对应函数。这里就调用到了_scheduleInCFRunLoop:forMode:,若不实现这种方法就会crash。

详见这篇文章。

 
3.源代码凝视
  1. AFURLRequestSerialization.m

具体内容分析点击打开链接

AFNetworking2.0源代码解析的更多相关文章

  1. AFNetworking2.0源码解析<一>

    本篇先看看AFURLConnectionOperation,AFURLConnectionOperation继承自NSOperation,是一个封装好的任务单元,在这里构建了NSURLConnecti ...

  2. Arrays.sort源代码解析

    Java Arrays.sort源代码解析 Java Arrays中提供了对所有类型的排序.其中主要分为Primitive(8种基本类型)和Object两大类. 基本类型:采用调优的快速排序: 对象类 ...

  3. qemu-kvm-1.1.0源代码中关于迁移的代码分析

    这篇文档基于qemu-kvm-1.1.0源代码进行分析. 首先,源代码中的hmp-commands.hx文件里有下面内容: { .name = "migrate",/* 在moni ...

  4. volley源代码解析(七)--终于目的之Response&lt;T&gt;

    在上篇文章中,我们终于通过网络,获取到了HttpResponse对象 HttpResponse是android包里面的一个类.然后为了更高的扩展性,我们在BasicNetwork类里面看到.Volle ...

  5. Cocos2d-x源代码解析(1)——地图模块(3)

    接上一章<Cocos2d-x源代码解析(1)--地图模块(2)> 通过前面两章的分析,我们能够知道cocos将tmx的信息结构化到 CCTMXMapInfo.CCTMXTilesetInf ...

  6. Android EventBus源代码解析 带你深入理解EventBus

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/40920453,本文出自:[张鸿洋的博客] 上一篇带大家初步了解了EventBus ...

  7. 源代码解析Android中View的layout布局过程

    Android中的Veiw从内存中到呈如今UI界面上须要依次经历三个阶段:量算 -> 布局 -> 画图,关于View的量算.布局.画图的整体机制可參见博文 < Android中Vie ...

  8. Android xUtils3源代码解析之网络模块

    本文已授权微信公众号<非著名程序猿>原创首发,转载请务必注明出处. xUtils3源代码解析系列 一. Android xUtils3源代码解析之网络模块 二. Android xUtil ...

  9. Android View体系(八)从源代码解析View的layout和draw流程

    相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源 ...

随机推荐

  1. hdu 4111 Alice and Bob 记忆化搜索 博弈论

    Alice and Bob Time Limit: 20 Sec  Memory Limit: 256 MB 题目连接 http://acm.hdu.edu.cn/showproblem.php?pi ...

  2. GIT 提交步骤

    1.提交 git add .

  3. Spring+JDBC实例

    1. Customer 表 在这个例子中,我们使用的是MySQL数据库. CREATE TABLE `customer` ( `CUST_ID` int(10) unsigned NOT NULL A ...

  4. .net程序保护方式大观

    .net软件保护方式大观 最近调试一个运行于.net 2.0下的软件,发现该软件使用的保护方式很具有代表性,基本囊括了现在.net下的所有保护措施.实践证明,这些保护措施就像全真七子,单打独斗功力差了 ...

  5. 【资料】wod书籍

    世界掉落 特点 风化的书卷 可用三次的无限耗材 华丽的书卷 可用5次 无限耗材 队伍唯一 抄录页:新手躲避 近远防御 +34%X技能等级 风化的书卷:新手躲避 华丽的书卷:新手躲避 抄录页:高级闪避技 ...

  6. Javascript:前端利器 之 JSDuck

    背景 文档的重要性不言而喻,对于像Javascript这种的动态语言来说就更重要了,目前流行的JDoc工具挺多的,最好的当属JSDuck,可是JSDuck在Windows下的安装非常麻烦,这里就写下来 ...

  7. pytest文档8-html报告报错截图+失败重跑

    前言 做web自动化的小伙伴应该都希望在html报告中展示失败后的截图,提升报告的档次,pytest-html也可以生成带截图的报告. conftest.py 1.失败截图可以写到conftest.p ...

  8. JPA入门样例(採用JPA的hibernate实现版本号)

    (1).JPA介绍: JPA全称为Java Persistence API ,Java持久化API是Sun公司在Java EE 5规范中提出的Java持久化接口.JPA吸取了眼下Java持久化技术的长 ...

  9. There is no Action mapped for namespace [/] and action name [Login] associated with context path [/e

    近期学习web开发时,就遇到这个令人头疼的问题. 百度谷歌了N遍,最终在博客http://blog.csdn.net/liu578182160/article/details/17266879中找到了 ...

  10. fatal error: sys/cdefs.h: No such file or directory

    sudo apt-get install g++-multilib