当前位置: BOLT界面引擎 > 知识库文章 > 界面引擎 XML文件格式以及XAR包介绍

界面引擎 XML文件格式以及XAR包介绍

作者:hansom
术语:
XLUE:Xunlei UIEngine,迅雷UI引擎,代号BOLT
XAR: Xunlei Archive,一种迅雷自定义文件格式。
标准对象: XLUE引擎内部实现的基础对象类,以及从它派生的各种对象类。
控件: XLUE引擎支持的一种由用户在外部通过XML和lua来配置的复合对象,一个控件
可以由多个标准对象或者控件组成。
对象树:XLUE引擎内置的一种数据结构,用于管理标准对象以及控件
 
XLUE XAR包文件结构介绍
XAR包的设计目的主要是用来实现模块化,方便代码重用,一般使用XLUE引擎的应用程序,一个XAR包就够用了。但是对于复杂的应用程序,比如支持插件扩展的应用程序,一个XAR就不够用了,需要多个XAR一起协同工作。XAR提供了一种引用包含的机制,一个XAR包可以包含另外一个XAR包,但是不能互相包含。即A包中包含了B包,那么B包中就不能再包含A包。因此这时我们可以将主程序里面用到的XAR包作为主包,插件里面用到的XAR包作为附属XAR包,然后插件包包含主XAR包,使用其中提供的类或者注册的全局函数,以及资源文件。
 
XAR包文件结构如下:
1. package.cfg
本文件主要是用来描述XAR包的一些信息,包括包名,作者,版权所有者等,另外还指明nametable.cfg文件的路径,主资源包名,以及启动脚本路径,除此之外还可以指明该XAR包包含了哪个XAR包。如果指定了包含的XAR,那么加载该XAR包的时候也会去加载包含的XAR,除非包含的XAR已经被加载过。因此,该XAR包可以使用包含的XAR包中的控件,资源等信息。
 
2. nametable.cfg(可选,用户可以自定义名字)
本文件主要用来描述layout包里面定义的控件和模板的位置,便于查找。该文件由XLUE编译工具生成。
 
3. layout(布局XML及LUA脚本文件目录)(可选)
 本目录主要由xml文件和lua脚本构成,也可能是打包过的.xar格式的文件,其中包括对象树模板,对象模板,宿主窗口模板,动画模板的定义(这里要特别注意,定义的是模板,并非模板的实例),并描述他们的属性,方法以及事件响应。并且其中包含所有用户自定义的控件,所有的用户自定义控件由一个xml文件以及一个lua文件构成,xml文件中定义控件的属性,方法,以及事件对应的响应函数,lua脚本中是这些方法和响应函数的实现。XML文件中定义的模板和控件都是静态的配置信息,只有在通过lua脚本创建控件或者创建模板实例的时候才会去获取并解析其中的XML节点配置信息,解析的过程中会创建出来相应类型的模板或者控件实例
 
4. res(资源包集合目录) (可选)
   本目录主要由资源包构成,里面可能是一个个的zip格式的资源包,也可能只是文件夹格式的资源包。资源包的构成如下:
1) resource.cfg
本文件描述资源包的信息,包括包名,作者,版权所有者这些信息,还可能会指定父资源包。当一个资源的ID在本包里找不到对应的资源时,会尝试到父包中查找。
2) bitmap(图片等资源文件的存放目录,用户可以自定义名字)
    本文件夹存放图片文件,只能是PNG格式的图片。
3) bitmap.xml(可选,用户可以自定义名字)
4) color.xml(可选,用户可以自定义名字)
5) font.xml(可选,用户可以自定义名字)
上面这三个XML不是必须有的,只是为了分类存放方便,用户也可以把所有的资源定义放在一个自己命名的xml中。
6) onload.lua(可选,用户可以自定义名字)
   本文件在资源包被加载或者资源包切换的时候被加载,于加载资源之前执行,如果需要进行一些额外的处理可以加上需要的lua代码
7) onunload.lua(可选,用户可以自定义名字)
                  本文件在资源包被加载或者资源包切换的时候被加载,于释放资源之前执行,
如果需要进行一些额外的处理可以加上需要的lua代码
5. onload.lua(可选,用户可以自定义名字)
本文件是XAR中的驱动,XAR被加载时发现其中有这个文件,就会去加载并运行它,通过它来将layout目录里面定义的控件和res目录里面定义的资源展示到程序界面中。这个文件标明为可选,因为没有这个文件程序不会报错,但对于功能完整的XAR包这个是必须的
 
