深入理解WKWebView白屏

6 篇文章 0 订阅
1 篇文章 0 订阅

白屏是webview进程终止之后的表现,在webview因异常使用内存、CPU等资源时,webkit会终止当前m页展示的进程,在用户端表现为白页。

第一、webview的进程被终止的原因有哪些?

ProcessTerminationReason {
    ExceededMemoryLimit,//超出内存限制
    ExceededCPULimit,//超出CPU限制
    RequestedByClient,//主动触发的terminate
    Crash,//web进程自己发生了crash
    NavigationSwap,//m页的加载环境出现了变化
};

第二、内存限制是如何计算的?

1、苹果是如何计算当前内存的使用量的?

主要是通过计算当前进程的phys_footprint来度量内存的使用量

namespace WTF {

size_t memoryFootprint()
{
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    kern_return_t result = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
    if (result != KERN_SUCCESS)
        return 0;
    return static_cast<size_t>(vmInfo.phys_footprint);
}

}

2、内存使用过量的阈值是如何计算的?

内存超出的计算依赖了两个值:基于CPU框架的基本阈值baseThreshold和手机内存大小ramSize来计算阈值

static size_t thresholdForMemoryKillWithProcessState(WebsamProcessState processState, unsigned tabCount)
{
    size_t baseThreshold = 2 * GB;
#if CPU(X86_64) || CPU(ARM64)
    if (processState == WebsamProcessState::Active)
        baseThreshold = 4 * GB;
    if (tabCount > 1)
        baseThreshold += std::min(tabCount - 1, 4u) * 1 * GB;
#else
    if ((tabCount > 1) || (processState == WebsamProcessState::Active))
        baseThreshold = 3 * GB;
#endif
    return std::min(baseThreshold, static_cast<size_t>(ramSize() * 0.9));
}

这里可以看出,baseThreshold初始化为2GB,64位CPU初始化位4GB,多tab时每个tab可以多加1GB(总计不超过4GB)。非64位CPU在多tab的情况下最多为3GB。

再来看ramSize大小的计算方法,因为webkit是一个多平台使用的框架,这里计算ramsize,我们只分析iOS系统的计算方法:

size_t ramSize()
{
    static size_t ramSize;
    static std::once_flag onceFlag;
    std::call_once(onceFlag, [] {
        ramSize = computeRAMSize();
    });
    return ramSize;
}

这是个单例,只运算一次。而computeRAMSize()的实现如下:

static size_t computeRAMSize()
{
#if OS(WINDOWS)
    MEMORYSTATUSEX status;
    status.dwLength = sizeof(status);
    bool result = GlobalMemoryStatusEx(&status);
    if (!result)
        return ramSizeGuess;
    return status.ullTotalPhys;
#elif defined(USE_SYSTEM_MALLOC) && USE_SYSTEM_MALLOC
#if OS(UNIX)
    struct sysinfo si;
    sysinfo(&si);
    return si.totalram * si.mem_unit;
#else
#error "Missing a platform specific way of determining the available RAM"
#endif // OS(UNIX)
#else
    return bmalloc::api::availableMemory();
#endif
}

这里有windows系统和Unix系统以及其他三种分类,但是这里的Unix系统不包括iOS系统(虽然iOS是基于Unix的),我们的iOS系统在最后一个分支bmalloc::api::availableMemory()中。来看看该方法的实现:

//调用第一步
inline size_t availableMemory()
{
    return bmalloc::availableMemory();
}

//调用第二步
size_t availableMemory()
{
    static size_t availableMemory;
    static std::once_flag onceFlag;
    std::call_once(onceFlag, [] {
        availableMemory = computeAvailableMemory();
    });
    return availableMemory;
}

