1.屏蔽或拦截图片、字体、多媒体等资源的下载

1.1.屏蔽图片和字体的下载

在使用CefBrowserHost::CreateBrowser方法创建Browser时,将CefBrowserSettings参数中的image_loading和remote_fonts设置为STATE_DISABLED。
关键代码段如下:

CefBrowserSettings browserSettings;
browserSettings.image_loading = STATE_DISABLED;
browserSettings.remote_fonts = STATE_DISABLED;
bool bRet = 
CefBrowserHost::CreateBrowser(info,static_cast<CefRefPtr<CefClient>>(pCefClient.get()),csUrl.GetBuffer(),browserSettings, NULL);

1.2.期望达成的目的

  • 1.减少部分存储图片等资源的内存消耗;
  • 2.减少渲染的压力,优化加载速度;

1.3.拦截图片、字体、多媒体的下载
第一步:类CefClientHandler(该类为CefClient子类,可能在各自的项目里不叫这个名字)继承CefRequestHandler
第二步:重写OnBeforeResourceLoad函数:

virtual ReturnValue OnBeforeResourceLoad(
CefRefPtr<CefBrowser> browser,
CefRefPtr<CefFrame> frame,
CefRefPtr<CefRequest> request,
CefRefPtr<CefRequestCallback> callback) override;

第三步:实现OnBeforeResourceLoad函数:

CCefClientHandler::ReturnValue CCefClientHandler::OnBeforeResourceLoad(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefRequest> request,
    CefRefPtr<CefRequestCallback> callback)
{
    //不影响调试台
    HWND hWnd = browser->GetHost()->GetWindowHandle();
    WCHAR wsName[1024] = { 0 };
    ::GetWindowText(hWnd, wsName, 1024);
    if (lstrcmp(wsName, L"DevTools") == 0)
    {
        return RV_CONTINUE;
    }
    //屏蔽图片、字体、多媒体资源
    CefRequest::ResourceType type = request->GetResourceType();
    if (type == RT_IMAGE || type == RT_FONT_RESOURCE || type == RT_MEDIA)
    {
        return RV_CANCEL;
    }
    return RV_CONTINUE;
}

2.拦截css的下载

2.1.css不能用上述方法拦截的原因

直接禁止css的加载会导致js报错,因此不能直接拦截css的加载,而是允许css加载,但在加载过程中将内容替换为空。

2.2.期望达成的目的

  • 1.减少部分存储css的内存消耗;
  • 2.减少渲染的压力,优化加载速度;

2.3.实现css的拦截
第一步:实现一个CefResponseFilter的子类:

class XResourceHandler
    : public CefResponseFilter
{
public:

    XResourceHandler()
    {
    }

    void AddRef() const
    {
    }

    bool Release() const
    {
        return false;
    }

    bool HasOneRef() const
    {
        return false;
    }

    bool InitFilter()
    {
        return true;
    }

    FilterStatus Filter(void* data_in,
        size_t data_in_size,
        size_t& data_in_read,
        void* data_out,
        size_t data_out_size,
        size_t& data_out_written)
    {
        data_in_read = data_in_size;
        data_out_written = 0;
        return RESPONSE_FILTER_DONE;
    }

};

第二步:类CefClientHandler(该类为CefClient子类,可能在各自的项目里不叫这个名字)继承CefRequestHandler
第三步:重写GetResourceResponseFilter方法:

virtual CefRefPtr<CefResponseFilter> GetResourceResponseFilter(
        CefRefPtr<CefBrowser> browser,
        CefRefPtr<CefFrame> frame,
        CefRefPtr<CefRequest> request,
        CefRefPtr<CefResponse> response) override;

第四步:实现GetResourceResponseFilter方法:

CefRefPtr<CefResponseFilter> CCefClientHandler::GetResourceResponseFilter(
    CefRefPtr<CefBrowser> browser,
    CefRefPtr<CefFrame> frame,
    CefRefPtr<CefRequest> request,
    CefRefPtr<CefResponse> response)
{
    //不影响调试台
    HWND hWnd = browser->GetHost()->GetWindowHandle();
    WCHAR wsName[1024] = { 0 };
    ::GetWindowText(hWnd, wsName, 1024);
    if (lstrcmp(wsName, L"DevTools") == 0)
    {
        return CefRequestHandler::GetResourceResponseFilter(browser,frame,request,response);
    }
    //只处理css类的文件
    CefRequest::ResourceType type = request->GetResourceType();
    if (type == RT_STYLESHEET)
    {
        return new XResourceHandler;
    }

    return CefRequestHandler::GetResourceResponseFilter(browser,frame,request,response);
}