可以正常工作,功能完整的XAR包,有以下几种配置方式:
1.最简单XAR包
1) package.cfg
2) onload.lua
 
这种方式配置的XAR包,本身不带有资源或者布局,依赖其他包里面提供的
资源,常用于逻辑较为简单,功能单一的插件
 
2. 较复杂的XAR包
1) package.cfg
2) res
3) onload.lua
这种方式配置的XAR包,和上面那种相比,多了资源包目录, 自己有资源
包,一般用于自带皮肤,然后使用主程序中默认控件布局的插件。
 
 
3. 最复杂的XAR包
1)   package.cfg
2)   nametable.cfg
3)   layout
4)   res
5)   onload.lua
这种方式配置的XAR包,带有资源和布局文件目录,而且还有nametable.cfg
配置文件,这个文件主要用于查找模板或者控件。可以是一个独立的包,不依赖其他包,也可是依赖其他包。程序中的主XAR包一般都是这种结构。
 
XLUE 布局XML文件结构
上面主要介绍了XAR包的构成,以及包内各文件的用途,其中讲到layout包中主要是一些模板的定义,还有用户自定义的控件。那么这些XML的格式又是怎样的呢? 下面我将详细介绍下layout包里的XML文件的格式,至于每个XML节点对应的属性,以及属性值描述可以参考下附录。
首先,一个应用程序应该有一个主窗口,因此我们需要定义一个主窗口的模板,注意是模板并在其中定义一下这个窗口模板的一些属性。
主窗口的XML结构如下:
<xlue>
    <hostwndtemplate>   <!—宿主窗口模板-->
       …
    </hostwndtemplate>
    <objtreetemplate>  <!—对象树模板-->
       …
</objtreetemplate>
    <animationtemplate> <!—动画模板,可选项-->
       …
    </animationtemplate>
    <objtemplate>         <!—对象模板,可选项-->
       …
     </objtemplate>
</xlue>
 
示例如下:
<!—filename: Mainwnd.xml-->
<xlue>
<hostwndtemplate id=”MainWnd” class=”FrameHostWnd”>
 <attr>
       <left>0</left>
       <top>0</top>
       <width>1280</width>
       <height>800</height>
        …
 </attr>
<eventlist>
     <event name=”OnCreate” file=”MainWnd.xml.lua”
func=” OnCreate” />
       …
</eventlist>
</hostwndtemplate>
</xlue>
 
上面定义了一个主窗口模板,id为”MainWnd”,类型为”FrameHostWnd”,表示这个窗口是一个常用的主窗口类型,可以带有标题栏,系统菜单等属性。属性列表里面指明了窗口原始的位置,左上顶点坐标(0,0),宽度高度分别为1280,800等其他信息。 现在主窗口定义好了,我们需要把控件绘制到主窗口上,而我们的控件都是挂载在对象树上的,因此,我们得先定义一个对象树模板,示例如下:
<!—filename: Mainwnd.xml-->
<xlue>
   …
<objtreetemplate id="MainWndTree" class="ObjectTreeTemplate">
       <attr>
           <left>-200</left>
           <top>-200</top>
           <width>2000</width>
           <height>2000</height>
       </attr>
       <obj id="MainWnd.RootLayout" class="LayoutObject">
           <attr>
              <left>0</left>
              <top>0</top>
              <width>1280</width>
              <height>800</height>
           </attr>
           <children>
              <obj id=”MyEditControl” class=”MyEdit”>
                  <attr>
                     <Width>100</Width>
<Text>编辑框默认显示文字</Text>
                  </attr>
              </obj>
           </children>
           <eventlist>
              <event name=”OnMouseMove” file=”MainWnd.xml.lua”
func=”OnMouseMove”/>
           </eventlist>
       </obj>
</objtreetemplate>
</xlue>
 
上面定义了一个对象树模板,id为”MainWndTree”,类型为”ObjectTreeTemplate”,里面包含一个根对象”MainWnd.RootLayout”,所有的子标准对象或者子控件都可以挂在根对象的<children></children>中。标准对象的XML结构比较简单,上面的例子就可以看出来,这里不做详细介绍,另外标准对象模板的结构与标准对象一致。上面的示例中,根对象下挂了一个名为MyEditControl的控件对象,下面以该控件为例介绍下控件的XML结构。
控件的XML结构如下:
   <!—filename: MyEdit.xml -->