//调用第三步
static size_t computeAvailableMemory()
{
#if BOS(DARWIN)
    size_t sizeAccordingToKernel = memorySizeAccordingToKernel();
#if BPLATFORM(IOS_FAMILY)
    sizeAccordingToKernel = std::min(sizeAccordingToKernel, jetsamLimit());
#endif
    size_t multiple = 128 * bmalloc::MB;

    // Round up the memory size to a multiple of 128MB because max_mem may not be exactly 512MB
    // (for example) and we have code that depends on those boundaries.
    return ((sizeAccordingToKernel + multiple - 1) / multiple) * multiple;
#elif BOS(UNIX)
    long pages = sysconf(_SC_PHYS_PAGES);
    long pageSize = sysconf(_SC_PAGE_SIZE);
    if (pages == -1 || pageSize == -1)
        return availableMemoryGuess;
    return pages * pageSize;
#else
    return availableMemoryGuess;
#endif
}

第三步是计算设备ramsize的核心代码,这里sizeAccordingToKernel的计算与两个很重要的方法memorySizeAccordingToKernel()、jetsamLimit()相关联,前者是与当前设备内核相关的内存size,后者jetsamLimit是苹果对各个进程使用内存的限制,我们来分析一下。

static const size_t availableMemoryGuess = 512 * bmalloc::MB;

#if BOS(DARWIN)
static size_t memorySizeAccordingToKernel()
{
#if BPLATFORM(IOS_FAMILY_SIMULATOR)
    BUNUSED_PARAM(availableMemoryGuess);
    // Pretend we have 1024MB of memory to make cache sizes behave like on device.
    return 1024 * bmalloc::MB;
#else
    host_basic_info_data_t hostInfo;

    mach_port_t host = mach_host_self();
    mach_msg_type_number_t count = HOST_BASIC_INFO_COUNT;
    kern_return_t r = host_info(host, HOST_BASIC_INFO, (host_info_t)&hostInfo, &count);
    mach_port_deallocate(mach_task_self(), host);
    if (r != KERN_SUCCESS)
        return availableMemoryGuess;

    if (hostInfo.max_mem > std::numeric_limits<size_t>::max())
        return std::numeric_limits<size_t>::max();

    return static_cast<size_t>(hostInfo.max_mem);
#endif
}
#endif

这里有三个要点:

一是模拟器的内存size为1G;

二是对于真实设备,它主要计算结构体host_basic_info_data_t中max_mem的大小,然后取MIN(max_mem,std::numeric_limits<size_t>::max()),这里std::numeric_limits<size_t>::max()取当前设备可以表示的最大值;

三是上述计算失败时默认为512M。

再来看jatsamLimit的实现

#if BPLATFORM(IOS_FAMILY)
static size_t jetsamLimit()
{
    memorystatus_memlimit_properties_t properties;
    pid_t pid = getpid();
    if (memorystatus_control(MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES, pid, 0, &properties, sizeof(properties)))
        return 840 * bmalloc::MB;
    if (properties.memlimit_active < 0)
        return std::numeric_limits<size_t>::max();
    return static_cast<size_t>(properties.memlimit_active) * bmalloc::MB;
}
#endif

这个方法实现有三种可能

1、如果memorystatus_control返回值不为0时,其返回值为840M

2、如果memoryStatus的限制属性memlimit_active<0时,返回当前设备可以表示的最大值。

3、如果运算正常,则返回系统的取值。

这里jetsam的相关运算对我们开发者来说是黑盒无文档的,有兴趣的同学可以通过下面这篇文章了解一下

(译)Handling low memory conditions in iOS and Mavericks - 掘金

至此,我们可以获取iOS设备的ramsize了

#if BPLATFORM(IOS_FAMILY)
    sizeAccordingToKernel = std::min(sizeAccordingToKernel, jetsamLimit());
#endif
    size_t multiple = 128 * bmalloc::MB;

    // Round up the memory size to a multiple of 128MB because max_mem may not be exactly 512MB
    // (for example) and we have code that depends on those boundaries.
    return ((sizeAccordingToKernel + multiple - 1) / multiple) * multiple;

那么进程被kill的内存上限为:

return std::min(baseThreshold, static_cast<size_t>(ramSize() * 0.9));

第三、内存使用过度时白屏发生的具体流程是什么?

