AndroidWebview是一个内嵌于手机上的轻量级浏览器,谷歌的内核提供的AW安卓WebView浏览器是由JAVA,C++混合组成的,一般浏览器的核心功能是CSS渲染引擎和JS语法树的构造和编译执行,还有最终要解析HTML,HTML的数据结构就是一颗DOM树,一般浏览器在解析的时候是,为了提升效率,会边解析边执行,执行的时候开启了多个线程,每个线程只需要负责他们自己的工作。
本文从系统架构,算法,设计模式,技巧等多个维度来分析浏览器内核的各个层面的精髓。
为了软件工程健全性:所有工程都可以拆分为N个逻辑单元,每个逻辑单元都具备Testing特效,这个伟大的工程也是的。所有属性都具备测试性。
拓展下:如何在浏览器上添加AI属性?How to make the browser has the ai abilities?
????????????????????????????????????
一段HTML脚本到WebView容器里的主体处理过程如下
CSS部分和浏览器是一直的,因手机端的环境变化,还需要适配,所以加入了metrics
下图大概描述了整个完成整个JS和CSS的解析->优化->Cache->渲染->绘画->减少重绘->布局->显示整个过程直到显示一个完整的画面的执行过程
RenderTree生成过程
GC也是一个很重要的部分,Chrome有两种回收算法,一种是计数回收,一种是可达性回收,如下图,
计数回收实现主函数在base/memory/ref_counted.h
chrome用了大量的设计技巧。
private:
class Core;
// Calls straight to ThreadLocalStorage::HasBeenDestroyed(). Exposed purely
// for 'friend' to work.
static bool HasThreadLocalStorageBeenDestroyed();
mutable Lock lock_;
mutable std::unique_ptr<Core> core_ GUARDED_BY(lock_);
};
省略
语法树有个网站可以去试试.esprima转换算法:https://esprima.org/demo/parse.html
前端编译构建工具也是用这种语法树转换器
语法树的模板结构是
Program:程序
sourceType:资源类型
varibleDeclaration:成员变量类型
kind:类型 "let var等"
Identifier:标识符号
Literal:识别符号"=,>"
另外还有callTree,结构上也类型,但是CallTree有调用链条
下面这张图是javaScript的语法树模板
比如下面代码
let a=1
let b=2
let c=a+b
转换为语法树的JSON数据结构是
* type: Program
* -body
* +#1
* -#2
* type: VariableDeclaration
* -declarations
* -#1
* type: VariableDeclarator
* -id
* type: Identifier
* name: b
* -init
* type: Literal
* value: 2
* raw: 2
* kind: let
* -#3
* type: VariableDeclaration
* -declarations
* -#1
* type: VariableDeclarator
* -id
* type: Identifier
* name: c
* -init
* type: BinaryExpression
* operator: +
* -left
* type: Identifier
* name: a
* -right
* type: Identifier
* name: b
* kind: let
* sourceType: script
CPU指令集类:## base/*cpu.cc
//cpu处理器信息
struct ProcCpuInfo {
std::string brand;
uint8_t implementer = 0;
uint32_t part_number = 0;
};
intelCpu指令
//
// Currently only XCR0 is defined by Intel so |xcr| should always be zero.
uint64_t xgetbv(uint32_t xcr) {
if defined(COMPILER_MSVC)
return _xgetbv(xcr);
else
uint32_t eax, edx;
asm volatile (
"xgetbv" : "=a"(eax), "=d"(edx) : "c"(xcr));
return (static_cast<uint64_t>(edx) << 32) | eax;
endif // defined(COMPILER_MSVC)
}
ParseProcCpu()获取cpu信息,windows下可以敲入dos命令systeminfo查看
const char kModelNamePrefix[] = "model name";
const char kProcessorPrefix[] = "Processor";
auto model_name = FindFirstProcCpuKey(pairs, kModelNamePrefix);
if (model_name == pairs.end())
model_name = FindFirstProcCpuKey(pairs, kProcessorPrefix);
if (model_name != pairs.end()) {
info.brand =
std::string(TrimWhitespaceASCII(model_name->second, TRIM_ALL));
}
auto implementer_string = FindFirstProcCpuKey(pairs, "CPU implementer");
if (implementer_string != pairs.end()) {
// HexStringToUInt() handles the leading whitespace on the value.
uint32_t implementer;
HexStringToUInt(implementer_string->second, &implementer);
if (!CheckedNumeric<uint32_t>(implementer)
.AssignIfValid(&info.implementer)) {
info.implementer = 0;
}
}
auto part_number_string = FindFirstProcCpuKey(pairs, "CPU part");
if (part_number_string != pairs.end())
HexStringToUInt(part_number_string->second, &info.part_number);
#if defined(ARCH_CPU_X86_FAMILY)
CPU::IntelMicroArchitecture CPU::GetIntelMicroArchitecture() const {
if (has_avx2()) return AVX2;
if (has_fma3()) return FMA3;
if (has_avx()) return AVX;
if (has_sse42()) return SSE42;
if (has_sse41()) return SSE41;
if (has_ssse3()) return SSSE3;
if (has_sse3()) return SSE3;
if (has_sse2()) return SSE2;
if (has_sse()) return SSE;
return PENTIUM;
}
#endif
Android | IOS WebView的Framework组织形式是这样的
Document包含: Animation-Element,Normal-Element,Normal-Tag-List,Tree,eg.
下面的图是直接通过Console.log(document)获取的
优化点2:
base/CityHash 充分调度了并行计算的功能SIMD。
//WebView和WebBrowser两者都是这种驱动模式。如下图所示
当WebView进行工作时主要有四个进程一直在运行处理[绘画,渲染,追踪,内存管理等]
Chrome的核心是Blink和cc,Blink负责解析执行JS脚本,cc负责进程管理,绘画,排版组织等工作。
注意:/chromium/cc/Scheduler下的调度都是在一个scheduler里面循环
包cc下的是负责paint, chromium 中的 ipc(进程间通信) 采用 mojo,使用双向的消息管道,每个终结点都有一个传入消息队列,并在一个终结点上写入消息,有效地将该消息排队到另一个 (对等 ) 终结点上。
通过 mojom 文件采用强类型消息来描述并记录接口。(和protobufs类似)
cc负责构建layerTree并用scheduler在状态机设计模式下进行创建frame并以Task任务形式进行 渲染RenderDomTree工作。
cc/chromium compositor下有个核心类scheduler,这个类是一个调度类,并用状态机来管控。
代码路径是:/cc/scheduler)/scheduler_state_machine.h
有LayerTreeFrame维度,Action纬度,enigineImplFrame维度,重绘状态。由于类别过多,状态也过多,这里只列出一些,
Tips:状态机设计模式能更好地控制事件的生命周期以及每个事件状态触发的前置和后置处理更容易得到保证,
而且更有利于追踪。
enum class LayerTreeFrameSinkState {
NONE,
ACTIVE,
CREATING,
WAITING_FOR_FIRST_COMMIT,
WAITING_FOR_FIRST_ACTIVATION,
};
enum class BeginImplFrameState {
IDLE,
INSIDE_BEGIN_FRAME,
INSIDE_DEADLINE,
};
enum class ForcedRedrawOnTimeoutState {
IDLE,
WAITING_FOR_COMMIT,
WAITING_FOR_ACTIVATION,
WAITING_FOR_DRAW,
};
enum class Action {
NONE,
SEND_BEGIN_MAIN_FRAME,
COMMIT,
POST_COMMIT,
ACTIVATE_SYNC_TREE,
PERFORM_IMPL_SIDE_INVALIDATION,
DRAW_IF_POSSIBLE,
DRAW_FORCED,
DRAW_ABORT,
BEGIN_LAYER_TREE_FRAME_SINK_CREATION,
PREPARE_TILES,
INVALIDATE_LAYER_TREE_FRAME_SINK,
NOTIFY_BEGIN_MAIN_FRAME_NOT_EXPECTED_UNTIL,
NOTIFY_BEGIN_MAIN_FRAME_NOT_EXPECTED_SOON,
};
这里重点讲下Scheduler::ProcessScheduledActions()这个轮询监听方法,这个方法做了渲染,动画帧播放,重画,管控Frame创建任务等事情,帧控制等。定义的Layer,Frame,Action,各种事件的状态就是在这方法里面才起关键作用的,有时间可以画个状态机图,这种模式是典型的状态复合机设计模式。基本上涵盖了浏览器所有进程子事件任务。其执行流程图如下
部分代码如下
action = state_machine_.NextAction();
if (trace_actions_ && action != SchedulerStateMachine::Action::NONE &&
commit_debug_action_sequence_.size() < 40)
commit_debug_action_sequence_.push_back(action);
TRACE_EVENT(TRACE_DISABLED_BY_DEFAULT("cc.debug.scheduler"),
"SchedulerStateMachine", [this](perfetto::EventContext ctx) {
this->AsProtozeroInto(ctx,
ctx.event()->set_cc_scheduler_state());
});
base::AutoReset<SchedulerStateMachine::Action> mark_inside_action(
&inside_action_, action);
switch (action) {
case SchedulerStateMachine::Action::NONE:
break;
case SchedulerStateMachine::Action::SEND_BEGIN_MAIN_FRAME:
compositor_timing_history_->WillBeginMainFrame(begin_main_frame_args_);
compositor_frame_reporting_controller_->WillBeginMainFrame(
begin_main_frame_args_);
state_machine_.WillSendBeginMainFrame();
client_->ScheduledActionSendBeginMainFrame(begin_main_frame_args_);
last_dispatched_begin_main_frame_args_ = begin_main_frame_args_;
break;
case SchedulerStateMachine::Action::POST_COMMIT:
client_->ScheduledActionPostCommit();
state_machine_.DidPostCommit();
break;
case SchedulerStateMachine::Action::ACTIVATE_SYNC_TREE:
compositor_timing_history_->WillActivate();
compositor_frame_reporting_controller_->WillActivate();
state_machine_.WillActivate();
client_->ScheduledActionActivateSyncTree();
compositor_timing_history_->DidActivate();
compositor_frame_reporting_controller_->DidActivate();
last_activate_origin_frame_args_ = last_commit_origin_frame_args_;
break;
case SchedulerStateMachine::Action::PERFORM_IMPL_SIDE_INVALIDATION:
state_machine_.WillPerformImplSideInvalidation();
compositor_timing_history_->WillInvalidateOnImplSide();
compositor_frame_reporting_controller_->WillInvalidateOnImplSide();
client_->ScheduledActionPerformImplSideInvalidation();
break;
省略
}
} while (action != SchedulerStateMachine::Action::NONE);
ScheduleBeginImplFrameDeadline();
PostPendingBeginFrameTask();
StartOrStopBeginFrames();
UpdatePowerModeVote();
WebView自身的重复渲染会做一次移除,在这个过程中会计算整个画面的Weight * Height
对于Paint部分画Div的方式是用到了并查集算法,也就是将一个矩形分解成了1D(直线,每个矩形由4条直线组成哦),转换成axios(2D)后,并查集计算出来的合并面积就是面积的最小覆盖所有区域的DIV,并查集就是并集和叫集的迭代计算。
运算完并查集之后还会根据Y坐标排序。大小是像素表示。最终合并的区域是
FinalArea=Min(area)+[(Max纵坐标)-Min(纵坐标)]*Min(横坐标))
4 7 11 14 18
1 +------+ [4,+1], [11,-1] cov=7 area += 0
2 +----------------+ [1,+1], [4,+1], [11,-1], [18,-1], cov=17, rea += 71
3 | | +------+ | [1,+1], [4,+1], [7,+1], [11,-1], [14,-1], [18,-1], cov=17 area += 171
4 | +------+ | | [1,+1], [7,+1], [14,-1], [18,-1], cov=17 area += 171
5 +----------------+ [7,+1], [14,-1] cov=7 area += 171
6 +------+ [] area += 7*1
具体代码实现该算法如下
public static int calculatePixelsOfCoverage(Rect screenRect, List<Rect> coverageRects) {
if (coverageRects.size() == 0) {
return 0;
}
// Always allocate enough space for all passed rects and never trim allocations as a result
// of clipping
if ((sClippedRects == null 0 : sClippedRects.length) < coverageRects.size()) {
sClippedRects = new Rect[coverageRects.size()];
}
int numClippedRects = 0;
for (int i = 0; i < coverageRects.size(); i++) {
Rect clipRect = coverageRects.get(i);
if (clipRect.intersect(screenRect)) { // This line may modify the value of the passed
// in coverage rects
sClippedRects[numClippedRects++] = clipRect;
}
}
if (numClippedRects == 0) {
return 0;
}
int maxSegments = numClippedRects * 2;
int numVerticalSegments = 0;
if ((sHorizontalSegments == null 0 : sHorizontalSegments.length) < maxSegments) {
sHorizontalSegments = new HorizontalSegment[maxSegments];
sVerticalSegments = new VerticalSegment[maxSegments];
for (int i = 0; i < maxSegments; i++) {
sHorizontalSegments[i] = new HorizontalSegment();
sVerticalSegments[i] = new VerticalSegment();
}
}
for (int i = 0; i < maxSegments; i += 2) {
Rect coverageRect = sClippedRects[i / 2];
sHorizontalSegments[i].set(
coverageRect.left, coverageRect.top, coverageRect.bottom, SegmentType.START);
sHorizontalSegments[i + 1].set(
coverageRect.right, coverageRect.top, coverageRect.bottom, SegmentType.END);
}
Arrays.sort(sHorizontalSegments, 0, maxSegments);
int prev_x = -1;
int coveredPixels = 0;
for (int i = 0; i < maxSegments; i++) {
HorizontalSegment hSegment = sHorizontalSegments[i];
coveredPixels += getCoverageOfVerticalSegments(sVerticalSegments, numVerticalSegments)
* (hSegment.mX - prev_x);
sVerticalSegment1.set(hSegment.mTop, SegmentType.START);
sVerticalSegment2.set(hSegment.mBottom, SegmentType.END);
if (hSegment.mSegmentType == SegmentType.START) {
insertSorted(
sVerticalSegments, numVerticalSegments, sVerticalSegment1, maxSegments);
numVerticalSegments++;
insertSorted(
sVerticalSegments, numVerticalSegments, sVerticalSegment2, maxSegments);
numVerticalSegments++;
} else {
int ret;
ret = deleteElement(sVerticalSegments, numVerticalSegments, sVerticalSegment1);
assert ret != -1;
numVerticalSegments = ret;
ret = deleteElement(sVerticalSegments, numVerticalSegments, sVerticalSegment2);
assert ret != -1;
numVerticalSegments = ret;
}
prev_x = hSegment.mX;
}
return coveredPixels;
}
下图模拟了上面的结果
具体代码如下,感兴趣的可以画图表示下
//更新整个内容尺寸
因为有很多弹窗还有叠加的Frame
void AwRenderViewExt::UpdateContentsSize() {
blink::WebView* webview = GetWebView();
blink::WebFrame* main_frame = webview->MainFrame();
// Even without out-of-process iframes, we now create RemoteFrames for the
// main frame when you navigate cross-process, to create a placeholder in the
// old process. This is necessary to support things like postMessage across
// windows that have references to each other. The RemoteFrame will
// immediately go away if there aren't any active frames left in the old
// process.
if (!main_frame->IsWebLocalFrame())
return;
if (!needs_contents_size_update_)
return;
needs_contents_size_update_ = false;
gfx::Size contents_size = main_frame->ToWebLocalFrame()->DocumentSize();
// Fall back to contentsPreferredMinimumSize if the mainFrame is reporting a
// 0x0 size (this happens during initial load).
if (contents_size.IsEmpty()) {
contents_size = webview->ContentsPreferredMinimumSize();
}
if (contents_size == last_sent_contents_size_)
return;
last_sent_contents_size_ = contents_size;
// Registry for blink::WebView => AwRenderViewExt lookups
typedef std::map<blink::WebView*, AwRenderViewExt*> ViewExtMap;
ViewExtMap* GetViewExtMap() {
static base::NoDestructor<ViewExtMap> map;
return map.get();
}
// Registry for blink::WebView => AwRenderViewExt lookups
typedef std::map<blink::WebView*, AwRenderViewExt*> ViewExtMap;
ViewExtMap* GetViewExtMap() {22
static base::NoDestructor<ViewExtMap> map;
return map.get();
}
AwRenderViewExt::~AwRenderViewExt() {
// Remove myself from the blink::WebView => AwRenderViewExt register. Ideally,
// we'd just use GetWebView() and erase by key. However, by this time the
// GetWebView has already been cleared so we have to iterate over all
// WebViews in the map and wipe the one(s) that point to this
// AwRenderViewExt
auto* map = GetViewExtMap();
auto it = map->begin();
while (it != map->end()) {
if (it->second == this) {
it = map->erase(it);
} else {
++it;
}
}
}
注意:blink内核,内核目录结构在third-part/blink
大概组件是这些
See the ui fragment list as below
WebView核心组件
JSSandbox:沙箱,独立分配内存空间,完全隔离管理
// AdjustToValidHeapSize will either round the provided heap size up to a valid
// allocation page size or clip the value to the maximum supported heap size.
size_t AdjustToValidHeapSize(const size_t heap_size_bytes) {
// This value is not necessarily the same as the system's memory page
// size. https://bugs.chromium.org/p/v8/issues/detail?id=13172#c6
const size_t page_size = GetAllocatePageSize();
const size_t max_supported_heap_size =
size_t{UINT_MAX} / page_size * page_size;
if (heap_size_bytes < max_supported_heap_size) {
return (heap_size_bytes + (page_size - 1)) / page_size * page_size;
} else {
return max_supported_heap_size;
}
}
WebView有两种模式:Dev-Model和Product-Model,谷歌工程师为了迎合开发者的口味设计了Dev模式的frame组成,DEV开发模式下主要由以下几个ui片段组成,组成形式是非内嵌于原生UI模式,也就是说生产者显示模式会不存在。
以原生模式显示,主要是UI元素和部分事件处理上的细微差别
AW做了兜底unit测试,以事件方式通知。体现在lifecycle生命周期中.WebView视为整个组件,组件是由几个元素组成
metrics:自适应机制
UI: redefined by client
A string IDS_MY_STRING can be accessed in Java with org.chromium.android_webview.R.string.MY_STRING.
UI默认的允许客户端执行部分是
#display error page html
IDR_ANDROID_ERROR_PAGE_LOAD_ERROR_HTML
#show about credits(信任)html
IDR_ABOUT_UI_CREDITS_HTML
//过期js文件
IDR_WEBUI_JS_LOAD_TIME_DATA_DEPRECATED_JS
//JS模板文件初始化
IDR_JSTEMPLATE_JSTEMPLATE_COMPILED_JS
//安全化间隙HTML?
IDR_SECURITY_INTERSTITIAL_HTML
//CSS文本默认
IDR_WEBUI_CSS_TEXT_DEFAULTS_CSS
//间隙缓和HTML
IDR_SECURITY_INTERSTITIAL_QUIET_HTML
------------------------------------------------------
| part1:IDR_SAFE_BROWSING_CSS |
| part2:"IDR_SAFE_BROWSING_JS |
| part3:IDR_SAFE_BROWSING_HTML|
------------------------------------------------------
IDR_WEBUI_JS_CR_JS
IDR_WEBUI_JS_ASSERT_TS_JS
IDR_WEBUI_JS_UTIL_TS_JS
//PR.js
IDR_WEBUI_JS_PROMISE_RESOLVER_JS
//进程之间的通讯机制
IDR_MOJO_BINDINGS_JS
//下面这个
//在同种分辨率的情况下,屏幕密度不一样的情况下,自动适配页面:
分别率是:hdpi,mdpi
DisplayMetrics dm = getResources().getDisplayMetrics();
// 获取当前界面的高度
//int width = dm.widthPixels;
//int height = dm.heightPixels;
int scale = dm.densityDpi;
if (scale == 240) {
webView.getSettings().setDefaultZoom(ZoomDensity.FAR);
} else if (scale == 160) {
webView.getSettings().setDefaultZoom(ZoomDensity.MEDIUM);
} else {
webView.getSettings().setDefaultZoom(ZoomDensity.CLOSE);
}
生命周期是,以观察者模式实现
//启动起
void SetUp() override {
observer_ = std::make_unique<TestWebViewAppObserver>();
callback_ = std::make_unique<TestOnLoseForegroundCallback>(observer_.get());
notifier_ = std::make_unique<TestAwContentsLifecycleNotifier>(
//base 包下的绑定重绘,当丢失前景色的时候
base::BindRepeating(&TestOnLoseForegroundCallback::OnLoseForeground,
base::Unretained(callback_.get())));
notifier_->AddObserver(observer_.get());
}
...............................................我是有底线的........................................................................................
AW为了性能出发,从工程角度调度了所有的底层资源,Android操作系统,GPU加速器,JNI,即时编译,Worker,多线程处理等技术,调度这块合理化了Scheduler的工作。
V8引擎内核:负责渲染CSS,Paint Webew,调度操作系统处理绘画工作,任务处理工作
JSSandBox:js沙箱盒子,独自分配了堆空间,手机端可以设置运行期间内存的消耗上限。maxByteSize.
并把JS当做一个service来管理,大概率可以演化成一个process,可以进行单独的close-reopen操作,
JSB设置了隔离化,并安置了callback事件,Callback interface for the native code to report a JavaScript evaluation outcome.
JNI:调用Android操作系统的C++实现的method
Render:渲染工作,WebView自己负责了一部分渲染工作、
桥接模式部分:纯JAVA实现,大概实现了以下桥接。桥接模式在获取设备权限场景下的交互是由Android客户端获取权限,也可以由H5获取。列举下清单
1.Full-screen-view:全屏浏览模式
2.Safemodel-view-webrowser:安全模式浏览
3.Get-geo-location-privilege:获取地理坐标权限
4.ZoomInOut-webview:缩放、
5.获取各种交互设备的权限(摄像头,蓝牙功能,视音频功能等)
6.其他
[图片上传中...(image.png-ec2a64-1675482512345-0)]
JS-Interaction-With-JAVA(JS和JAVA交互机制)这里实际只只是为了内存管理。实际上就是JS 和JAVA交互的时候都是转换为引用了本地对象。
See below C++ code
JsReplyProxy::JsReplyProxy(js_injection::WebMessageReplyProxy* reply_proxy)
: reply_proxy_(reply_proxy) {
//android主线程
JNIEnv* env = base::android::AttachCurrentThread();
java_ref_.Reset(
Java_JsReplyProxy_create(env, reinterpret_cast<intptr_t>(this)));
}
//重新定义成员函数的虚函数~
JsReplyProxy::~JsReplyProxy() {
if (!java_ref_)
return;
//
JNIEnv* env = base::android::AttachCurrentThread();
Java_JsReplyProxy_onDestroy(env, java_ref_);
}
base::android::ScopedJavaLocalRef<jobject> JsReplyProxy::GetJavaPeer() {
return base::android::ScopedJavaLocalRef<jobject>(java_ref_);
}
void JsReplyProxy::PostMessage(
JNIEnv* env,
const base::android::JavaParamRef<jobject>& payload) {
reply_proxy_->PostWebMessage(
content::android::ConvertToWebMessagePayloadFromJava(
base::android::ScopedJavaLocalRef<jobject>(payload)));
}
分析下media drm 视音频数字版权,Licence,设计上是为了尊重版权设计不接受低成本侵权而加入的功能。
media::MediaDrmBridgeClient::KeySystemUuidMap::value_type
CreateMappingFromString(const std::string& key_system_uuid_mapping) {
std::vector<uint8_t> uuid;
//分割token
std::vector<std::string> tokens =
base::SplitString(key_system_uuid_mapping, ",", base::KEEP_WHITESPACE,
base::SPLIT_WANT_NONEMPTY);
RCHECK(tokens.size() == 2);
std::string key_system;
base::TrimWhitespaceASCII(tokens[0], base::TRIM_ALL, &key_system);
//获取第二段为guid 36G个uinicode编码长度
std::string guid(tokens[1]);
RCHECK(guid.length() == kGUIDLength);
//替换掉"-"
base::RemoveChars(guid, "-", &guid);
RCHECK(base::HexStringToBytes(guid, &uuid));
//键值对
return std::make_pair(key_system, uuid);
}
} // namespace
//桥梁模式
AwMediaDrmBridgeClient::AwMediaDrmBridgeClient(
const std::vector<std::string>& key_system_uuid_mappings)
: key_system_uuid_mappings_(key_system_uuid_mappings) {}
AwMediaDrmBridgeClient::~AwMediaDrmBridgeClient() {}
//加载至map里面
void AwMediaDrmBridgeClient::AddKeySystemUUIDMappings(KeySystemUuidMap* map) {
for (const std::string& key_system_uuid_mapping : key_system_uuid_mappings_) {
auto mapping = CreateMappingFromString(key_system_uuid_mapping);
if (!mapping.first.empty())
map->insert(mapping);
}
}
小属性1:直接打开来源于安卓设备上的文件显示在WebView上,说明本人是阅读过源码的
gpu部分
当Browser端的GPU通道处于PREEMPTING状态时,Browser端的OpenGL上下文就可以要求其它OpenGL上下文停止执行手头上的任务,以便将GPU线程交出来运行Browser端的OpenGL上下文。
下图来源于其他博客,这个描述的比较清楚.GPU的运行在Chrome中是有一个概念上的OpenGL上下文的。
//异步瞄点管理器回调执行
gpu::SyncPointManager* AwContentGpuClient::GetSyncPointManager() {
return sync_point_manager_callback_.Run();
}
//OpenGL共享视图回调执行器,防止重绘
gpu::SharedImageManager* AwContentGpuClient::GetSharedImageManager() {
return shared_image_manager_callback_.Run();
}
gpu::Scheduler* AwContentGpuClient::GetScheduler() {
return scheduler_callback_.Run();
}
viz::VizCompositorThreadRunner*
AwContentGpuClient::GetVizCompositorThreadRunner() {
return viz_compositor_thread_runner_callback_.Run();
}
DBUS 数据总线
在D-Bus中,“bus”是核心的概念,它是一个通道:不同的程序可以通过这个通道做些操作,比如方法调用、发送信号和监听特定的信号。在一台机器上总线守护有多个实例(instance)。这些总线之间都是相互独立的。Dbus有多个总线,每个总线对应一个进程,他们之间的资源是隔离的。Dbus是基于TCP协议簇传播的。
下面这个图大概画了一下工作流程:
//下面展示了通过messager 输入流管道将不同的类型转换为String
//如果是个method 相当于把整个类的元数据传递过去了
property有如下状态
template class Property<uint8_t>;
template class Property<bool>;
template class Property<int16_t>;
template class Property<uint16_t>;
template class Property<int32_t>;
template class Property<uint32_t>;
template class Property<int64_t>;
template class Property<uint64_t>;
template class Property<double>;
template class Property<std::string>;
template class Property<ObjectPath>;
template class Property<std::vector<std::string>>;
//ObjectPath放在vector容器中
template class Property<std::vector<ObjectPath>>;
template class Property<std::vector<uint8_t>>;
template class Property<std::map<std::string, std::string>>;
template class Property<std::vector<std::pair<std::vector<uint8_t>, uint16_t>>>;
template class Property<std::map<std::string, std::vector<uint8_t>>>;
template class Property<std::map<uint16_t, std::vector<uint8_t>>>;
std::string Message::ToString() {
if (!raw_message_)
return std::string();
// Generate headers first.
std::string headers;
AppendStringHeader("message_type", GetMessageTypeAsString(), &headers);
AppendStringHeader("destination", GetDestination(), &headers);
AppendStringHeader("path", GetPath().value(), &headers);
AppendStringHeader("interface", GetInterface(), &headers);
AppendStringHeader("member", GetMember(), &headers);
AppendStringHeader("error_name", GetErrorName(), &headers);
AppendStringHeader("sender", GetSender(), &headers);
AppendStringHeader("signature", GetSignature(), &headers);
AppendUint32Header("serial", GetSerial(), &headers);
AppendUint32Header("reply_serial", GetReplySerial(), &headers);
// Generate the payload.
MessageReader reader(this);
return headers + "\n" + ToStringInternal(std::string(), &reader);
}
std::string Message::ToStringInternal(const std::string& indent,
MessageReader* reader) {
const char* kBrokenMessage = "[broken message]";
std::string output;
while (reader->HasMoreData()) {
const DataType type = reader->GetDataType();
switch (type) {
case BYTE: {
uint8_t value = 0;
if (!reader->PopByte(&value))
return kBrokenMessage;
output += indent + "byte " + base::NumberToString(value) + "\n";
break;
}
case BOOL: {
bool value = false;
if (!reader->PopBool(&value))
return kBrokenMessage;
output += indent + "bool " + (value "true" : "false") + "\n";
break;
}
case INT16: {
int16_t value = 0;
if (!reader->PopInt16(&value))
return kBrokenMessage;
output += indent + "int16_t " + base::NumberToString(value) + "\n";
break;
}
case UINT16: {
uint16_t value = 0;
if (!reader->PopUint16(&value))
return kBrokenMessage;
output += indent + "uint16_t " + base::NumberToString(value) + "\n";
break;
}
case INT32: {
int32_t value = 0;
if (!reader->PopInt32(&value))
return kBrokenMessage;
output += indent + "int32_t " + base::NumberToString(value) + "\n";
break;
}
case UINT32: {
uint32_t value = 0;
if (!reader->PopUint32(&value))
return kBrokenMessage;
output += indent + "uint32_t " + base::NumberToString(value) + "\n";
break;
}
case INT64: {
int64_t value = 0;
if (!reader->PopInt64(&value))
return kBrokenMessage;
output += (indent + "int64_t " + base::NumberToString(value) + "\n");
break;
}
case UINT64: {
uint64_t value = 0;
if (!reader->PopUint64(&value))
return kBrokenMessage;
output += (indent + "uint64_t " + base::NumberToString(value) + "\n");
break;
}
case DOUBLE: {
double value = 0;
if (!reader->PopDouble(&value))
return kBrokenMessage;
output += indent + "double " + base::NumberToString(value) + "\n";
break;
}
case STRING: {
std::string value;
if (!reader->PopString(&value))
return kBrokenMessage;
// Truncate if the string is longer than the limit.
const size_t kTruncateLength = 100;
if (value.size() < kTruncateLength) {
output += indent + "string \"" + value + "\"\n";
} else {
std::string truncated;
base::TruncateUTF8ToByteSize(value, kTruncateLength, &truncated);
base::StringAppendF(&truncated, "... (%" PRIuS " bytes in total)",
value.size());
output += indent + "string \"" + truncated + "\"\n";
}
break;
}
case OBJECT_PATH: {
ObjectPath value;
if (!reader->PopObjectPath(&value))
return kBrokenMessage;
output += indent + "object_path \"" + value.value() + "\"\n";
break;
}
case ARRAY: {
MessageReader sub_reader(this);
if (!reader->PopArray(&sub_reader))
return kBrokenMessage;
output += indent + "array [\n";
output += ToStringInternal(indent + " ", &sub_reader);
output += indent + "]\n";
break;
}
case STRUCT: {
MessageReader sub_reader(this);
if (!reader->PopStruct(&sub_reader))
return kBrokenMessage;
output += indent + "struct {\n";
output += ToStringInternal(indent + " ", &sub_reader);
output += indent + "}\n";
break;
}
case DICT_ENTRY: {
MessageReader sub_reader(this);
if (!reader->PopDictEntry(&sub_reader))
return kBrokenMessage;
output += indent + "dict entry {\n";
output += ToStringInternal(indent + " ", &sub_reader);
output += indent + "}\n";
break;
}
case VARIANT: {
MessageReader sub_reader(this);
if (!reader->PopVariant(&sub_reader))
return kBrokenMessage;
output += indent + "variant ";
output += ToStringInternal(indent + " ", &sub_reader);
break;
}
case UNIX_FD: {
CHECK(IsDBusTypeUnixFdSupported());
base::ScopedFD file_descriptor;
if (!reader->PopFileDescriptor(&file_descriptor))
return kBrokenMessage;
output +=
indent + "fd#" + base::NumberToString(file_descriptor.get()) + "\n";
break;
}
default:
LOG(FATAL) << "Unknown type: " << type;
}
}
return output;
}
一个持久的系统总线(system bus):
它在引导时就会启动。这个总线由操作系统和后台进程使用,安全性非常好,以使得任意的应用程序不能欺骗系统事件。它是桌面会话和操作系统的通信,这里操作系统一般而言包括内核和系统守护进程。这种通道的最常用的方面就是发送系统消息,比如:插入一个新的存储设备;有新的网络连接;等等。
还将有很多会话总线(session buses):
这些总线当用户登录后启动,属于那个用户私有。它是用户的应用程序用来通信的一个会话总线。同一个桌面会话中两个桌面应用程序的通信,可使得桌面会话作为整体集成在一起以解决进程生命周期的相关问题.
前端编码优化原则导引
编写正确的HTML 代码,浏览器在html解释的时候,遇到错误标签,会启动容错机制,开发者应当规避这些错误。
css优先,css优先于js引入,因为渲染树需要拿到DOM树和CSS规则,而js会停止DOM树的构建。
可以用媒体查询(media query)加载css,来解除对渲染的阻塞,这样,只有当出现符合media的情况时,才会加载该资源。
尽量不要使用css import 来加载css,@import无论写在哪一行,都会在页面加载完再加载css
优化css选择器。浏览器在处理选择器时依照从右到左的原则,因此最右端的选择器应该是优先级最高的,比如 div > span.test 优于 div span。 两个原因,一是 .test 比 span更准确,二是,浏览器看到 > span.test 会去找 div 的子元素,而不加大于号,则会寻找全局的span标签。
减少重绘重排
当你需要修改DOM节点样式时,不要一条一条改n次,直接定义好样式,修改css类即可,尽管chrome做了优化,并不会真的重绘/重排n次,但是不不能保证你没有强制重绘的代码破坏这个机制,更何况,作为开发者,应当有意识编写高质量代码
将多次对DOM的修改合并。或者,你先把它从渲染树移除(display:none),这会重排一次,然后你想做什么做什么
当需要频繁获取元素位置等信息时,可先缓存
不要使用table布局
transform和opacity属性只会引起合成,所以写css动画的时候,注意两个属性的使用,尽量只开启GPU合成,而不重绘重排。
必要时使用函数防抖
//计时器执行,如果频繁调用函数本身就会执行一次抖动一次。抖动的原因是函数栈每执行一次就会重绘一次,这个函数的功能是在页面上显示计数器
function debounce(func, wait, immediate) {
let timer;
return function() {
let context = this,
args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timer = setTimeout(() => {
func.apply(context, args);
}, wait)
}
}
. 防止js阻塞页面,将script标签放在</body>前面,或者使用defer****async 属性加载
. 文件大小和文件数量做好平衡,不要因为数量太多,大大超过了浏览器可并行下载的资源数量,要不要因为文件太大,提高了单一资源加载的时间