iOS之UIWebView的坑

weixin_34124651發表於2017-06-29

iOS之UIWebView的坑

背景:我廠這個版本在開發航拍圖,是一個H5頁面,但是就是一個H5頁面竟然引起了APP的crash。在當前航拍圖頁面,app進入後臺,會直接crash。掛在gpus_returnnotpermittedkillclient
這個bug是iOS10.8以後就有的,特大好訊息就是,iOS11蘋果就把這個bug修復了,所以iOS11不會出現。

原因是,iOS進入後臺禁止呼叫webGL,一幀都不允許,而這個H5還在呼叫,所以直接崩潰。而且遊戲裡面使用的openGL,在進入後臺的時候是直接停止的

953632-6c43ddbbc5f88632.png
E04598C5-7BAD-4A29-B686-39FC80115D91.f9475027fb52451fa7a58f23c72e3c3a.png

失敗方案:
參考連結:其中一個解決方案
[https://forums.developer.apple.com/thread/30896]

1.webView loadRequest 之間呼叫,set bEnable NO,但是會引起webView上整張圖都載入不出來。
2.進入後臺的時候 set bEnable NO,進入前臺 set bEnable YES,但是依然沒有什麼用。

typedef void (*CallFuc)(id, SEL, BOOL);
typedef BOOL (*GetFuc)(id, SEL);
-(BOOL)webView:(UIWebView*)view enableGL:(BOOL)bEnable
{
  BOOL bRet = NO;
  do
  {
  Ivar internalVar = class_getInstanceVariable([view class], "_internal");
  if (!internalVar)
  {
  NSLog(@"enable GL _internal invalid!");
  break;
  }
   
  UIWebViewInternal* internalObj = object_getIvar(view, internalVar);
  Ivar browserVar = class_getInstanceVariable(object_getClass(internalObj), "browserView");
  if (!browserVar)
  {
  NSLog(@"enable GL browserView invalid!");
  break;
  }
   
  id webbrowser = object_getIvar(internalObj, browserVar);
  Ivar webViewVar = class_getInstanceVariable(object_getClass(webbrowser), "_webView");
  if (!webViewVar)
  {
  NSLog(@"enable GL _webView invalid!");
  break;
  }
   
  id webView = object_getIvar(webbrowser, webViewVar);
  if (!webView)
  {
  NSLog(@"enable GL webView obj nil!");
  }
   
  if(object_getClass(webView) != NSClassFromString(@"WebView"))
  {
  NSLog(@"enable GL webView not WebView!");
  break;
  }
   
  SEL selector = NSSelectorFromString(@"_setWebGLEnabled:");
  IMP impSet = [webView methodForSelector:selector];
  CallFuc func = (CallFuc)impSet;
  func(webView, selector, bEnable);
   
  SEL selectorGet = NSSelectorFromString(@"_webGLEnabled");
  IMP impGet = [webView methodForSelector:selectorGet];
  GetFuc funcGet = (GetFuc)impGet;
  BOOL val = funcGet(webView, selector);
   
  bRet = (val == bEnable);
   
  }while(NO);
   
  return bRet;
}

最終解決方案有兩個:
1.H5協議,暴露出可以停止繪製和重新繪製的方法,app端在進前臺和後臺的時候呼叫。
2.用WKWebView把UIWebView替換掉。
參考文件:[https://developer.apple.com/documentation/webkit/wkwebview]

一些我用到的WKWebView代理如下,詳細的參考官方文件;

- (void)loadWebViewWithURL:(NSString *)urlString
{
    NSURL *url = [self rtCreateWebViewURL:urlString];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    [request setValue:[AIFAppContext sharedInstance].appName forHTTPHeaderField:@"X-AJK-APP"];
    NSString *cv = [[AJKSettingsManager sharedInstance] getAppVersion]?[[AJKSettingsManager sharedInstance] getAppVersion]:@"";
    [request setValue:cv forHTTPHeaderField:@"X-AJK-CV"];
    [self.webView loadRequest:request];
}

- (NSURL *)rtCreateWebViewURL:(NSString *)urlString
{
    return [NSURL URLWithString:urlString];
}

pragma mark - WKNavigationDelegate
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation{
    [self routeHandleWithRequest:webView.URL];
}

- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation{
    weakify(self);
    [webView evaluateJavaScript:@"document.title" completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
        if (obj) {
            strongify(self);
            NSString *title = [NSString stringWithFormat:@"%@",obj];
            self.title = title;
        }
    }];
    NSURL *requestURL = webView.URL;
    NSString *host = [requestURL host];
    if ([host rangeOfString:@"anjuke.com" options:NSCaseInsensitiveSearch].location != NSNotFound
        || [host rangeOfString:@"58.com" options:NSCaseInsensitiveSearch].location != NSNotFound
        || [host rangeOfString:@"anjuke.test" options:NSCaseInsensitiveSearch].location != NSNotFound) {
        weakify(self);
        [webView evaluateJavaScript:[NSString stringWithFormat:@"getShareContents()"] completionHandler:^(id _Nullable obj, NSError * _Nullable error) {
            if (obj) {
                strongify(self);
                NSString *str = [NSString stringWithFormat:@"%@",obj];
                if (str.length > 0) {
                    NSArray *shares = [str JSONValue];
                    if ([shares isKindOfClass:[NSArray class]] && shares.count > 0) {
                        [self setShareDicFromArray:shares];
                        [self showShareBtn];
                    }
                }
            }
        }];
    }

    [self.activity removeFromSuperview];
}

- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error{
    if (![self checkNetwork]) {
        [self.errorView showInView:self.view type:RTMessageTypeNoNetWork target:self action:@selector(reloadWebView)];
    }
    [self.activity removeFromSuperview];
}

- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error{
    
}



pragma mark - WKScriptMessageHandler
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message{
    
}

html的alert不彈的問題

需要實現WKWebView的UIDelegate

#pragma mark - WKUIDelegate
- (void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler();
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
    
}

- (void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"提示" message:message?:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addAction:([UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(NO);
    }])];
    [alertController addAction:([UIAlertAction actionWithTitle:@"確認" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(YES);
    }])];
    [self presentViewController:alertController animated:YES completion:nil];
}

- (void)webView:(WKWebView *)webView runJavaScriptTextInputPanelWithPrompt:(NSString *)prompt defaultText:(NSString *)defaultText initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(NSString * _Nullable))completionHandler{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:prompt message:@"" preferredStyle:UIAlertControllerStyleAlert];
    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
        textField.text = defaultText;
    }];
    [alertController addAction:([UIAlertAction actionWithTitle:@"完成" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        completionHandler(alertController.textFields[0].text?:@"");
    }])];
    
    
    [self presentViewController:alertController animated:YES completion:nil];
}

所以下版本計劃將原本app裡面的ADViewController的UIWebView替換成WKWebView,希望沒有什麼新坑!

相關文章