<xlue>
<control class=" MyEdit ">
        <attr_def>   <!— 自定义属性列表,可选项-->
           …
        </attr_def>
       <method_def>  <!— 自定义方法列表,可选项-->
           …
        </method_def>
           …
        <event_def>  <!— 自定义事件列表,可选项-->
           …
       </event_def>
        <objtemplate> <!—控件根对象模板定义-->
           …
       </objtemplate>
    </control>
   
    <hostwndtemplate>   <!—宿主窗口模板,可选项-->
       …
    </hostwndtemplate>
    <objtreetemplate>   <!—对象树模板,可选项-->
       …
</objtreetemplate>
    <animationtemplate> <!—动画模板,可选项-->
       …
    </animationtemplate>
    <objtemplate>         <!—对象模板,可选项-->
       …
     </objtemplate>
     …
</xlue>
 
上面的结构图中可以看出控件XML的大致结构了。下面给一个较完整的控件的XML示例
 
<!-- filename: MyEdit.xml>
<xlue>
 <control class="MyEdit">
        <attr_def>
            <attr name="NormalBkgID" type="string">
                <default>texture.edit.bkg.normal</default>
            </attr>
          
           <attr name="Text" type="string" />
            <attr name="Width" type="int" >
                <default>50</default>
            </attr>
            <attr name="ReadOnly" type="bool">
                <default>false</default>
            </attr>
           ...
        </attr_def>
        <method_def>
            <SetReadOnly file="MyEdit.xml.lua" func="SetReadOnly" />
         <GetReadOnly file="MyEdit.xml.lua" func="GetReadOnly" />
 
         <SetText file="MyEdit.xml.lua" func="SetText" />
         <GetText file="MyEdit.xml.lua" func="GetText" />
           ...
        </method_def>
        <event_def>
            <OnEditChange>
                <param>
                    <string/>
                </param>
            </OnEditChange>
      
           <OnEditKeyDown>
              <param>
                  <int/>
                  <int/>
                  <int/>
              </param>
           </OnEditKeyDown>
           ...
        </event_def>
        <objtemplate>
            <children>
                <obj id="newedit.bkg" class="TextureObject">
                    <attr>
                        <left>0</left>
                        <top>0</top>
                        <width>father.width</width>
                        <height>father.height</height>
                   </attr>
                    <children>
                     <obj id="edit.limit" class="LayoutObject">
                         <attr>
                            <left>0</left>
                            <top>0</top>
                            <width>father.width</width>
                            <height>father.height</height>
                            <limitchild>1</limitchild>
                         </attr>
                         <children>
                               <obj id="newedit.edit"
class="EditObject">
                                <attr>
                                   <left>0</left>
                                   <top>0</top>
                                   <width>father.width</width>
                                           <height>father.height
</height>
                                   <zorder>1</zorder>
                                   <transparent>1</transparent>
                                </attr>
                                <children>
                                </children>
                                <eventlist>
                                        <event name="OnChange"
file="MyEdit.xml.lua" func="Edit__OnChange"/>
                                   <event name="OnKeyDown"
 file="MyEdit.xml.lua" func="Edit_OnKeyDown"/>
                                   ...
                                </eventlist>
                            </obj>
                         </children>
                     </obj>
                    </children>
                  <eventlist>
                   <event name="OnMouseMove"
file="MyEdit.xml.lua" func="OnMouseMove"/>
                   <event name="OnMouseLeave"
file="MyEdit.xml.lua" func="OnMouseLeave"/>
                   <event name="OnFocusChange"
file="MyEdit.xml.lua" func="OnFocusChange"/>
                     ...
                  </eventlist>
                </obj>
            </children>
            <eventlist>
                <event name="OnBind" file="MyEdit.xml.lua"
func="OnBind" />
                <event name="OnInitControl" file="MyEdit.xml.lua"
func="OnInitControl"/>
              <event name="OnFocusChange" file="MyEdit.xml.lua"
 func="Control_OnFocusChange"/>
              ...
            </eventlist>
        </objtemplate>
    </control>
