日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

UIWebView实现离线浏览

發(fā)布時間:2025/6/15 编程问答 49 豆豆
生活随笔 收集整理的這篇文章主要介紹了 UIWebView实现离线浏览 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

轉(zhuǎn)自

http://www.keakon.net/2011/08/14/%E4%B8%BAUIWebView%E5%AE%9E%E7%8E%B0%E7%A6%BB%E7%BA%BF%E6%B5%8F%E8%A7%88

智能手機的流行讓移動運營商們大賺了一筆,然而消費者們卻不得不面對可怕的數(shù)據(jù)流量賬單。因為在線看部電影可能要上千塊通訊費,比起電影院什么的簡直太坑爹了。

所 以為了減少流量開銷,離線瀏覽也就成了很關(guān)鍵的功能,而UIWebView這個讓人又愛又恨的玩意弱爆了,居然只在Mac OS X上提供webView:resource:willSendRequest:redirectResponse:fromDataSource:這個方 法,于是只好自己動手實現(xiàn)了。

原理就是SDK里絕大部分的網(wǎng)絡請求都會訪問[NSURLCache sharedURLCache]這個對象,它的cachedResponseForRequest:方法會返回一個 NSCachedURLResponse對象。如果這個NSCachedURLResponse對象不為nil,且沒有過期,那么就使用這個緩存的響應, 否則就發(fā)起一個不訪問緩存的請求。
要注意的是NSCachedURLResponse對象不能被提前釋放,除非UIWebView去調(diào)用 NSURLCache的removeCachedResponseForRequest:方法,原因貌似是UIWebView并不retain這個響應。 而這個問題又很頭疼,因為UIWebView有內(nèi)存泄露的嫌疑,即使它被釋放了,也很可能不去調(diào)用上述方法,于是內(nèi)存就一直占用著了。

順便說下NSURLRequest對象,它有個cachePolicy屬性,只要其值為NSURLRequestReloadIgnoringLocalCacheData的話,就不會訪問緩存??上驳氖沁@種情況貌似只有在緩存里沒取到,或是強制刷新時才可能出現(xiàn)。
實 際上NSURLCache本身就有磁盤緩存功能,然而在iOS上,NSCachedURLResponse卻被限制為不能緩存到磁盤 (NSURLCacheStorageAllowed被視為NSURLCacheStorageAllowedInMemoryOnly)。
不過既然知道了原理,那么只要自己實現(xiàn)一個NSURLCache的子類,然后改寫cachedResponseForRequest:方法,讓它從硬盤讀取緩存即可。

于是就開工吧。這次的demo邏輯比較復雜,因此我就按步驟來說明了。

先定義視圖和控制器。
它的邏輯是打開應用時就嘗試訪問緩存文件,如果發(fā)現(xiàn)存在,則顯示緩存完畢;否則就嘗試下載整個網(wǎng)頁的資源;在下載完成后,也顯示緩存完畢。
不過下載所有資源需要解析HTML,甚至是JavaScript和CSS。為了簡化我就直接用一個不顯示的UIWebView載入這個頁面,讓它自動去發(fā)起所有請求。
當然,緩存完了還需要觸發(fā)事件來顯示網(wǎng)頁。于是再提供一個按鈕,點擊時顯示緩存的網(wǎng)頁,再次點擊就關(guān)閉。
順帶一提,我本來想用Google為例的,可惜它自己實現(xiàn)了HTML 5離線瀏覽,也就體現(xiàn)不出這種方法的意義了,于是只好拿百度來墊背。

#import <UIKit/UIKit.h>@interface WebViewController : UIViewController <UIWebViewDelegate> {UIWebView *web;UILabel *label; }@property (nonatomic, retain) UIWebView *web; @property (nonatomic, retain) UILabel *label;- (IBAction)click;@end#import "WebViewController.h" #import "URLCache.h"@implementation WebViewController@synthesize web, label;- (IBAction)click {if (web) {[web removeFromSuperview];self.web = nil;} else {CGRect frame = {{0, 0}, {320, 380}};UIWebView *webview = [[UIWebView alloc] initWithFrame:frame];webview.scalesPageToFit = YES;self.web = webview;NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com/"]];[webview loadRequest:request];[self.view addSubview:webview];[webview release];} }- (void)addButton {CGRect frame = {{130, 400}, {60, 30}};UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];button.frame = frame;[button addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];[button setTitle:@"我點" forState:UIControlStateNormal]; [self.view addSubview:button]; }- (void)viewDidLoad {[super viewDidLoad];URLCache *sharedCache = [[URLCache alloc] initWithMemoryCapacity:1024 * 1024 diskCapacity:0 diskPath:nil];[NSURLCache setSharedURLCache:sharedCache];CGRect frame = {{60, 200}, {200, 30}};UILabel *textLabel = [[UILabel alloc] initWithFrame:frame];textLabel.textAlignment = UITextAlignmentCenter;[self.view addSubview:textLabel];self.label = textLabel;if (![sharedCache.responsesInfo count]) { // not cachedtextLabel.text = @"緩存中…";CGRect frame = {{0, 0}, {320, 380}};UIWebView *webview = [[UIWebView alloc] initWithFrame:frame];webview.delegate = self;self.web = webview;NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.baidu.com/"]];[webview loadRequest:request];[webview release];} else {textLabel.text = @"已從硬盤讀取緩存";[self addButton];}[sharedCache release]; }- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error {self.web = nil;label.text = @"請接通網(wǎng)絡再運行本應用"; }- (void)webViewDidFinishLoad:(UIWebView *)webView {self.web = nil;label.text = @"緩存完畢";[self addButton];URLCache *sharedCache = (URLCache *)[NSURLCache sharedURLCache];[sharedCache saveInfo]; }- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];if (!web) {URLCache *sharedCache = (URLCache *)[NSURLCache sharedURLCache];[sharedCache removeAllCachedResponses];} }- (void)viewDidUnload {self.web = nil;self.label = nil; }- (void)dealloc {[super dealloc];[web release];[label release]; }@end