发生白屏的基本流程如下图所示

当我们给webview设置了navigationDelegate时,这里的delegate方法将由NavigationState这个类来触发。

MemoryPressureHandler是一个单例,当它判断当前内存使用过量时会触发WebProcess的memoryKillCallBack,此回调是在initializeWebProcess进行注册的,WebProcess通过IPC机制将消息转发给WebProcessProxy,WebProcessProxy调用requestTermination方法,然后调用了processDidTerminate--->dispatchProcessDidTerminate,这里才会将ProcessDidTerminate的处理交给用户。

这里需要说明一下,memoryKillCallback以及设置每隔30s检查内存,Apple的实现中都用宏包裹了,

#if (PLATFORM(MAC) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 101200) || PLATFORM(GTK) || PLATFORM(WPE)
        memoryPressureHandler.setShouldUsePeriodicMemoryMonitor(true);
        memoryPressureHandler.setMemoryKillCallback([this] () {
            WebCore::logMemoryStatisticsAtTimeOfDeath();
            if (MemoryPressureHandler::singleton().processState() == WebsamProcessState::Active)
                parentProcessConnection()->send(Messages::WebProcessProxy::DidExceedActiveMemoryLimit(), 0);
            else
                parentProcessConnection()->send(Messages::WebProcessProxy::DidExceedInactiveMemoryLimit(), 0);
        });
        memoryPressureHandler.setDidExceedInactiveLimitWhileActiveCallback([this] () {
            parentProcessConnection()->send(Messages::WebProcessProxy::DidExceedInactiveMemoryLimitWhileActive(), 0);
        });
#endif

看起来并不是iOS的机制,但是我们从方法的调用关系进行了全局检索,目前发现内存超出导致的白屏只有这么一条调用链。

第四、如果开发者不实现webViewWebContentProcessDidTerminate:(WKWebView *)webView的代理方法,默认的处理是什么?

苹果对WebContentProcessDidTerminate的处理逻辑如下:

void WebPageProxy::dispatchProcessDidTerminate(ProcessTerminationReason reason)
{
    bool handledByClient = false;
    if (m_loaderClient)
        handledByClient = reason != ProcessTerminationReason::RequestedByClient && m_loaderClient->processDidCrash(*this);
    else
        handledByClient = m_navigationClient->processDidTerminate(*this, reason);

    if (!handledByClient && shouldReloadAfterProcessTermination(reason))
        tryReloadAfterProcessTermination();
}

这里的m_loaderClient只在苹果的单元测试中有使用,所以,正式版本的iOS下应该会执行

handledByClient = m_navigationClient->processDidTerminate(*this, reason);

这里如果开发者未实现webViewWebContentProcessDidTerminate的代理方法,它将返回false,进入苹果的默认逻辑:

static bool shouldReloadAfterProcessTermination(ProcessTerminationReason reason)
{
    switch (reason) {
    case ProcessTerminationReason::ExceededMemoryLimit:
    case ProcessTerminationReason::ExceededCPULimit:
    case ProcessTerminationReason::Crash:
        return true;
    case ProcessTerminationReason::NavigationSwap:
    case ProcessTerminationReason::RequestedByClient:
        break;
    }
    return false;
}

从这个方法可以看出,苹果认为在内存超出、CPU超出、以及发生了Crash的场景下需要重新刷新。这里有两点需要注意:

第一、重新刷新是苹果官方的默认处理webViewWebContentProcessDidTerminate的方法;

第二、重新刷新是有条件的。

再来看具体的刷新逻辑:

static unsigned maximumWebProcessRelaunchAttempts = 1;

void WebPageProxy::tryReloadAfterProcessTermination()
{
    m_resetRecentCrashCountTimer.stop();

    if (++m_recentCrashCount > maximumWebProcessRelaunchAttempts) {
        RELEASE_LOG_IF_ALLOWED(Process, "%p - WebPageProxy's process crashed and the client did not handle it, not reloading the page because we reached the maximum number of attempts", this);
        m_recentCrashCount = 0;
        return;
    }
    RELEASE_LOG_IF_ALLOWED(Process, "%p - WebPageProxy's process crashed and the client did not handle it, reloading the page", this);
    reload(ReloadOption::ExpiredOnly);
}

