-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathindex.html
More file actions
253 lines (232 loc) · 25.5 KB
/
Copy pathindex.html
File metadata and controls
253 lines (232 loc) · 25.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>项目简介</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="1.illustrate.html" class="active"><strong aria-hidden="true">1.</strong> 项目简介</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="1.1.Design.html"><strong aria-hidden="true">1.1.</strong> 方案设计</a></li><li class="chapter-item expanded "><a href="1.2.Plan.html"><strong aria-hidden="true">1.2.</strong> 开发计划与进展</a></li><li class="chapter-item expanded "><a href="1.3.explain.html"><strong aria-hidden="true">1.3.</strong> 项目分工与目录说明</a></li></ol></li><li class="chapter-item expanded "><a href="2.Bitcomet_Design.html"><strong aria-hidden="true">2.</strong> Bitcomet设计文档</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="2.1.Bitcomet方案概述.html"><strong aria-hidden="true">2.1.</strong> Bitcomet方案分析</a></li><li class="chapter-item expanded "><a href="2.2.Bitcomet方案设计与主要工作.html"><strong aria-hidden="true">2.2.</strong> Bitcomet方案设计与主要工作</a></li></ol></li><li class="chapter-item expanded "><div><strong aria-hidden="true">3.</strong> Bitcomet实现</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="3.1安卓图形架构中的HAL模块.html"><strong aria-hidden="true">3.1.</strong> Bitcomet图形架构HAL模块实现</a></li><li class="chapter-item expanded "><a href="3.2关于Anbox中图形渲染分析与总结.html"><strong aria-hidden="true">3.2.</strong> Bitcomet图形渲染实现</a></li><li class="chapter-item expanded "><a href="3.3Anbox实现分析-容器管理服务.html"><strong aria-hidden="true">3.3.</strong> Anbox分析资料:Anbox容器管理服务</a></li><li class="chapter-item expanded "><a href="3.4.Anbox实现分析-IO模型.html"><strong aria-hidden="true">3.4.</strong> Anbox分析资料:IO 模型</a></li><li class="chapter-item expanded "><a href="3.5.Anbox实现分析-会话管理器与容器管理器的通信.html"><strong aria-hidden="true">3.5.</strong> Anbox分析资料:会话管理器与容器管理器的通信</a></li></ol></li><li class="chapter-item expanded "><a href="4.Bitcomet实验测试.html"><strong aria-hidden="true">4.</strong> Bitcomet实验测试</a></li><li class="chapter-item expanded "><a href="5.run.html"><strong aria-hidden="true">5.</strong> 使用教程</a></li><li class="chapter-item expanded "><div><strong aria-hidden="true">6.</strong> 项目记录</div></li><li><ol class="section"><li class="chapter-item expanded "><a href="6.1.record.html"><strong aria-hidden="true">6.1.</strong> 问题记录与总结</a></li><li class="chapter-item expanded "><a href="6.2.experience.html"><strong aria-hidden="true">6.2.</strong> 比赛收获</a></li></ol></li><li class="chapter-item expanded "><a href="7.Bitcomet未来展望与参考文献.html"><strong aria-hidden="true">7.</strong> Bitcomet未来展望与参考文献</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title"></h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="proj156-一种基于linux系统运行android-11的解决方案"><a class="header" href="#proj156-一种基于linux系统运行android-11的解决方案">proj156 一种基于Linux系统运行Android 11的解决方案</a></h1>
<p>高校队伍:广东东软学院 Bitcomet队</p>
<p>项目成员:邹明燊、田梓汎、张阳彬</p>
<p>指导教师:刘翠莲、罗泉</p>
<h2 id="1-项目简介"><a class="header" href="#1-项目简介">1. 项目简介</a></h2>
<h3 id="11-目标描述"><a class="header" href="#11-目标描述">1.1 目标描述</a></h3>
<p> 本项目目标在现有Anbox实现Android 7在Linux平台正常运行的基础上将其进一步完善,实现Android11在Linux(Ubuntu20.04)平台上的运行,还可根据用户需求下载并正常运行第三方软件。Anbox(Android in a box)是一个基于容器的方法,可以在普通的 GNU/Linux 系统上启动完整的 Android 系统。它将Android应用放进密封的容器中,无需直接访问硬件或数据,所有硬件或数据的访问都是通过与主机上的Anbox守护进程进行的。由于Anbox 直接跑在硬件上,没有软件模拟层,无需虚拟化硬件即可运行 Android,因此可以无缝桥接硬件加速功能。</p>
<p> 本项目主要基于Anbox来参考实现,旨在完成以下四个目标:</p>
<ul>
<li>
<p><strong>目标1:<strong>完成针对Android11的</strong>SDcard的文件系统挂载机制</strong>、<strong>SELinux安全机制、非特权模式下运行处理</strong>对应在Docker容器环境内的处理。</p>
</li>
<li>
<p><strong>目标2:<strong>配置</strong>安卓项目工程</strong>,实现并配置好各个需要的系统模块和AIDL、HIDL、HAL模块,搭建初步调试与测试的Demo1版本,使用adb配合Scrcpy对内部Android远程访问以方便有个初步预期,并且方便本项目有个初步测试的环境。</p>
</li>
<li>
<p><strong>目标3:<strong>完成</strong>Anbox通信部分</strong>实现向Android 11的移植,使Android能与Anbox外部实现跨系统环境通信。</p>
</li>
<li>
<p><strong>目标4:<strong>完成</strong>Anbox其余部分的各个模块移植</strong>,使Android的三个基础部分OpenGL、HWC图形输出、键鼠输入能正常工作。</p>
<p> 目前,我们的赛题目标完成度如下:</p>
<center>表1.1 赛题完成度</center>
<div class="table-wrapper"><table><thead><tr><th style="text-align: center">目标编号</th><th style="text-align: center">基本完成情况</th><th>额外说明</th></tr></thead><tbody>
<tr><td style="text-align: center">1</td><td style="text-align: center">基本完成(≈85%)</td><td>1. 与SDcard以及SELinux组件相关的Android 11的基本功能、SDcard文件管理、安装第三方软件、闹铃与联系人等基础应用测试均通过。<br>2. 非特权模式仅做了部分处理,未进行测试。</td></tr>
<tr><td style="text-align: center">2</td><td style="text-align: center">基本完成(≈90%)</td><td>1. 搭建好相关容器,进行相关基础测试并通过。<br/>2. 目前网络部分由于在Docker特权模式下运行,可能会有Bug。</td></tr>
<tr><td style="text-align: center">3</td><td style="text-align: center">初步完成(≈80%)</td><td>1. 完成跨系统环境通信,安卓中Anbox的相关模块可以使用QEMU_PIPE(实际容器中是没有QEMU设备,Anbox实际用了Unix Domain Socket代替,但其通信的相关通道仍然叫做QEMU PIPE)进行通信或者使用Anbox实现的RPC调用。<br/>2. Anbox的OpenGL ES的emulation库实现初步测试能通过该通信创建QEMU_PIPE连接,实现RPC调用获取到主机侧支持的OpenGL信息。<br/>3. 由于各个部分的模块暂未完全移植好,还需要全部移植才能测试所有功能。</td></tr>
<tr><td style="text-align: center">4</td><td style="text-align: center">初步测试(≈60%)</td><td>1. 已经把相应的Anbox的各个模块的移植到Android中,其中GPS与Audio模块使用了谷歌给Goldfish的实现。<br/>2.OpenGL ES实现和HWC实现在Android 11下无法测试,由于Android 11的相关变动,导致输出画面调用了Gralloc的PostFB去输出,而在Anbox中是未做这部分RPC调用的,输出画面失败。<br/>3.把相关方案在Android 10上进行测试,测试可以输出画面,但卡在安卓Launcher第一屏画面。</td></tr>
<tr><td style="text-align: center">总计</td><td style="text-align: center">≈80%</td><td>1. 本项目自立项以来经历了三个月的时间进行研究与开发,实现方案的过程中遇到困难重重,主要遇到的阻力在系统庞大、相关实现的资料缺失方面。<br />2. 目前还有部分的工作需要测试和完成。由于Android 11在图形和音频方面变动较大,即使目前进行了相关模块的移植,但是还需要在图形模块、音频模块、输入模块方面进行各类测试和调试工作。<br />3. 项目将放在Github进行公开,把截至目前学习的成果公开,希望后续有更多的参与者参与进来进行开发完善。</td></tr>
</tbody></table>
</div></li>
</ul>
<h3 id="12-比赛题目分析和相关资料调研"><a class="header" href="#12-比赛题目分析和相关资料调研">1.2 比赛题目分析和相关资料调研</a></h3>
<h4 id="121-题目分析"><a class="header" href="#121-题目分析">1.2.1 题目分析</a></h4>
<p> (1). 题目旨在实现在Linux运行Android11的解决方案,在Linux上基于容器技术隔离一个环境运行Android,其运行机制是直接基于宿主机内核下运行Android,其运行效率非常高效,对系统资源占用和运算资源的损耗都极低。<br />
(2). 在所有实现方案中,选定了基于Anbox的实现方式。由于现今用户使用的软件硬件环境不统一,Mesa 3D等实现方式对图形处理硬件的支持有限,用户的图形环境不一定是Wayland而是X11偏多。同时考虑到可能未来需要在各类国产主机运行架构不统一(Arm、x86、MIPS、LoongArch等架构),<strong>Anbox对于不同系统不同硬件的兼容性优势使得其是现有方案中最合适的</strong>。<br />
(3). 在实现过程中,考虑到Anbox的LXC也是比较老的容器架构,同时其集成LXC的操作不利于后续容器核心升级、配置调整、在线镜像更新和镜像快速部署等,所以使用Docker来支撑其Android的运行环境,对比最初的LXC方案来说拥有更高的灵活性和可靠性。<br />
(4). 项目计划是初期构建调整好Android高版本核心跑起一个基于Docker容器实现的Demo,后续逐步移植剩余Anbox组件到Android11上。</p>
<h4 id="122-资料调研"><a class="header" href="#122-资料调研">1.2.2 资料调研</a></h4>
<h5 id="现有方案"><a class="header" href="#现有方案">现有方案</a></h5>
<p> (1). Anbox:基于LXC容器运行Android 7,利用QEMU pipe实现Android与Anbox上层Linux软件用户接口进行通信。其通信中输入的数据包括:接收传入的传感器数据,接收用户鼠标点击和触摸事件,接收APP启动和窗口调整数据、Anbox上层软件屏幕缓冲区的可选回传等;通信中输出的数据则包括:传输Android上2D图形画面和OpenGL的渲染指令到Linux环境中渲染、传输音频输出数据。Anbox运行Android的机制的实现方式就是通过将Android内必要的输入输出交给外部Linux环境处理,<strong>其实现接口均通过软件实现,这样对于兼容性容易出现问题的OpenGL加速部分尤为友好</strong>。但是也正因为如此,虽然保证了其兼容性,但是<strong>其支持的OpenGL库选择就非常有限</strong>,目前仅支持OpenGL ESv2,性能和稳定性会较为一般。总结其优点就是对于各类操作系统兼容性好,外部有D-Bus和OpenGL环境基本就能正常运行,缺点是通过QEMU pipe传输到外部渲染的方式,其模拟的库只支持OpenGL ESv2,<strong>稳定性一般,性能也比较逊色</strong>。</p>
<center><img src=images/Anbox.png></center>
<center>图1.1 Anbox实现架构图</center>
<p> (2). Waydroid: 基于Anbox繁衍而来的实现方案,Anbox除了2018年后面稍微更新了下支持Snap和Anbox Cloud外,实现的方案已经多年未进行更新,核心无较大变化,所支持的最新Android系统仍然是2016年11月24日发布的Android 7.1.1。因此Waydroid应运而生,其前身是基于Anbox的中期版本,基于重建脚本、更新的LXC3、Mesa 3D、最新的Android8-11版本、去掉Anbox代码,后续演化后改名Waydroid。Waydroid更加新颖和完善,其优势在于最新的Android、直接通过Mesa 3D驱动显卡、支持Wayland环境;缺点是<strong>只支持Wayland,Mesa 3D作为开源驱动,只支持部分显卡</strong>。</p>
<center><img src=images/Docker_run_Android_with_Mesa3d.png></center>
<center>图1.2 Docker运行Android+Mesa3D架构图</center>
<p> (3). 其他方案:xDroid、Kydroid、KMRE等第三方闭源方案:xDroid和Kydroid也是类似Anbox的方式,其核心原理仍然是通过容器技术让Android直接运行在Linux上,以Linux原生程序运行,由于其闭源,能够了解的公开信息不多。KMRE从其论文<sup class="footnote-reference"><a href="#1">1</a></sup>来看是类似Waydroid的Mesa3D加速的方案,其性能损耗低,但其一大特色优点在于其对接了现在国产系统常见的Xorg环境下的桌面环境,同时做好支持触摸、优化各类输入输出、传感器等的实现,对比以上开源项目,KMRE不但只实现了基本需求,而且在人性化交互等方面,易用性更高。</p>
<center>表1.2 方案对比</center>
<div class="table-wrapper"><table><thead><tr><th>现有方案</th><th>优点</th><th>缺点</th></tr></thead><tbody>
<tr><td>Anbox</td><td>对各类环境兼容性好、原生运行速度快</td><td>稳定性一般、其所支持的Android版本比较老旧、且Android内只支持OpenGL ESv2,图形化API老旧</td></tr>
<tr><td>waydroid</td><td>最新Android、支持Wayland、直接驱动显卡</td><td>只支持Wayland图形环境、Mesa 3D开源驱动只支持部分显卡,兼容性一般</td></tr>
<tr><td>其他闭源方案</td><td>闭源或商用的项目在用户交互等方面做到了更优,易用性更高</td><td>闭源,能查到公开的信息不多</td></tr>
</tbody></table>
</div>
<h5 id="项目需求和状况"><a class="header" href="#项目需求和状况">项目需求和状况</a></h5>
<p> (1). 项目现状:</p>
<p> 由于西方加速技术封锁的态势,我们必须迫切的找到相应的替代方案来给国内用户有选择的权利。虽然国产Linux系统目前在飞速发展,但其应用生态紧缺,甚至一个输入法、Linux版的QQ这种基础性应用都Bug百出,使用体验极其糟糕,导致用户普及度始终不能有明显的上涨,用户也不情愿使用国产Linux作为替代系统。</p>
<p> 因此我们需要一些<strong>中期可替代性方案</strong>,Linux上部署安卓这一个想法应运而生,我们可以利用安卓作为一个暂时的方案来承载现在的业务需求例如办公,视频通话等。安卓在2020年的中国移动操作系统市场份额占比达75.98%,相当于每4个使用智能手机的人中就有3名安卓用户。因其用户整体基数大,所以针对安卓应用开发者非常多,面对安卓延申出来的各种业务层出不穷,整个生态对于国产Linux有很大的优势。</p>
<p> 我们可以直接<strong>借助这个优势</strong>,在Linux上运行安卓系统,完成国产Linux跟安卓生态的整合,进而发展国产Linux,并对其产生助力。而同期也比较少同类型的厂商成功实现在GNU/Linux系统中运行着整个Android系统,市场上有空缺的需求等待填补,我们的项目就是要针对这个市场的空缺,成功实现在Linux系统中运行Android系统而不是单纯的模拟,为我们之后国产Linux和国内安卓的生态整合打下坚实的基础。</p>
<p> (2). 项目需求:</p>
<p> 单纯的Linux<strong>无法</strong>成功运行安卓系统中的各种应用程序,因为安卓在Linux层面上主要增加了ART虚拟机和其Application Framework框架,以及其他细枝末节的修改和优化。我们的项目需求就是需要通过一个安卓<strong>中间层</strong>在Linux上提供安卓运行环境,使安卓当中的应用程序也可以运行在我们所配置的容器当中。但是却不是以模拟器的方式,而是使用当前Linux内核基于<strong>容器技术隔离</strong>来运行安卓系统,不需要大量的软件模拟,又保证了安全性。</p>
<p> 我们选择了这种方案,对各类硬件资源可以被无缝桥接,让性能的效率损失降到比较低的水准,完全可以满足我们日常需求。该方案补充了当前国产Linux应用生态,可以实现国产Linux操作系统应用生态不足的前提下需要快速扩充用户数量的愿景,让国产Linux操作系统走入平民百姓家。</p>
<h2 id="2-参考资料"><a class="header" href="#2-参考资料">2. 参考资料</a></h2>
<ol>
<li>KMRE: An Efficient and Compatible Runtime to Execute Android Application on Linux System, [Date of Conference: 10-13 December 2021]. 10.1109/ICCC54389.2021.9674681</li>
<li>Anbox(所有项目仓库): https://github.com/anbox</li>
<li>安卓AOSP源码获取来自清华大学开源软件镜像站点</li>
</ol>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="next" href="1.1.Design.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="next" href="1.1.Design.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<!-- Livereload script (if served using the cli tool) -->
<script type="text/javascript">
const wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsAddress = wsProtocol + "//" + location.host + "/" + "__livereload";
const socket = new WebSocket(wsAddress);
socket.onmessage = function (event) {
if (event.data === "reload") {
socket.close();
location.reload();
}
};
window.onbeforeunload = function() {
socket.close();
}
</script>
<script type="text/javascript">
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>