<xlue>
 
 
上面这个例子中就是一个完整控件的实现,由于篇幅限制,我省略了许多内容,但是从上面的描述中,你可以大致知道一个控件的XML大致是什么结构了。使用Lua脚本就可以很简单的使用主窗口模板创建出主窗口实例,然后,使用对象树模板创建对象树实例,然后将对象树实例绑定到主窗口实例上,然后使用主窗口实例创建一个窗口,并把它显示出来。这样,我们就创建并显示了一个带有简单编辑框的主窗口。lua脚本示例如下:
local templateMananger =
XLGetObject("Xunlei.UIEngine.TemplateManager")
local frameHostWndTemplate =
templateMananger:GetTemplate("MainWnd","HostWndTemplate")
if frameHostWndTemplate then 
    local frameHostWnd =
frameHostWndTemplate:CreateInstance("MainFrame")
    if frameHostWnd then
       local objectTreeTemplate =
templateMananger:GetTemplate("MainWndTree",
"ObjectTreeTemplate")
       if objectTreeTemplate then
           local uiObjectTree =
objectTreeTemplate:CreateInstance("MainObjectTree")
           if uiObjectTree then
              frameHostWnd:BindUIObjectTree(uiObjectTree)
              frameHostWnd:Create()
           end
       end
    end
end
 
虽然做控件说起来简单,做起来就没有那么简单了,需要了解许多XLUE引擎内置对象的类型,属性,事件等。附录中的文件中对这些有很详细的介绍。经常使用引擎,就会熟能生巧,实现自己想要的控件就没那么难了。等到你可以熟练使用引擎开发控件,从此你就可以告别 MFC,WTL等大块头了。
 
 
 
XLUE XAR相关介绍
1. XML与XML以及XML与lua之间的联系
1) XML与XML之间的联系
一个XML里面定义的控件,可以在另外一个XML中使用,甚至一个XAR包中定义的控件,可以在另外一个XAR包中使用,这是怎么做到的呢?答案是通过查找nametable.cfg文件。当我们在XML中使用了一个在其他的XAR中定义的控件时,要保证这个XAR之前已经被加载过,引擎的XML解析模块在解析该控件时,会通过查找模块在当前所有已加载的XAR包中查找这个控件的定义,首先查找XAR包中的nametable.cfg文件,找到对应的控件所在的XML位置,如果找到则加载这个XML,如果找不到则继续下一个,直到查找完所有已加载的XAR如果找到对应的控件,并创建该控件的一个实例。但是如果包含该控件的XAR没有被加载起来,也会出现找不到的时候,而使用XAR包含是解决这个问题的一个比较好的方法,在当前XAR中包含含有待使用控件的XAR包。如果碰到有多个地方定义了同一个控件的情况,可以通过加XAR包名字空间来解决冲突的问题,假设当前在AddinXAR中声明一个在MainXAR中定义的控件,那么可以按照<obj id=”edit1” class= ”MainXAR::MyEdit”/>这样的方式来做,其中MainXAR是包名,MyEdit是其中定义的控件名。同样,正常使用这个控件,需要MainXAR包之前被加载起来,如果没有,你也可以在lua脚本加载插件XAR之前,手动加载MainXAR。XAR重复加载不会导致问题,内部会进行判断,因此不必担心重复加载的问题。
 
2) XML与lua之间的联系
XML里面定义的控件属性在lua脚本中怎么获取到的呢,事件,方法等又是怎么响应的呢?下面我来给大家讲解下,引擎内部在实现自定义控件的时候,在内部注册了控件的GetAttribute以及SetAttribute方法,这两个方法都可以在lua脚本中直接使用。其中GetAttribute方法用于获取控件XML中定义的<attr_def></attr_def>属性列表,SetAttribute方法用于设置新的属性列表,这个方法一般很少会用到。XML中定义的<method_def></method_def>中的方法,会在使用的时候来查找方法名节点的属性值,lua文件名以及lua函数名来确定lua函数的位置,当在一个lua文件中调用另外的lua文件中定义的函数时,这个功能将起到作用,因为lua并不能跨文件使用变量或者函数,除非在全局表中注册这些变量或者函数。假设我们需要在MainWnd.xml对应的lua文件:MainWnd.xml.lua中动态的对MyEdit控件的属性进行修改,可以这样做,
   <!—filename:MainWnd.xml.lua-->