这里每次crash时苹果会给crash标示+1,在标示不超过1时(其实只有m_recentCrashCount为0时),系统会进行刷新,当最近crash的次数超过一次时它便不会刷新,只是将标示归位为0,下次就可以刷新。

后记:我们在iOS的Safari上测试了safari的白屏处理逻辑,当第一次发生白屏时Safari会默认重刷,第二次时safari会展示错误加载页,提示当前页面多次发生了错误。这个逻辑和上面webkit的默认处理逻辑时相似的。

第五、CPU使用过量的白屏是如何触发的?

很遗憾,截止目前,苹果在iOS下没有监控CPU,只有mac下会监控。我们可以从mac的逻辑中窥探一二。

#if PLATFORM(MAC)
    std::unique_ptr<WebCore::CPUMonitor> m_cpuMonitor;
    std::optional<double> m_cpuLimit;
#endif


void WebProcess::updateCPUMonitorState(CPUMonitorUpdateReason reason)
{
#if PLATFORM(MAC)
    if (!m_cpuLimit) {
        if (m_cpuMonitor)
            m_cpuMonitor->setCPULimit(std::nullopt);
        return;
    }

    if (!m_cpuMonitor) {
        m_cpuMonitor = std::make_unique<CPUMonitor>(cpuMonitoringInterval, [this](double cpuUsage) {
            RELEASE_LOG(PerformanceLogging, "%p - WebProcess exceeded CPU limit of %.1f%% (was using %.1f%%) hasVisiblePages? %d", this, m_cpuLimit.value() * 100, cpuUsage * 100, hasVisibleWebPage());
            parentProcessConnection()->send(Messages::WebProcessProxy::DidExceedCPULimit(), 0);
        });
    } else if (reason == CPUMonitorUpdateReason::VisibilityHasChanged) {
        // If the visibility has changed, stop the CPU monitor before setting its limit. This is needed because the CPU usage can vary wildly based on visibility and we would
        // not want to report that a process has exceeded its background CPU limit even though most of the CPU time was used while the process was visible.
        m_cpuMonitor->setCPULimit(std::nullopt);
    }
    m_cpuMonitor->setCPULimit(m_cpuLimit.value());
#else
    UNUSED_PARAM(reason);
#endif
}

这里有两个成员变量:m_cpuMonitor和m_cpuLimit,这两个成员变量只在mac下定义。m_cpuMonitor是一个unique的智能指针,它初始化时需要添加一个闭包来响应超过cpu使用量时的事件,这里的响应行为不难发现时向WebProcessProxy发送消息,WebProcessProxy处理逻辑如下:

void WebProcessProxy::didExceedCPULimit()
{
    for (auto& page : pages()) {
        if (page->isPlayingAudio()) {
            RELEASE_LOG(PerformanceLogging, "%p - WebProcessProxy::didExceedCPULimit() WebProcess with pid %d has exceeded the background CPU limit but we are not terminating it because there is audio playing", this, processIdentifier());
            return;
        }

        if (page->hasActiveAudioStream() || page->hasActiveVideoStream()) {
            RELEASE_LOG(PerformanceLogging, "%p - WebProcessProxy::didExceedCPULimit() WebProcess with pid %d has exceeded the background CPU limit but we are not terminating it because it is capturing audio / video", this, processIdentifier());
            return;
        }
    }

    bool hasVisiblePage = false;
    for (auto& page : pages()) {
        if (page->isViewVisible()) {
            page->didExceedBackgroundCPULimitWhileInForeground();
            hasVisiblePage = true;
        }
    }

    // We only notify the client that the process exceeded the CPU limit when it is visible, we do not terminate it.
    if (hasVisiblePage)
        return;

    RELEASE_LOG_ERROR(PerformanceLogging, "%p - WebProcessProxy::didExceedCPULimit() Terminating background WebProcess with pid %d that has exceeded the background CPU limit", this, processIdentifier());
    logDiagnosticMessageForResourceLimitTermination(DiagnosticLoggingKeys::exceededBackgroundCPULimitKey());
    requestTermination(ProcessTerminationReason::ExceededCPULimit);
}