*以上图片以及css拦截后,完成全电登录流程的内存从144M降低到了101M,加载速度加快1~2秒

3.渲染优化

3.1.离屏渲染
第一步:CefSettings的windowless_rendering_enabled设置为true
第二步:CefWindowInfo设置为SetAsWindowless
第三步:类CefClientHandler(该类为CefClient子类,可能在各自的项目里不叫这个名字)继承CefRenderHandler
第四步:重写GetViewRect和OnPaint方法:

virtual bool GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect);
virtual void OnPaint(CefRefPtr<CefBrowser> browser,
    PaintElementType type,
    const RectList& dirtyRects,
    const void* buffer,
    int width, int height) override;

第五步:实现GetViewRect和OnPaint方法:

bool CCefClientHandler::GetViewRect(CefRefPtr<CefBrowser> browser, CefRect& rect)
{
    return false;
}

void CCefClientHandler::OnPaint(CefRefPtr<CefBrowser> browser,
    PaintElementType type,
    const RectList& dirtyRects,
    const void* buffer,
    int width, int height)
{
}

3.1.停止渲染
只需将CefWindowInfo设置为SetAsWindowless,页面就会停止渲染,绘制时的cpu可以从15%下降到5%

4.其他优化项

4.1.命令行

  • --disable-gpu:禁用gpu加速
  • --disable-partial-raster:禁用渲染器中的局部光栅
  • --disable-low-res-tiling:该命令行不确定是否起效,官方的描述为:When using CPU rasterizing disable low resolution tiling. This uses less power, particularly during animations, but more white may be seen during fast scrolling especially on slower devices(当使用CPU光栅化时,禁用低分辨率平铺。这将消耗更少的能量,特别是在动画过程中,但在快速滚动时可能会看到更多的白色,特别是在较慢的设备上)。
  • --no-proxy-server:禁用代理,可以极大地提升冷启动的速度。可以读注册表判断有无开启代理,若无则使用此命令。(该命令冷启动能快2~5秒,取决于网络)

4.2.js优化
可以像优化css的方式一样,在js加载过程中,将js内容修改为我们需要的,这一点就需要结合具体场景来处理了。

5.对libcef.dll挂钩子

5.1.挂不同钩子的效果

  • CefRenderWidgetHostViewOSR::ResizeRootLayer : 可以截断绘制,但不影响页面的渲染,内核依旧会对页面进行渲染,并且需要配合SetAsWindowless使用,感觉意义不大。
  • cc::DisplayScheduler::OnBeginFrameDeadline:可以截断绘制,但不影响页面的渲染,内核依旧会对页面进行渲染。
  • base::internal::Invoker<base::IndexSequence<0>, base::internal::BindState<base::internal::RunnableAdapter<void(__thiscall web_cache::WebCacheManager::)(void)>, void __cdecl(web_cache::WebCacheManager ), base::WeakPtr<web_cache::WebCacheManager> >, base::internal::TypeList<base::internal::UnwrapTraits<base::WeakPtr<web_cache::WebCacheManager> > >, base::internal::InvokeHelper<1, void, base::internal::RunnableAdapter<void(__thiscall web_cache::WebCacheManager::*)(void)>, base::internal::TypeList<base::WeakPtr<web_cache::WebCacheManager> const &> >, void __cdecl(void)>::Run : 可以截断渲染,但不影响dom-tree的生成,打开一个需要不停渲染的页面(hetong.nuonuo.com 含有动图),可以看到cpu稳定在0%(原本cpu为15%),且内存也有一定程度的下降(该测试页面下降了10M左右)。
    base::CancelableCallback<void __cdecl(void)>::Forward : 可以截断渲染,但不影响dom-tree的生成,打开一个需要不停渲染的页面(hetong.nuonuo.com 含有动图),可以看到cpu稳定在0%(原本cpu为15%),且内存也有一定程度的下降(该测试页面下降了10M左右)。
  • base::MessageLoop::RunTask : 在此截断,未生成dom-tree,页面无法正常加载
  • blink::ScriptRunner::Task::run : 在此截断,可以减少非渲染部分cpu的30%,并减少内存(该测试页面下降了10M左右)

5.2.选择钩子
这里选择的钩子为:

base::internal::Invoker<base::IndexSequence<0>, base::internal::BindState<base::internal::RunnableAdapter<void(__thiscall web_cache::WebCacheManager::)(void)>, void __cdecl(web_cache::WebCacheManager ), base::WeakPtr<web_cache::WebCacheManager> >, base::internal::TypeList<base::internal::UnwrapTraits<base::WeakPtr<web_cache::WebCacheManager> > >, base::internal::InvokeHelper<1, void, base::internal::RunnableAdapter<void(__thiscall web_cache::WebCacheManager::*)(void)>, base::internal::TypeList<base::WeakPtr<web_cache::WebCacheManager> const &> >, void __cdecl(void)>::Run