大部分的代碼沒什么要說的,隨便挑2點。
實現(xiàn)了UIWebViewDelegate,因為需要知道緩存完畢或下載失敗這個事件。
另外,正如前面所說的,UIWebView可能不會通知釋放緩存。所以在收到內(nèi)存警告時,如果UIWebView對象已被釋放,那么就可以安全地清空緩存了(或許還要考慮多線程的影響)。

接下來就是重點了:實現(xiàn)URLCache類。
它需要2個屬性:一個是用于保存NSCachedURLResponse的cachedResponses,另一個是用于保存響應信息的responsesInfo(包括MIME類型和文件名)。
另外還需要實現(xiàn)一個saveInfo方法,用于將responsesInfo保存到磁盤。不過大多數(shù)應用應該使用數(shù)據(jù)庫來保存,這里我只是為了簡化而已。

#import <Foundation/Foundation.h>@interface URLCache : NSURLCache {NSMutableDictionary *cachedResponses;NSMutableDictionary *responsesInfo; }@property (nonatomic, retain) NSMutableDictionary *cachedResponses; @property (nonatomic, retain) NSMutableDictionary *responsesInfo;- (void)saveInfo;@end#import "URLCache.h"@implementation URLCache@synthesize cachedResponses, responsesInfo;- (void)removeCachedResponseForRequest:(NSURLRequest *)request {NSLog(@"removeCachedResponseForRequest:%@", request.URL.absoluteString);[cachedResponses removeObjectForKey:request.URL.absoluteString];[super removeCachedResponseForRequest:request]; }- (void)removeAllCachedResponses {NSLog(@"removeAllObjects");[cachedResponses removeAllObjects];[super removeAllCachedResponses]; }- (void)dealloc {[cachedResponses release];[responsesInfo release]; }@end


寫完這些沒技術(shù)含量的代碼后,就來實現(xiàn)saveInfo方法吧。
這 里有一個要點需要說下,iTunes會備份所有的應用資料,除非放在Library/Caches或tmp文件夾下。由于緩存并不是什么很重要的用戶資 料,沒必要增加用戶的備份時間和空間,所以我們應該把緩存放到這2個文件夾里。而后者會在退出應用或重啟系統(tǒng)時清空,這顯然不是我們想要的效果,于是最佳 選擇是前者。

static NSString *cacheDirectory;+ (void)initialize {NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);cacheDirectory = [[paths objectAtIndex:0] retain]; }- (void)saveInfo {if ([responsesInfo count]) {NSString *path = [cacheDirectory stringByAppendingString:@"responsesInfo.plist"];[responsesInfo writeToFile:path atomically: YES];} }

這里我用了stringByAppendingString:方法,更保險的是使用stringByAppendingPathComponent:。不過我估計后者會做更多的檢查工作,所以采用了前者。

在實現(xiàn)saveInfo后,初始化方法就也可以實現(xiàn)了。它主要就是載入保存的plist文件,如果不存在則新建一個空的NSMutableDictionary對象。

- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path {if (self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path]) {cachedResponses = [[NSMutableDictionary alloc] init];NSString *path = [cacheDirectory stringByAppendingString:@"responsesInfo.plist"];NSFileManager *fileManager = [[NSFileManager alloc] init];if ([fileManager fileExistsAtPath:path]) {responsesInfo = [[NSMutableDictionary alloc] initWithContentsOfFile:path];} else {responsesInfo = [[NSMutableDictionary alloc] init];}[fileManager release];}return self; }