可以看出:

第一、当前的web如果有窗口在播放音视频,则不做响应;

第二、当前web如果存在可见页面时不做响应;

第三、调用了requestTermination方法,和超过内存限制情形下进行同样的处理。

另:我们在WebKit的源码中没有看到Mac下m_cpuLimit的值的大小。

第六、ProcessTerminationReason为Crash发生在哪些场景下?

整个WebKit源码中只有一处调用了Crash的情况:

void WebProcessProxy::processDidTerminateOrFailedToLaunch()
{
    // Protect ourselves, as the call to disconnect() below may otherwise cause us
    // to be deleted before we can finish our work.
    Ref<WebProcessProxy> protect(*this);

    if (auto* webConnection = this->webConnection())
        webConnection->didClose();

    auto pages = copyToVectorOf<RefPtr<WebPageProxy>>(m_pageMap.values());

    shutDown();

#if ENABLE(PUBLIC_SUFFIX_LIST)
    if (pages.size() == 1) {
        auto& page = *pages[0];
        String domain = topPrivatelyControlledDomain(WebCore::URL({ }, page.currentURL()).host().toString());
        if (!domain.isEmpty())
            page.logDiagnosticMessageWithEnhancedPrivacy(WebCore::DiagnosticLoggingKeys::domainCausingCrashKey(), domain, WebCore::ShouldSample::No);
    }
#endif

    for (auto& page : pages)
        page->processDidTerminate(ProcessTerminationReason::Crash);
}

这里,当WebProcessProxy的processDidTerminateOrFailedToLaunch方法被调用时,它会发送消息给WebPageProxy,后续的处理和内存过度使用是一致的。现在我们来看processDidTerminateOrFailedToLaunch被调用的情况:

第一种调用情况:当前的IPC连接标示不合法时会触发

void WebProcessProxy::didFinishLaunching(ProcessLauncher* launcher, IPC::Connection::Identifier connectionIdentifier)
{
    RELEASE_ASSERT(isMainThreadOrCheckDisabled());

    ChildProcessProxy::didFinishLaunching(launcher, connectionIdentifier);

    if (!IPC::Connection::identifierIsValid(connectionIdentifier)) {
        RELEASE_LOG_IF(m_websiteDataStore->sessionID().isAlwaysOnLoggingAllowed(), Process, "%p - WebProcessProxy didFinishLaunching - invalid connection identifier (web process failed to launch)", this);
        processDidTerminateOrFailedToLaunch();
        return;
    }

    ...
}

这里连接标示的合法性由mach_port来完成

#define MACH_PORT_VALID(name)				\
		(((name) != MACH_PORT_NULL) && 		\
		 ((name) != MACH_PORT_DEAD))

static bool identifierIsValid(Identifier identifier) { return MACH_PORT_VALID(identifier.port); }

主要是验证标示的端口是否合法。

第二种情况:

当WebProcessProxy的didClose方法调用时会触发

void WebProcessProxy::didClose(IPC::Connection&)
{
    RELEASE_LOG_IF(m_websiteDataStore->sessionID().isAlwaysOnLoggingAllowed(), Process, "%p - WebProcessProxy didClose (web process crash)", this);
    processDidTerminateOrFailedToLaunch();
}

void WebProcessProxy::didReceiveInvalidMessage(IPC::Connection& connection, IPC::StringReference messageReceiverName, IPC::StringReference messageName)
{
    WTFLogAlways("Received an invalid message \"%s.%s\" from the web process.\n", messageReceiverName.toString().data(), messageName.toString().data());

    WebProcessPool::didReceiveInvalidMessage(messageReceiverName, messageName);

    // Terminate the WebProcess.
    terminate();

    // Since we've invalidated the connection we'll never get a IPC::Connection::Client::didClose
    // callback so we'll explicitly call it here instead.
    didClose(connection);
}