blink::ScriptRunner::Task::run

对应的地址:0x100b44d0和0x103bfed0(地址仅对于当前提供的libcef.dll而言,即便是同版本的不同libcef.dll的这个函数的地址也是不同的.

*注意:挂钩子必须谨慎,万一地址挂偏了程序运行的逻辑就不可控了。另外由于对于源码的阅读无法达到完全理解的程度,因此挂钩子也有可能会导致一些意料之外的情况发生。如果出现什么异常的问题,第一时间怀疑钩子,先吧钩子去了跑一下试试。

相应代码如下:

#include <TlHelp32.h>

HMODULE GetProcessModuleHandle(DWORD dwPid, const TCHAR *tsModuleName)
{
    MODULEENTRY32 moduleEntry;
    HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPid);
    if (hSnapshot == NULL) return NULL;
    ::ZeroMemory(&moduleEntry, sizeof(MODULEENTRY32));
    moduleEntry.dwSize = sizeof(MODULEENTRY32);
    if (!Module32First(hSnapshot, &moduleEntry))
    {
        CloseHandle(hSnapshot);
        return NULL;
    }
    do
    {
        if (lstrcmp(moduleEntry.szModule, tsModuleName) == 0)
        {
            return moduleEntry.hModule;
        }
    } while (Module32Next(hSnapshot, &moduleEntry));
    CloseHandle(hSnapshot);
    return NULL;
}

#include "detours.h"
#pragma comment(lib, "detours.lib")

static bool s_bHookSwitch = false;

//file : bind_internal.h
//line : 341
typedef bool(__stdcall *p_Invoker_Void_Void_Run)(void *);
p_Invoker_Void_Void_Run OLD__Invoker_Void_Void_Run = nullptr;
static void NEW__Invoker_Void_Void_Run(void *pBindStateBase)
{
    void *pthis = nullptr;
    __asm mov pthis, ecx;
    if (s_bHookSwitch)
    {
        __asm
        {
            mov eax, [pBindStateBase];
            push eax;
            mov ecx, pthis;
            call OLD__Invoker_Void_Void_Run;
        }
    }
}

//file : ScriptRunner.cpp
//line : 72
typedef void(__stdcall *p_ScriptRunner_Task_run)();
p_ScriptRunner_Task_run OLD__ScriptRunner_Task_run = nullptr;
static void NEW__ScriptRunner_Task_run()
{
    void *pthis = nullptr;
    __asm mov pthis, ecx;
    if (s_bHookSwitch)
    {
        __asm
        {
            mov ecx, pthis;
            call OLD__ScriptRunner_Task_run;
        }
    }
}

void DetoursApiHook()
{
    unsigned char *pLibcefAddress = (unsigned char*)GetProcessModuleHandle(GetCurrentProcessId(), L"libcef.dll");
    OLD__Invoker_Void_Void_Run = (p_Invoker_Void_Void_Run)(pLibcefAddress + 0x100b44d0 - 0x10000000);
    OLD__ScriptRunner_Task_run = (p_ScriptRunner_Task_run)(pLibcefAddress + 0x103bfed0 - 0x10000000);

    DetourRestoreAfterWith();
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());

    DetourAttach(&(PVOID &)OLD__Invoker_Void_Void_Run, NEW__Invoker_Void_Void_Run);
    DetourAttach(&(PVOID &)OLD__ScriptRunner_Task_run, NEW__ScriptRunner_Task_run);

    DetourTransactionCommit();
}

void DetoursApiUnHook()
{
    DetourTransactionBegin();
    DetourUpdateThread(GetCurrentThread());

    DetourDetach(&(PVOID &)OLD__Invoker_Void_Void_Run, NEW__Invoker_Void_Void_Run);
    DetourDetach(&(PVOID &)OLD__ScriptRunner_Task_run, NEW__ScriptRunner_Task_run);

    DetourTransactionCommit();
}

6.页面加载完毕执行document.open()

底层原理
CEF在构建DOM树是,每个元素对应都是一个C++对象,清空DOM树可以触发Chromium内核释放底层内存

事例代码

if (document.getElementById("header-content") != null) {

window.pushGlobal('EtaxSH', JSON.stringify(EtaxSH));
window.CallEnd({ method: "loginByQy", json: EtaxSH.CheckLogin() })
document.open();
return true;

}

经过测试 render进程内存从131M减少到116M


Simple
10 声望4 粉丝

引用和评论

0 条评论