接下來就可以實現(xiàn)cachedResponseForRequest:方法了。
我們得先判斷是不是GET方法,因為其他方法不應該被緩存。還得判斷是不是網(wǎng)絡請求,例如http、https和ftp,因為連data協(xié)議等本地請求都會跑到這個方法里來…

static NSSet *supportSchemes;+ (void)initialize {NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);cacheDirectory = [[paths objectAtIndex:0] retain];supportSchemes = [[NSSet setWithObjects:@"http", @"https", @"ftp", nil] retain]; }- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request {if ([request.HTTPMethod compare:@"GET"] != NSOrderedSame) {return [super cachedResponseForRequest:request];}NSURL *url = request.URL;if (![supportSchemes containsObject:url.scheme]) {return [super cachedResponseForRequest:request];}//... }

因為沒必要處理它們,所以直接交給父類的處理方法了,它會自行決定是否返回nil的。

接著判斷是不是已經(jīng)在cachedResponses里了,這樣的話直接拿出來即可:

NSString *absoluteString = url.absoluteString; NSLog(@"%@", absoluteString); NSCachedURLResponse *cachedResponse = [cachedResponses objectForKey:absoluteString]; if (cachedResponse) {NSLog(@"cached: %@", absoluteString);return cachedResponse; }


再查查responsesInfo里有沒有,如果有的話,說明可以從磁盤獲取:

NSDictionary *responseInfo = [responsesInfo objectForKey:absoluteString]; if (responseInfo) {NSString *path = [cacheDirectory stringByAppendingString:[responseInfo objectForKey:@"filename"]];NSFileManager *fileManager = [[NSFileManager alloc] init];if ([fileManager fileExistsAtPath:path]) {[fileManager release];NSData *data = [NSData dataWithContentsOfFile:path];NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL MIMEType:[responseInfo objectForKey:@"MIMEType"] expectedContentLength:data.length textEncodingName:nil];cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data];[response release];[cachedResponses setObject:cachedResponse forKey:absoluteString];[cachedResponse release];NSLog(@"cached: %@", absoluteString);return cachedResponse;}[fileManager release]; }

這里的難點在于構(gòu)造NSURLResponse和NSCachedURLResponse,不過對照下文檔看看也就清楚了。如前文所說,我們還得把cachedResponse保存到cachedResponses里,避免它被提前釋放。

接下來就說明緩存不存在了,需要我們自己發(fā)起一個請求??珊薜氖荖SURLResponse不能更改屬性,所以還需要手動新建一個NSMutableURLRequest對象:

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

實際上NSMutableURLRequest還有一些其他的屬性,不過并不太重要,所以我就只復制了這2個。

然后就可以用它來發(fā)起請求了。由于UIWebView就是在子線程調(diào)用cachedResponseForRequest:的,不用擔心阻塞的問題,所以無需使用異步請求:

NSError *error = nil; NSURLResponse *response = nil; NSData *data = [NSURLConnection sendSynchronousRequest:newRequest returningResponse:&response error:&error]; if (error) {NSLog(@"%@", error);NSLog(@"not cached: %@", absoluteString);return nil; }


如果下載沒出錯的話,我們就能拿到data和response了,于是就能將其保存到磁盤了。保存的文件名必須是合法且獨一無二的,所以我就用到了sha1算法。

NSString *filename = sha1([absoluteString UTF8String]); NSString *path = [cacheDirectory stringByAppendingString:filename]; NSFileManager *fileManager = [[NSFileManager alloc] init]; [fileManager createFileAtPath:path contents:data attributes:nil]; [fileManager release];


接下來還得將文件信息保存到responsesInfo,并構(gòu)造一個NSCachedURLResponse。
然而這里還有個陷阱,因為直接使用response對象會無效。我稍微研究了一下,發(fā)現(xiàn)它其實是個NSHTTPURLResponse對象,可能是它的allHeaderFields屬性影響了緩存策略,導致不能重用。
不過這難不倒我們,直接像前面那樣構(gòu)造一個NSURLResponse對象就行了,這樣就沒有allHeaderFields屬性了:

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


OK,現(xiàn)在終于大功告成了,打開WIFI然后啟動這個程序,過一會就會提示緩存完畢了。然后關(guān)掉WIFI,嘗試打開網(wǎng)頁,你會發(fā)現(xiàn)網(wǎng)頁能正常載入了。
而查看log,也能發(fā)現(xiàn)這確實是從我們的緩存中取出來的。
還不放心的話可以退出程序,這樣內(nèi)存緩存肯定就釋放了。然后再次進入并打開網(wǎng)頁,你會發(fā)現(xiàn)一切仍然正常~ ? ?

轉(zhuǎn)載于:https://www.cnblogs.com/jiangshiyong/archive/2012/12/26/2834122.html

總結(jié)

以上是生活随笔為你收集整理的UIWebView实现离线浏览的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。