zoukankan      html  css  js  c++  java
  • UIWebView 本地缓存

    由于需要用到UIWebView本地缓存功能.在网上找了一些demo

    cocoaChina上一篇 是试用ASIHttpRequest.这个是比较好的.

    http://www.cocoachina.com/bbs/read.php?tid=69287

    还有一篇就是上一篇中提到的一个网站内容,试用NSURLCache

    http://re-reference.iteye.com/blog/1391408

    使用ASIHttpRequest本地缓存的代码:

    3.ASIHTTPRequestASIDownloadCache   ASIWebPageRequest

       首先我得说,这确实是个很好的框架,使用起来确实很方便,但是对于缓存这个问题,好像也跟第二点提到的效果差不多,加载速度没有明显的提升,离线模式下也无法加载。这是实现的代码:

    -(void)loadURL:(NSURL*)url

    {

        ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];

        //ASIWebPageRequest *request= [ASIWebPageRequest requestWithURL:url];

        [request setDelegate:self];

        //[request setUrlReplacementMode:ASIReplaceExternalResourcesWithData];

        [request setDidFailSelector:@selector(webPageFetchFailed:)];

        [request setDidFinishSelector:@selector(webPageFetchSucceeded:)];

         //设置缓存

        [request setDownloadCache:[ASIDownloadCache sharedCache]];

        //[request setCacheStoragePolicy:ASICachePermanentlyCacheStoragePolicy];

        [request setCachePolicy:ASIAskServerIfModifiedWhenStaleCachePolicy|ASIFallbackToCacheIfLoadFailsCachePolicy];

        [request setDownloadDestinationPath:[[ASIDownloadCache sharedCache]pathToStoreCachedResponseDataForRequest:request]];

         [request startAsynchronous];

    }

    - (void)webPageFetchFailed:(ASIHTTPRequest *)theRequest

    {

        // Obviously you should handle the error properly...

        NSLog(@"%@",[theRequest error]);

        NSString *path = [[NSBundle mainBundle] pathForResource:@"error1.html" ofType:nil inDirectory:@"WebResources/Error"];

        NSURL  *url=[NSURL fileURLWithPath:path];  

        [viewer loadRequest:[NSURLRequest requestWithURL:url]];

    }

    - (void)webPageFetchSucceeded:(ASIHTTPRequest *)theRequest

    {

        NSString *response = [NSString stringWithContentsOfFile:

                              [theRequest downloadDestinationPath] encoding:[theRequest responseEncoding] error:nil];

        // Note we're setting the baseURL to the url of the page we downloaded. This is important!

        [viewer loadHTMLString:response baseURL:[theRequest url]];

        //[viewer loadHTMLString:response baseURL:nil];

    }

    使用USURLCache缓存本地文件

    智能手机的流行让移动运营商们大赚了一笔,然而消费者们却不得不面对可怕的数据流量账单。因为在线看部电影可能要上千块通讯费,比起电影院什么的简直太坑爹了。 

    所以为了减少流量开销,离线浏览也就成了很关键的功能,而UIWebView这个让人又爱又恨的玩意弱爆了,居然只在Mac OS X上提供webView:resource:willSendRequest:redirectResponse:fromDataSource:这个方法,于是只好自己动手实现了。 

    原理就是SDK里绝大部分的网络请求都会访问[NSURLCache sharedURLCache]这个对象,它的cachedResponseForRequest:方法会返回一个NSCachedURLResponse对象。如果这个NSCachedURLResponse对象不为nil,且没有过期,那么就使用这个缓存的响应,否则就发起一个不访问缓存的请求。 

    要注意的是NSCachedURLResponse对象不能被提前释放,除非UIWebView去调用NSURLCacheremoveCachedResponseForRequest:方法,原因貌似是UIWebView并不retain这个响应。而这个问题又很头疼,因为UIWebView有内存泄露的嫌疑,即使它被释放了,也很可能不去调用上述方法,于是内存就一直占用着了。 

    顺便说下NSURLRequest对象,它有个cachePolicy属性,只要其值为NSURLRequestReloadIgnoringLocalCacheData的话,就不会访问缓存。可喜的是这种情况貌似只有在缓存里没取到,或是强制刷新时才可能出现。 

    实际上NSURLCache本身就有磁盘缓存功能,然而在iOS上,NSCachedURLResponse却被限制为不能缓存到磁盘(NSURLCacheStorageAllowed被视为NSURLCacheStorageAllowedInMemoryOnly)。 

    不过既然知道了原理,那么只要自己实现一个NSURLCache的子类,然后改写cachedResponseForRequest:方法,让它从硬盘读取缓存即可。 

    于是就开工吧。这次的demo逻辑比较复杂,因此我就按步骤来说明了。 

    先定义视图和控制器。 

    它的逻辑是打开应用时就尝试访问缓存文件,如果发现存在,则显示缓存完毕;否则就尝试下载整个网页的资源;在下载完成后,也显示缓存完毕。 

    不过下载所有资源需要解析HTML,甚至是JavaScriptCSS。为了简化我就直接用一个不显示的UIWebView载入这个页面,让它自动去发起所有请求。 

    当然,缓存完了还需要触发事件来显示网页。于是再提供一个按钮,点击时显示缓存的网页,再次点击就关闭。 

    顺带一提,我本来想用Google为例的,可惜它自己实现了HTML 5离线浏览,也就体现不出这种方法的意义了,于是只好拿百度来垫背。 

    Objective-c代码  

    1. #import <UIKit/UIKit.h>  
    2.   
    3. @interface WebViewController : UIViewController <UIWebViewDelegate> {  
    4.     UIWebView *web;  
    5.     UILabel *label;  
    6. }  
    7.   
    8. @property (nonatomic, retain) UIWebView *web;  
    9. @property (nonatomic, retain) UILabel *label;  
    10.   
    11. - (IBAction)click;  
    12.   
    13. @end  
    14.   
    15.   
    16. #import "WebViewController.h"  
    17. #import "URLCache.h"  
    18.   
    19. @implementation WebViewController  
    20.   
    21. @synthesize web, label;  
    22.   
    23. - (IBAction)click {  
    24.     if (web) {  
    25.         [web removeFromSuperview];  
    26.         self.web = nil;  
    27.     } else {  
    28.         CGRect frame = {{0, 0}, {320, 380}};  
    29.         UIWebView *webview = [[UIWebView alloc] initWithFrame:frame];  
    30.         webview.scalesPageToFit = YES;  
    31.         self.web = webview;  
    32.           
    33.         NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com/"]];  
    34.         [webview loadRequest:request];  
    35.         [self.view addSubview:webview];  
    36.         [webview release];  
    37.     }  
    38. }  
    39.   
    40. - (void)addButton {  
    41.     CGRect frame = {{130, 400}, {60, 30}};  
    42.     UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];  
    43.     button.frame = frame;  
    44.     [button addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];  
    45.     [button setTitle:@"我点" forState:UIControlStateNormal];    
    46.     [self.view addSubview:button];  
    47. }  
    48.   
    49. - (void)viewDidLoad {  
    50.     [super viewDidLoad];  
    51.   
    52.     URLCache *sharedCache = [[URLCache alloc] initWithMemoryCapacity:1024 * 1024 diskCapacity:0 diskPath:nil];  
    53.     [NSURLCache setSharedURLCache:sharedCache];  
    54.       
    55.     CGRect frame = {{60, 200}, {200, 30}};  
    56.     UILabel *textLabel = [[UILabel alloc] initWithFrame:frame];  
    57.     textLabel.textAlignment = UITextAlignmentCenter;  
    58.     [self.view addSubview:textLabel];  
    59.     self.label = textLabel;  
    60.       
    61.     if (![sharedCache.responsesInfo count]) { // not cached  
    62.         textLabel.text = @"缓存中…";  
    63.           
    64.         CGRect frame = {{0, 0}, {320, 380}};  
    65.         UIWebView *webview = [[UIWebView alloc] initWithFrame:frame];  
    66.         webview.delegate = self;  
    67.         self.web = webview;  
    68.           
    69.         NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com/"]];  
    70.         [webview loadRequest:request];  
    71.         [webview release];  
    72.     } else {  
    73.         textLabel.text = @"已从硬盘读取缓存";  
    74.         [self addButton];  
    75.     }  
    76.       
    77.     [sharedCache release];  
    78. }  
    79.   
    80. - (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {  
    81.     self.web = nil;  
    82.     label.text = @"请接通网络再运行本应用";  
    83. }  
    84.   
    85. - (void)webViewDidFinishLoad:(UIWebView *)webView {  
    86.     self.web = nil;  
    87.     label.text = @"缓存完毕";  
    88.     [self addButton];  
    89.       
    90.     URLCache *sharedCache = (URLCache *)[NSURLCache sharedURLCache];  
    91.     [sharedCache saveInfo];  
    92. }  
    93.   
    94. - (void)didReceiveMemoryWarning {  
    95.     [super didReceiveMemoryWarning];  
    96.       
    97.     if (!web) {  
    98.         URLCache *sharedCache = (URLCache *)[NSURLCache sharedURLCache];  
    99.         [sharedCache removeAllCachedResponses];  
    100.     }  
    101. }  
    102.   
    103. - (void)viewDidUnload {  
    104.     self.web = nil;  
    105.     self.label = nil;  
    106. }  
    107.   
    108.   
    109. - (void)dealloc {  
    110.     [super dealloc];  
    111.     [web release];  
    112.     [label release];  
    113. }  
    114.   
    115. @end  

    大部分的代码没什么要说的,随便挑2点。 

    实现了UIWebViewDelegate,因为需要知道缓存完毕或下载失败这个事件。 

    另外,正如前面所说的,UIWebView可能不会通知释放缓存。所以在收到内存警告时,如果UIWebView对象已被释放,那么就可以安全地清空缓存了(或许还要考虑多线程的影响)。 

    接下来就是重点了:实现URLCache类。 

    它需要2个属性:一个是用于保存NSCachedURLResponsecachedResponses,另一个是用于保存响应信息的responsesInfo(包括MIME类型和文件名)。 

    另外还需要实现一个saveInfo方法,用于将responsesInfo保存到磁盘。不过大多数应用应该使用数据库来保存,这里我只是为了简化而已。 

    Objective-c代码  

    1. #import <Foundation/Foundation.h>  
    2.   
    3. @interface URLCache : NSURLCache {  
    4.     NSMutableDictionary *cachedResponses;  
    5.     NSMutableDictionary *responsesInfo;  
    6. }  
    7.   
    8. @property (nonatomic, retain) NSMutableDictionary *cachedResponses;  
    9. @property (nonatomic, retain) NSMutableDictionary *responsesInfo;  
    10.   
    11. - (void)saveInfo;  
    12.   
    13. @end  
    14.   
    15.   
    16. #import "URLCache.h"  
    17. @implementation URLCache  
    18. @synthesize cachedResponses, responsesInfo;  
    19.   
    20. - (void)removeCachedResponseForRequest:(NSURLRequest *)request {  
    21.     NSLog(@"removeCachedResponseForRequest:%@", request.URL.absoluteString);  
    22.     [cachedResponses removeObjectForKey:request.URL.absoluteString];  
    23.     [super removeCachedResponseForRequest:request];  
    24. }  
    25.   
    26. - (void)removeAllCachedResponses {  
    27.     NSLog(@"removeAllObjects");  
    28.     [cachedResponses removeAllObjects];  
    29.     [super removeAllCachedResponses];  
    30. }  
    31.   
    32. - (void)dealloc {  
    33.     [cachedResponses release];  
    34.     [responsesInfo release];  
    35. }  
    36.   
    37. @end  

    写完这些没技术含量的代码后,就来实现saveInfo方法吧。 

    这里有一个要点需要说下,iTunes会备份所有的应用资料,除非放在Library/Cachestmp文件夹下。由于缓存并不是什么很重要的用户资料,没必要增加用户的备份时间和空间,所以我们应该把缓存放到这2个文件夹里。而后者会在退出应用或重启系统时清空,这显然不是我们想要的效果,于是最佳选择是前者。 

    Objective-c代码  

    1. static NSString *cacheDirectory;  
    2.   
    3. + (void)initialize {  
    4.     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);  
    5.     cacheDirectory = [[paths objectAtIndex:0] retain];  
    6. }  
    7.   
    8. - (void)saveInfo {  
    9.     if ([responsesInfo count]) {  
    10.         NSString *path = [cacheDirectory stringByAppendingString:@"responsesInfo.plist"];  
    11.         [responsesInfo writeToFile:path atomically: YES];  
    12.     }     
    13. }  

    这里我用了stringByAppendingString:方法,更保险的是使用stringByAppendingPathComponent:。不过我估计后者会做更多的检查工作,所以采用了前者。 

    在实现saveInfo后,初始化方法就也可以实现了。它主要就是载入保存的plist文件,如果不存在则新建一个空的NSMutableDictionary对象。 

    Objective-c代码  

    1. - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path {  
    2.     if (self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path]) {  
    3.         cachedResponses = [[NSMutableDictionary alloc] init];  
    4.         NSString *path = [cacheDirectory stringByAppendingString:@"responsesInfo.plist"];  
    5.         NSFileManager *fileManager = [[NSFileManager alloc] init];  
    6.         if ([fileManager fileExistsAtPath:path]) {  
    7.             responsesInfo = [[NSMutableDictionary alloc] initWithContentsOfFile:path];  
    8.         } else {  
    9.             responsesInfo = [[NSMutableDictionary alloc] init];  
    10.         }  
    11.         [fileManager release];  
    12.     }  
    13.     return self;  
    14. }  

    接下来就可以实现cachedResponseForRequest:方法了。 

    我们得先判断是不是GET方法,因为其他方法不应该被缓存。还得判断是不是网络请求,例如httphttpsftp,因为连data协议等本地请求都会跑到这个方法里来… 

    Objective-c代码  

    1. static NSSet *supportSchemes;  
    2.   
    3. + (void)initialize {  
    4.     NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);  
    5.     cacheDirectory = [[paths objectAtIndex:0] retain];  
    6.     supportSchemes = [[NSSet setWithObjects:@"http", @"https", @"ftp", nil] retain];  
    7. }  
    8.   
    9. - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {  
    10.     if ([request.HTTPMethod compare:@"GET"] != NSOrderedSame) {  
    11.         return [super cachedResponseForRequest:request];  
    12.     }  
    13.   
    14.     NSURL *url = request.URL;  
    15.     if (![supportSchemes containsObject:url.scheme]) {  
    16.         return [super cachedResponseForRequest:request];  
    17.     }  
    18.     //...  
    19. }  

    因为没必要处理它们,所以直接交给父类的处理方法了,它会自行决定是否返回nil的。 

    接着判断是不是已经在cachedResponses里了,这样的话直接拿出来即可: 

    Objective-c代码  

    1. NSString *absoluteString = url.absoluteString;  
    2. NSLog(@"%@", absoluteString);  
    3. NSCachedURLResponse *cachedResponse = [cachedResponses objectForKey:absoluteString];  
    4. if (cachedResponse) {  
    5.     NSLog(@"cached: %@", absoluteString);  
    6.     return cachedResponse;  
    7. }  

    再查查responsesInfo里有没有,如果有的话,说明可以从磁盘获取: 

    Objective-c代码  

    1. NSDictionary *responseInfo = [responsesInfo objectForKey:absoluteString];  
    2. if (responseInfo) {  
    3.     NSString *path = [cacheDirectory stringByAppendingString:[responseInfo objectForKey:@"filename"]];  
    4.     NSFileManager *fileManager = [[NSFileManager alloc] init];  
    5.     if ([fileManager fileExistsAtPath:path]) {  
    6.         [fileManager release];  
    7.           
    8.         NSData *data = [NSData dataWithContentsOfFile:path];  
    9.         NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:[responseInfo objectForKey:@"MIMEType"] expectedContentLength:data.length textEncodingName:nil];  
    10.         cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data];  
    11.         [response release];  
    12.           
    13.         [cachedResponses setObject:cachedResponse forKey:absoluteString];  
    14.         [cachedResponse release];  
    15.         NSLog(@"cached: %@", absoluteString);  
    16.         return cachedResponse;  
    17.     }  
    18.     [fileManager release];  
    19. }  

    这里的难点在于构造NSURLResponseNSCachedURLResponse,不过对照下文档看看也就清楚了。如前文所说,我们还得把cachedResponse保存到cachedResponses里,避免它被提前释放。 

    接下来就说明缓存不存在了,需要我们自己发起一个请求。可恨的是NSURLResponse不能更改属性,所以还需要手动新建一个NSMutableURLRequest对象: 

    Objective-c代码  

    1. NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:request.timeoutInterval];  
    2. newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields;  
    3. newRequest.HTTPShouldHandleCookies = request.HTTPShouldHandleCookies;  

    实际上NSMutableURLRequest还有一些其他的属性,不过并不太重要,所以我就只复制了这2个。 

    然后就可以用它来发起请求了。由于UIWebView就是在子线程调用cachedResponseForRequest:的,不用担心阻塞的问题,所以无需使用异步请求: 

    Objective-c代码  

    1. NSError *error = nil;  
    2. NSURLResponse *response = nil;  
    3. NSData *data = [NSURLConnection sendSynchronousRequest:newRequest returningResponse:&response error:&error];  
    4. if (error) {  
    5.     NSLog(@"%@", error);  
    6.     NSLog(@"not cached: %@", absoluteString);  
    7.     return nil;  
    8. }  

    如果下载没出错的话,我们就能拿到dataresponse了,于是就能将其保存到磁盘了。保存的文件名必须是合法且独一无二的,所以我就用到了sha1算法。 

    Objective-c代码  

    1. uint8_t digest[CC_SHA1_DIGEST_LENGTH];  
    2.     CC_SHA1(data.bytes, data.length, digest);  
    3.     NSMutableString* output = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];  
    4.     for(int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++)  
    5.         [output appendFormat:@"x", digest[i]];  
    6.       
    7. NSString *filename = output;//sha1([absoluteString UTF8String]);   
    8. NSString *path = [cacheDirectory stringByAppendingString:filename];  
    9. NSFileManager *fileManager = [[NSFileManager alloc] init];  
    10. [fileManager createFileAtPath:path contents:data attributes:nil];  
    11. [fileManager release];  

    接下来还得将文件信息保存到responsesInfo,并构造一个NSCachedURLResponse 

    然而这里还有个陷阱,因为直接使用response对象会无效。我稍微研究了一下,发现它其实是个NSHTTPURLResponse对象,可能是它的allHeaderFields属性影响了缓存策略,导致不能重用。 

    不过这难不倒我们,直接像前面那样构造一个NSURLResponse对象就行了,这样就没有allHeaderFields属性了:

    Objective-c代码  

    1. NSURLResponse *newResponse = [[NSURLResponse alloc] initWithURL:response.URL MIMEType:response.MIMEType expectedContentLength:data.length textEncodingName:nil];  
    2. responseInfo = [NSDictionary dictionaryWithObjectsAndKeys:filename, @"filename", newResponse.MIMEType, @"MIMEType", nil];  
    3. [responsesInfo setObject:responseInfo forKey:absoluteString];  
    4. NSLog(@"saved: %@", absoluteString);  
    5.   
    6. cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:newResponse data:data];  
    7. [newResponse release];  
    8. [cachedResponses setObject:cachedResponse forKey:absoluteString];  
    9. [cachedResponse release];  
    10. return cachedResponse;  

    OK,现在终于大功告成了,打开WIFI然后启动这个程序,过一会就会提示缓存完毕了。然后关掉WIFI,尝试打开网页,你会发现网页能正常载入了。 

    而查看log,也能发现这确实是从我们的缓存中取出来的。 

    还不放心的话可以退出程序,这样内存缓存肯定就释放了。然后再次进入并打开网页,你会发现一切仍然正常~

  • 相关阅读:
    Codeforces466C Number of Ways
    hdu 4902 Nice boat--2014 Multi-University Training Contest 4
    怎样免费设置QQ空间背景音乐
    “小懒虫”安卓手机控制电脑关机
    js和jquery实现回到顶层
    机器学习概念
    HDU 4786(最小生成树 kruskal)
    算法----堆排序(heap sort)
    openfireserver和jdk环境删除命令
    POI操作Excel导入和导出
  • 原文地址:https://www.cnblogs.com/DamonTang/p/3039694.html
Copyright © 2011-2022 走看看