function OnCreate(self)
   local objTree = self:GetBindUIObjectTree()
    获取宿主窗口上绑定的对象树
     local edit =
objTree:GetUIObject(”MainWnd.RootLayout:MyEditControl”)
获取对象树上MainWnd.RootLayout节点下的MyEditControl控件实例
 
edit:SetText(”编辑框实例”)
设置Text属性值
edit:SetReadOnly(true)
设置是否只读
 
end
    
   其中SetText,SetReadOnly是method_def中定义的子节点名,而不是子节点func属性中的名字。<event_def></event_def>中定义的事件也是相同的道理,只是响应事件的时候,会以event节点的name属性值作为查找的key,找到后,定位到其中的file,func属性描述的lua函数。
 
2.主窗口加载流程以及相关事件说明
前面讲了很多关于XAR的知识,我想你一定有兴趣知道XLUE引擎是怎么拉起一个窗口并显示内容的下面我来为大家讲解一下XAR的工作流程。我们从上面的lua示例代码开始讲起,
local templateMananger =
XLGetObject("Xunlei.UIEngine.TemplateManager")
这句代码的作用是获取XLUE引擎内部的模板管理器对象,模板的创建,获取都需要通过这个对象
 
local frameHostWndTemplate =
templateMananger:GetTemplate("MainWnd","HostWndTemplate")
这句代码的作用是从模板管理器中查找ID为MainWnd,类型为HostWndTemplate的模板,如果模板管理器中不存在该模板,那么将会触发一个XML中的查找操作,首先在nametable.cfg中查找,看有没有对应ID和类型的记录存在,如果存在,那么将取出记录里面的path字段”layout\MainWnd.xml”, 并解析这个XML文件,解析XML文件的过程中,会查找对应ID和类型的节点,找到则记录下该节点对象,并创建一个类型为HostWndTemplate,ID为MainWnd的模板对象。
 
if frameHostWndTemplate then 
获取模板对象可能会失败,所以这里做个判断
 
    local frameHostWnd =
frameHostWndTemplate:CreateInstance("MainFrame")
使用模板对象创建一个ID为MainFrame的实例,这个时候会根据之前记录下的节点对象,解析MainWnd节点的属性,并将属性值绑定到新创建出来的frameHostWnd对象中。
 
if frameHostWnd then
创建宿主窗口模板实例也可能会失败,所以这里加个判断
 
       local objectTreeTemplate =
templateMananger:GetTemplate("MainWndTree",
"ObjectTreeTemplate")
与上面搜索MainWnd节点模板类似,过程基本一样,不同的是扫描对象树模板的时候发现有obj节点,会解析obj节点及其子节点,并把它加入到对象树中,这个操作会依次触发obj的OnPosChange, OnBind,以及OnInitControl事件,这里有一个问题是当一个obj收到OnBind事件时,该obj可能还没有被加入对象树中,这个时候是不能通过对象树管理器取到这个obj或者她的子obj的,当收到OnInitControl事件时,控件以及它的子对象或控件都已经加到对象树中,这时再来操作就正常了,所以建议把控件初始化以及获取控件子对象位置信息等相关的操作放到OnInitContrl事件的响应函数中
 
       if objectTreeTemplate then
       获取模板对象可能会失败,所以这里做个判断
           local uiObjectTree =
objectTreeTemplate:CreateInstance("MainObjectTree")
创建一个ID为MainObjectTree的对象树实例,并将解析出来的属性值绑定到其中
 
           if uiObjectTree then
              创建对象树模板实例也可能会失败,所以这里加个判断
 
              frameHostWnd:BindUIObjectTree(uiObjectTree)
                        将对象树实例绑定到宿主窗口实例上,对象树实例和宿主窗口实例的
关系是一一对应的,这个时候会触发宿主窗口模版XML节点里面定
义的OnBind事件
 
              frameHostWnd:Create()
              真正的创建窗口函数,会触发宿主窗口模版XML节点里面定义的
OnCreate事件,从此进入操作系统的消息循环,用户在窗口上的操作会触发系统事件,可以在lua脚本中响应。
           end
       end
    end
 