didClose则是进程间通信时收到不合法的消息会触发。所以,综上:

Crash类型的进程终止一般都发生在进程通信机制中,当进程连接的端口不合法或者通信时发送的消息不合法时,则会被认为是发生了Crash,进程会终止。

第七、RequestedByClient类型的进程终止发生的场景有哪些?

这种类型进程终止在两个类中有体现:

WKWebView.mm:

- (void)_killWebContentProcessAndResetState
{
    Ref<WebKit::WebProcessProxy> protectedProcessProxy(_page->process());
    protectedProcessProxy->requestTermination(WebKit::ProcessTerminationReason::RequestedByClient);
}

 WKPage.cpp

void WKPageTerminate(WKPageRef pageRef)
{
    Ref<WebProcessProxy> protectedProcessProxy(toImpl(pageRef)->process());
    protectedProcessProxy->requestTermination(ProcessTerminationReason::RequestedByClient);
}

这两个方法的调用全部在Webkit的单元测试中,因此开发者无法调用,属于WebKit的“特权”范畴,目前看它们的是提供给Apple内部来使用,而使用的时机和具体的业务场景没有强关联。

第八、NavigationSwap发生在什么场景下?

这里需要知道:WebKit有个进程池(ProcessPool),进程池中包含多个WebProcessProxy,页面每次加载时WebProcessProxy会去ProcessPool里面再次获取当前页面可能对应的WebProcessProxy,如果当前的WebProcessProxy与获取到的WebProcessProxy不是同一个时,则说明页面加载的进程发生了变化,此时需要交互页面加载的进程

