fix: 修复 WKWebView 白屏及 finishActivity JS Bridge 失效问题

- XJBaseWebViewVC: 补全 finishActivity 消息分发,基类默认实现改为 pop 返回
- XJBaseWebViewVC: 新增 decidePolicyForNavigationAction 拦截非 HTTP scheme(alipay:// 等),避免 WKWebView 加载失败导致白屏
- XJBaseWebViewVC: 新增 webViewWebContentProcessDidTerminate,进程被系统杀死后自动 reload
- XJBankWebDetailViewController: 移除重复的 finishActivity override,复用基类 pop 逻辑
- AppDelegate: 移除导致启动卡顿的 WKWebView 预热代码
- HomeV2: 将 Controllview 相关文件移入 Controllview 子目录,整理目录结构
This commit is contained in:
NSArray
2026-03-11 17:58:23 +08:00
parent b69578aa4a
commit d0ff8c2c19
8 changed files with 273 additions and 43 deletions
@@ -154,8 +154,10 @@
} }
- (void)finishActivity { - (void)finishActivity {
// H5 调用 finishActivity 的默认行为:pop 返回上一页
// 语义等同于 Android 的 finish(),即关闭当前页面
// 子类如需特殊处理(如 dismiss、发通知等),override 此方法即可
[self.navigationController popViewControllerAnimated:YES];
} }
@@ -189,7 +191,7 @@
WkJSMessageType messageType = [[dic objectForKey:@"messageType"] intValue]; WkJSMessageType messageType = [[dic objectForKey:@"messageType"] intValue];
switch (messageType) { switch (messageType) {
case WkJSMessageTypeBack:{ case WkJSMessageTypeBack:{
// [self.navigationController popViewControllerAnimated:YES]; [self.navigationController popViewControllerAnimated:YES];
} }
break; break;
default: default:
@@ -199,6 +201,12 @@
} else if ([message.name isEqualToString:@"htmlBack"]) { } else if ([message.name isEqualToString:@"htmlBack"]) {
[[NSNotificationCenter defaultCenter] postNotificationName:@"fromWebToReloadData" object:nil]; [[NSNotificationCenter defaultCenter] postNotificationName:@"fromWebToReloadData" object:nil];
} else if ([message.name isEqualToString:@"finishActivity"]) {
// JS 调用 window.webkit.messageHandlers.finishActivity.postMessage({}) 时触发
// 调用 finishActivity 方法,子类可 override 实现各自的关闭逻辑
// 基类默认实现为空,不做任何操作
[self finishActivity];
} }
} }
@@ -230,6 +238,65 @@
[self hiddenAllMBProgressHUD]; [self hiddenAllMBProgressHUD];
} }
#pragma mark - 白屏修复
/**
* 拦截所有导航请求,处理非 HTTP/HTTPS scheme。
*
* 问题根源:
* 银行 H5 点击支付/跳转按钮时,会触发 alipay:// weixin:// tel:// 等 URL Scheme。
* WKWebView 默认会尝试自己加载这些 scheme,加载失败后清空页面内容 → 永久白屏。
*
* 解决方案:
* 在此方法中拦截非 HTTP/HTTPS 的请求,交由系统(UIApplication)处理,
* WKWebView 自身取消该次导航,页面内容保持不变。
*/
- (void)webView:(WKWebView *)webView
decidePolicyForNavigationAction:(WKNavigationAction *)navigationAction
decisionHandler:(void (^)(WKNavigationActionPolicy))decisionHandler {
NSURL *url = navigationAction.request.URL;
NSString *scheme = url.scheme.lowercaseString;
// HTTP / HTTPS:正常加载,交给 WKWebView 处理
if ([scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"]) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
// about:blankWKWebView 内部使用,直接放行
if ([scheme isEqualToString:@"about"]) {
decisionHandler(WKNavigationActionPolicyAllow);
return;
}
// 其他 schemealipay:// weixin:// tel:// mailto:// 等):
// 交由系统打开对应 App 或拨号,WKWebView 取消此次导航,避免白屏
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url
options:@{}
completionHandler:nil];
}
decisionHandler(WKNavigationActionPolicyCancel);
}
/**
* WebContent 进程被系统终止时的兜底处理。
*
* 触发场景:
* 设备内存严重不足时,系统会强制 kill 掉 WKWebView 的 WebContent 进程。
* 此时 WKWebView 会立即变成空白页,且无法响应任何操作。
* 若不处理,用户只能退出页面重新进入。
*
* 解决方案:
* 检测到进程终止后,重新加载当前 URL,让用户无感知恢复。
*/
- (void)webViewWebContentProcessDidTerminate:(WKWebView *)webView {
NSLog(@"[XJBaseWebViewVC] WebContent 进程被系统终止,自动 reload");
// 重新加载,恢复页面内容
[webView reload];
}
@@ -0,0 +1,16 @@
//
// XJBankWebDetailViewController.h
// XinjiangProject
//
// Created by Apple on 2026/3/11.
//
#import "XJBaseWebViewVC.h"
NS_ASSUME_NONNULL_BEGIN
@interface XJBankWebDetailViewController : XJBaseWebViewVC
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,37 @@
//
// XJBankWebDetailViewController.m
// XinjiangProject
//
// Created by Apple on 2026/3/11.
//
#import "XJBankWebDetailViewController.h"
@interface XJBankWebDetailViewController ()
@end
@implementation XJBankWebDetailViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.isHidenNaviBar = true;
[self.wkWebView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.left.right.mas_offset(0);
make.bottom.mas_offset(-SafetyAreaHeight);
}];
}
// finishActivity 行为与基类一致(pop 返回),无需 override
// 基类 XJBaseWebViewVC 已实现默认 pop 逻辑
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
@@ -4,7 +4,7 @@
// //
// Created by Apple on 2026/2/9. // Created by Apple on 2026/2/9.
// //
#import "XJBankWebDetailViewController.h"
#import "XJHomePageV2ViewController.h" #import "XJHomePageV2ViewController.h"
#import "QuestionViewController.h" #import "QuestionViewController.h"
#import "HealthyQuestionTable.h" #import "HealthyQuestionTable.h"
@@ -76,6 +76,9 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
@property (nonatomic, strong) SDCycleScrollView * AIBannerImage; @property (nonatomic, strong) SDCycleScrollView * AIBannerImage;
//
@property (nonatomic, copy) NSString * secretString;
@end @end
@@ -123,6 +126,7 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
[self initCompleteBlock]; [self initCompleteBlock];
[self configLocationManager]; [self configLocationManager];
[self reGeocodeAction]; [self reGeocodeAction];
// //
// 4. V2TIMSDKListener self id<V2TIMSDKListener> IM SDK // 4. V2TIMSDKListener self id<V2TIMSDKListener> IM SDK
[[V2TIMManager sharedInstance] addIMSDKListener:self]; [[V2TIMManager sharedInstance] addIMSDKListener:self];
@@ -172,10 +176,9 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
// //
[self getMessageListData]; [self getMessageListData];
[self getUserWatchData]; [self getUserWatchData];
[self getWebKeySecret];
} }
} }
@@ -343,6 +346,37 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
[searchApi getHomeWatchData:parmDic]; [searchApi getHomeWatchData:parmDic];
} }
#pragma mark: 请求key的加密
- (void)getWebKeySecret {
NSString * scheme = SCHEME;
NSString * host;
#if DEBUG
host = @"xj-api.mcrm.vip";
#else
host = @"api-jkglpt.iosp.ydpt.tech";
#endif
NSString * token = User_Token;
NSString * keyStirng = [NSString stringWithFormat:@"scheme=%@&host=%@&tokenF=%@",scheme,host,token];
NSMutableDictionary * parmDic = [[NSMutableDictionary alloc] init];
[parmDic setObject:keyStirng forKey:@"str"];
XJPNetAPI * searchApi = [[XJPNetAPI alloc] init:self tag:@"SECRET" NeedToken:@""];
[searchApi getWebNeedEncryptionKey:parmDic];
}
- (void)Failed:(NSString*)message tag:(NSString*)tag - (void)Failed:(NSString*)message tag:(NSString*)tag
{ {
@@ -411,9 +445,6 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
[self getData]; [self getData];
} }
} }
if ([tag isEqualToString:@"IMAPI"]) { if ([tag isEqualToString:@"IMAPI"]) {
@@ -444,9 +475,6 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
}]; }];
} }
if ([tag isEqualToString:@"SMALLHELP"]) { if ([tag isEqualToString:@"SMALLHELP"]) {
@@ -644,6 +672,17 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
self.topView.watchArray = sortedArray; self.topView.watchArray = sortedArray;
} }
if ([tag isEqualToString:@"SECRET"]) {
DLog(@"%@",response)
NSDictionary * responseDic = response;
if ([responseDic.allKeys containsObject:@"encryptData"]) {
self.secretString = responseDic[@"encryptData"];
}
}
} }
// //
@@ -722,26 +761,26 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
[self.navigationController pushViewController:foundVC animated:true]; [self.navigationController pushViewController:foundVC animated:true];
} }
//
- (void)smallQuestionAction { //- (void)smallQuestionAction {
//
if ([ESCTUIManager TUIUserStatus] == false) { // if ([ESCTUIManager TUIUserStatus] == false) {
//
//
[self loginImAccount]; // [self loginImAccount];
return; // return;
} // }
//
NSMutableDictionary * dic = [[NSMutableDictionary alloc] init]; // NSMutableDictionary * dic = [[NSMutableDictionary alloc] init];
//
[self showMBProgressHUDWithString:@"咨询中,请稍候..." autoHidden:NO]; // [self showMBProgressHUDWithString:@"咨询中,请稍候..." autoHidden:NO];
XJPNetAPI * searchApi = [[XJPNetAPI alloc] init:self tag:@"SMALLHELP" NeedToken:@""]; // XJPNetAPI * searchApi = [[XJPNetAPI alloc] init:self tag:@"SMALLHELP" NeedToken:@""];
//
[searchApi postSmallHelpGroup:dic]; // [searchApi postSmallHelpGroup:dic];
//
//
//
} //}
- (void)myQuestionAction { - (void)myQuestionAction {
@@ -903,13 +942,62 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
}]; }];
// //
self.homeTable.headDelegate = self; self.homeTable.headDelegate = self;
kWeakSelf(self);
self.homeTable.tableBlock = ^(HomeQusetionRecordsModel * _Nonnull model) {
weakself.recomendModel = model;
if(![HQCommonUtils isLogin]) {
KPostNotification(KNotificationLoginStateChange, @NO);
return;
}
//1 2
if ([model.contentType intValue] == 2) {
XJDoctorMainViewController * doctorVC = [[XJDoctorMainViewController alloc] init];
doctorVC.yuyue_id = model.records_id;
doctorVC.popType = 1;
[weakself.navigationController pushViewController:doctorVC animated:YES];
}
if ([model.contentType intValue] == 1) {
if ([TUILogin isUserLogined]) {
ChatViewController * chatVC = [[ChatViewController alloc] init];
chatVC.isGroupID = YES;
chatVC.chatID = model.imId ;
chatVC.eableSendReport = true;
chatVC.eablehaveHelathData = false;
chatVC.navTitle = @"图文咨询";
chatVC.isVideo = NO;
chatVC.pictureEnd_id = model.records_id;
chatVC.questionType = 2;
chatVC.medicalRecordsId = model.memberId;
if ([model.contentStatus intValue] == 5) {
chatVC.isEveal = true;
}else
{
chatVC.isEveal = false;
}
if ([model.contentStatus intValue] >=4 ) {
chatVC.endRecordType = 100;
}
[weakself.navigationController pushViewController:chatVC animated:YES];
}else
{
if ([HQCommonUtils isLogin]) {
[weakself loginImAccountList];
}
}
}
};
// //
[self.homeTable addObserver:self [self.homeTable addObserver:self
forKeyPath:@"contentSize" forKeyPath:@"contentSize"
options:NSKeyValueObservingOptionNew options:NSKeyValueObservingOptionNew
context:nil]; context:nil];
kWeakSelf(self);
self.homeTable.lookMoreBlock = ^{ self.homeTable.lookMoreBlock = ^{
if(![HQCommonUtils isLogin]) { if(![HQCommonUtils isLogin]) {
@@ -988,6 +1076,7 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
}]; }];
} }
// table的高度 // table的高度
@@ -1084,10 +1173,31 @@ static CGFloat const kEmptyTableMinHeight = 250; // 或屏幕高度的一半
} }
if (type == 3) { if (type == 3) {
EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init]; // EmptyFunctionDevelopmentVC * vc = [[EmptyFunctionDevelopmentVC alloc] init];
vc.navigationItem.title = @"健康银行"; // vc.navigationItem.title = @"健康银行";
[self.navigationController pushViewController:vc animated:YES]; // [self.navigationController pushViewController:vc animated:YES];
if(![HQCommonUtils isLogin]) {
KPostNotification(KNotificationLoginStateChange, @NO);
return;
}
if (ValidStr(self.secretString)) {
[self showMBProgressHUDOnlyWithString:@"网络异常请稍候再试~"];
[self getWebKeySecret];
return;
}
XJBankWebDetailViewController * bankVC = [[XJBankWebDetailViewController alloc] init];
NSString * urlString;
#if DEBUG
urlString = @"https://gstest.superwx.cn/healthbank/index.html?";
#else
urlString = @"https://console-jkglpt.iosp.ydpt.tech/hb/healthweb/index.html";
#endif
bankVC.webUrl = [NSString stringWithFormat:@"%@?param=%@",urlString,self.secretString];
[self.navigationController pushViewController:bankVC animated:YES];
} }
} }