3. 创建控件的流程
前面讲到XML文件中定义的模板和控件都是静态的配置信息,只有在通过lua脚本创建控件或者创建模板实例的时候才会去获取并解析其中的XML节点配置信息,解析的过程中会创建出来相应类型的对象。下面讲解下详细的创建控件流程,XLUE引擎提供的创建控件最方便的办法是使用对象工厂类。对象工厂类是引擎内置的类,进行过lua封装。下面,我们以MyEdit.xml中定义的控件为例,介绍下如何动态创建一个控件实例,以及创建的流程。方法如下:
local objFactory = XLGetObject(”Xunlei.UIEngine.ObjectFactory”)
获取XLUE引擎内置的对象工厂类
 
local edit=
objFactory:CreateUIObject(”MyEdit.Instance”,”MyEdit”)
 
创建id为MyEdit.Instance,类型为MyEdit的控件对象实例,创建出来的实例拥有MyEdit控件中定义的所有属性,方法以及事件响应。
 
 
那么CreateUIObject是怎么做到创建一个控件的呢,MyEdit控件的定义可能在任何一个XML文件中,引擎么找到它的呢?答案同样是根据nametable.cfg文件。创建对象时会在所有已载的XAR中的nametable.cfg文件中查找MyEdit控件的定义,如果没找到名字为MyEdit的控件,则返回nil,如果找到,则加载对应的XML进行解析,创建一个控件实例,并找到XML中控件的objtemplate 子节点,然后解析objtemplate下的children子节点以及eventlist子节点,并将其中的内容挂靠到控件实例上,这样,一个控件实例就被创建出来了,创建出来的控件实例是不会被自动添加到对象树上的,因此若想让控件显示出来,需要把他添加到对象树中,完整的创建控件代码如下:
   
<!—filename:MainWnd.xml.lua-->
function OnCreate(self)
   local objTree = self:GetBindUIObjectTree()
     获取宿主窗口上绑定的对象树
local objFactory =
XLGetObject(”Xunlei.UIEngine.ObjectFactory”)
获取XLUE引擎内置的对象工厂类
 
local edit=
objFactory:CreateUIObject(”MyEdit.Instance”,”MyEdit”)
 
edit:SetText(”编辑框实例”)
设置Text属性值
edit:SetReadOnly(true)
设置是否只读
 
local root =
objTree:GetUIObject(”MainWnd.RootLayout”)
获取对象树上MainWnd.RootLayout节点
 
root:AddChild(edit)
将新创建的edit控件实例添加到对象树中,作为root对象的子节点对象
 
edit:SetObjPos(100,100,200,20)
设置edit控件实例的位置,左上顶点坐标(100,100)为相对于父节点对象root
的坐标,宽度为200,高度为20
end
  
   通过上面的代码,我们已经成功地创建了一个控件,并且将他展示在主窗口上。
 
4. XAR的编译以及nametable.cfg文件的说明
按照前面所说,nametable.cfg文件在很多时候都会被用到,那么它是怎么来的呢,答案是XAR编译生成,XAR编译的目的是为了检查XML和lua文件里面的语法错误,以及XML脚本中不符合模板或者控件编写规范的问题。编译XAR的时候,我们会检查所有的XML以及lua文件,如果发现其中存在错误,那么会给出提示,如果没有错误,则会生成nametable.cfg文件,这个过程中并不会对XML以及lua脚本执行编译操作。nametable.cfg文件记录了当前XAR中所有的二级模板,控件节点,即<xlue>节点下定义的模板,控件节点的id,type,class以及所在路径path信息。type表示节点对应的XLUE引擎中的类型,id和type一起可以用来定位模板或者控件,path信息是用来指示对应的XML文件路径的,class字段记录模板或者控件class信息,暂时没有用到
 
5. XAR中的资源获取
XAR包中res目录下的资源包下定义的资源XML,可以通过XAR提供的接口在lua脚本中获得,获取资源时会先从本XAR中查找,如果查找不到,则会尝试从包含的XAR包对应的资源包中查找一级一级往上查找,找完所有的包含XAR包,直到找到对应的资源。如果最后还是找不到,会返回失败。关于资源的介绍,详见XLUE XAR资源介绍文档
 
附录:
          upload/file/XLUE Layout XML Node Attribute Configuration Details.rar

迅雷公司 版权所有 Copyright 2003-2010 Thunder Inc.All Rights Reserved. 意见反馈:xl7doc@xunlei.com