void WebPageProxy::receivedNavigationPolicyDecision(PolicyAction policyAction, API::Navigation* navigation, ProcessSwapRequestedByClient processSwapRequestedByClient, WebFrameProxy& frame, API::WebsitePolicies* policies, Ref<PolicyDecisionSender>&& sender)
{
    ........
        auto proposedProcess = process().processPool().processForNavigation(*this, *navigation, processSwapRequestedByClient, policyAction, reason);
        ASSERT(!reason.isNull());
        
        if (proposedProcess.ptr() != &process()) {
            RELEASE_LOG_IF_ALLOWED(ProcessSwapping, "%p - WebPageProxy::decidePolicyForNavigationAction, swapping process %i with process %i for navigation, reason: %{public}s", this, processIdentifier(), proposedProcess->processIdentifier(), reason.utf8().data());
            LOG(ProcessSwapping, "(ProcessSwapping) Switching from process %i to new process (%i) for navigation %" PRIu64 " '%s'", processIdentifier(), proposedProcess->processIdentifier(), navigation->navigationID(), navigation->loggingString());
            
            RunLoop::main().dispatch([this, protectedThis = makeRef(*this), navigation = makeRef(*navigation), proposedProcess = WTFMove(proposedProcess)]() mutable {
                continueNavigationInNewProcess(navigation, WTFMove(proposedProcess));
            });
     ........

}

而在continueNavigationInNewProcess处理中,WebKit先触发了processDidTerminate,然后做了“交换进程的操作”:

void WebPageProxy::continueNavigationInNewProcess(API::Navigation& navigation, Ref<WebProcessProxy>&& process)
{
   ......
    processDidTerminate(ProcessTerminationReason::NavigationSwap);

    swapToWebProcess(WTFMove(process), navigation, mainFrameIDInPreviousProcess);

   ......
}

这里的processDidTerminate就进入了与处理过度使用内存类似的逻辑了,But!!!!这种情况不会出现白屏,也不会回调给用户,我们再来看上述方法的实现:

void WebPageProxy::processDidTerminate(ProcessTerminationReason reason)
{
    if (reason != ProcessTerminationReason::NavigationSwap)
        RELEASE_LOG_IF_ALLOWED(Process, "%p - WebPageProxy::processDidTerminate (pid %d), reason %d", this, processIdentifier(), reason);

    ASSERT(m_isValid);

#if PLATFORM(IOS_FAMILY)
    if (m_process->isUnderMemoryPressure()) {
        String domain = WebCore::topPrivatelyControlledDomain(WebCore::URL({ }, currentURL()).host().toString());
        if (!domain.isEmpty())
            logDiagnosticMessageWithEnhancedPrivacy(WebCore::DiagnosticLoggingKeys::domainCausingJetsamKey(), domain, WebCore::ShouldSample::No);
    }
#endif

    // There is a nested transaction in resetStateAfterProcessExited() that we don't want to commit before the client call.
    PageLoadState::Transaction transaction = m_pageLoadState.transaction();

    resetStateAfterProcessExited(reason);

    // For bringup of process swapping, NavigationSwap termination will not go out to clients.
    // If it does *during* process swapping, and the client triggers a reload, that causes bizarre WebKit re-entry.
    // FIXME: This might have to change
    if (reason == ProcessTerminationReason::NavigationSwap)
        m_webProcessLifetimeTracker.webPageLeavingWebProcess();
    else {
        navigationState().clearAllNavigations();
        dispatchProcessDidTerminate(reason);
    }

   ......

首先, WebPageProxy::processDidTerminate触发的log都避开了ProcessTerminationReason::NavigationSwap的情况;

其次,当reason为ProcessTerminationReason::NavigationSwap时,并不会执行dispatchProcessDidTerminate,而后者是回调真正处理白屏的逻辑。

所以,本质上这种情形的发生就是为了清空当前webProcessProxy的各种状态,为后续进程交换做好准备而已。

第九、小结

通过上述分析,我们可以知道,iOS设备上造成白屏的真正原因只有两个:

内存使用过度

进程通信机制出现错误

第十、目前主流app对白屏的处理方式

主流浏览器对白屏的处理方式
浏览器名称白屏的处理方式
微信每次发生时均会重新刷新
QQ每次发生时均会重新刷新
Safari第一次会刷新,并给出提示,第二次直接加载错误页,如下图1
京东每次重刷或移除当前页面
微博每次重新刷新
Chrome直接展示错误页面,如下图2

safari对白屏处理的效果图:

                                图1 Safari发生白屏后的处理效果图 

chrome的白屏处理效果:

图2 Chrome的白屏处理示意图

全篇完!

检测iOS中WKWebView白屏问题的方法有以下几种: 1. 检查网络连接:首先要确保设备处于正常的网络环境下。白屏问题有可能是由于网络连接不稳定或断开导致的,可以通过尝试打开其他网页或应用来测试网络连接是否正常。 2. 检查网页URL:检查加载的网页URL是否正确。有时候白屏问题是由于URL输入错误或者失效导致的,可以尝试通过其他方式打开相同的URL来检查是否能够正常加载网页。 3. 检查代理设置:如果设备上启用了代理服务器,可能会干扰WKWebView的正常加载。可以在系统设置中检查代理设置是否正确或者尝试禁用代理来解决问题。 4. 清除缓存和Cookie:有时候缓存或者Cookie的问题会导致WKWebView白屏。可以尝试清除应用的缓存和Cookie来解决问题。可以通过从系统设置中找到当前应用,然后选择清除缓存和Cookie来进行操作。 5. 检查WebView配置:在创建WKWebView实例时,可以通过配置参数来定制WebView的行为。检查WebView的配置是否正确,比如是否启用了JavaScript执行以及是否设置了正确的User Agent等。 6. 检查网页内容:有时候网页内部的错误会导致WKWebView无法正常加载并显示内容。可以尝试在其他设备或者浏览器上加载同一网页,检查是否有错误提示或者加载失败的情况。 通过以上的方法可以尝试解决iOS中WKWebView白屏问题,如果问题仍然存在,可能需要进一步排查代码逻辑或者咨询相关开发者或技术支持人员的帮助。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值