【转】Titanium 架构分析

【原文】Titanium 架构分析

虽然是对早期版本的分析,但是说的很深入,推荐大家看看!

 

 

一、分析的目标

  1. 了解Titanium产品的基本框架结构和特点
  2. 了解Titanium产品如何扩展本地API以及访问方式
  3. 了解Titanium产品中的动态语言之间如何相互调用

二、Titanium概述

2. 1 Titanium介绍

Titanium是一个Web应用程序运行环境,它支持不同的系统平台(WindowsLinuxMac),并且支持Web应用程序对本地APIs的访问。在基于Titanium平台上,用户可以快速开发和方便的部署应用程序,并且这些应用程序可以使用本地APIs实现许多普通Web应用程序无法完成的功能和特性。

2.2 Titanium特点

Titanium框架具有如下几个方面的特点:

  1. 支持多平台(LinuxMacWindows、移动设备)
  2. 使用Web技术加快软件开发速度
  3. 支持Web中内嵌多种编程语言
  4. 支持对本地APIs的访问
  5. 通过Appcelerator网络云服务,基于Titanium的应用可以更容易的打包、测试和部署
  6. 本地功能的模块化,可动态加载指定的功能模块
  7. 强大灵活的语言扩展,用户在Titanium框架中可以很方便的扩展多种动态语言

2.3 Titanium 框架结构

上图来自于Appcelerator官网,该图以iPhoneAndroid两个移动平台为例,描述了Titanium的总体框架结构。在Titanium框架中,Web应用程序可以很方便的访问设备UI组件。比如,可以在页面中使用Titanium提供的API控制导航条、工具栏、菜单,以及可以动态的向用户弹出对话框、警告框等。除此,之外Titanium API还支持本地功能模块的访问,即用户可以使用Titanium提供的APIs接口访问数据库、定位功能、文件系统功能、网络功能、媒体功能等。

不过该框架图,并没有将Titanium中对多种脚本语言的相互访问机制很好的表现出来。但是,这一机制却又是Titanium框架的一个比较重要的功能特性。

三、Titanium构建

Titanium的构建过程使用scons管理(http://www.scons.org/)。scons是一个开源的软件构建工具,使用Python语言来描述软件构建规则。通过Titanium的源码级构建和Titanium的构建规则两个方面,可以了解Titanium运行环境由那些部分组成、这些模块和模块之间的关系是什么。

[注]以下所有的测试和分析内容均是以Linux平台上Desktop版本的Titanium代码为基础。

  1. 构建Titanium所依赖的库和环境
  • Ruby 1.8.x 开发包
  • Python 2.5.x开发包
  • scons构建工具
  • git 版本管理工具
    1. Ubuntu 9.04上构建Titanium所需的支持包
      sudo apt-get install build-essential ruby rubygems libzip-ruby \
      scons libxml2-dev libgtk2.0-dev python-dev ruby-dev \
      libdbus-glib-1-dev libnotify-dev libgstreamer0.10-dev \
      libxss-dev libcurl4-openssl-dev 
      
      sudo apt-get install git-core
    2. 获取Titanium源码
      git clone git://github.com/marshall/titanium
      cd titanium
    3. 获取Kroll源码
      git submodule init
      git submodule update
      cd kroll
      git checkout master
    4. 构建Titanium测试程序
      cd ..
      scons debug=1
    5. 运行
scons testapp debug=1 run=1

有关Titanium构建相关的信息,可以访问以下页面获得:

http://wiki.github.com/marshall/titanium/build-instructions

3.2 Titanium构建规则分析

3.2.1 版本需求

 

构建过程所需的库/程序版本
Python2.5
Ruby1.8
Scons1.2
kroll 源码版本 12/30/99
titanium_desktop 源码版本 12/30/99
WebKit版本 libwebkittitanium-1.0.so.2.7.0
  

 

3.2.2 默认配置项

 

默认配置项
配置备注
PRODUCT_VERSION0.7.0 
INSTALL_PREFIX/usr/local 
PRODUCT_NAMETitanium 
CONFIG_FILENAMEtiapp.xml 
BUILD_DIRbuild 
THIRD_PARTY_DIRkroll/thirdparty 
DISTRIBUTION_URLapi.appcelerator.net 
CRASH_REPORT_URLapi.appcelerator.net/p/v1/app-crash-report 
GLOBAL_NS_VARNAMETitanium 定义了全局Titanium对象名称
   

3.2.3 scons编译参数

 

Scons编译参数
debug0表示release版本,1表示debug版本
clean清除构建的工程
qclean清除构建的工程
run 运行TestApp
run_with 带参数运行TestApp,好像Linux平台上没用

3.2.4 构建规则文件

 

构建规则文件
kroll/SConscript.thirdpartyTitanium所需的第三方支持文件规则
installation/SConscriptTitanium安装器构建规则
kroll/SConscript 构建kroll库规则
modules/SConscript构建语言支持模块规则
apps/SConscript 构建TestApp规则
SConscript.dist 构建SDK规则
SConscript.docs 构建APIs文档规则
SConscript.test构建测试程序规则

3.2.5 核心库和程序构建规则

 

/程序 规则
build/linux/runtime/template/kboot kroll/boot/breakpad/common/*.c

 

kroll/boot/breakpad/common/*.cc

kroll/boot/breakpad/client/*.cc

kroll/boot/breakpad/processor/*.cc

kroll/boot/breakpad/client/linux/handler/*.cc

kroll/boot/breakpad/common/linux/*.cc

build/linux/runtime/libkroll.so kroll/api/*.cpp

 

kroll/api/config/*.cpp

kroll/api/binding/*.cpp

kroll/api/utils/*.cpp

kroll/api/utils/poco/*.cpp

kroll/api/utils/linux/*.cpp

kroll/api/net/proxy_config.cpp

kroll/api/net/*_linux.cpp

build/linux/runtime/libkhost.so kroll/host/linux/host.cpp

 

kroll/host/linux/linux_job.cpp

/linux/modules/api/libapimodule.so poco third library(http://pocoproject.org/)

 

kroll/modules/api/*.cpp

build/linux/modules/javascript/libjavascriptmodule.so poco third library(http://pocoproject.org/)

 

webkittitanium-1.0 third library

kroll/modules/javascript/*.cpp

build/linux/modules/ruby/librubymodule.so poco third library(http://pocoproject.org/)

 

libruby third library

kroll/modules/ruby/*.cpp

build/linux/modules/php/libphpmodule.so poco third library(http://pocoproject.org/)

 

kroll/modules/php/*.cpp

  

四、Titanium静态分析

该部分主要是说明整个Titanium的阅读工作量、弄清楚Titanium中定义的核心对象的功能作用,以及各个模块之间的关系是什么。

4.1 代码统计

这里,将Titanium项目代码分成kroll和功能模块扩展两部分代码来统计,数据如下两表所示:

 

Kroll模块代码量统计
LanguageFilesBlankCommentCodeScaleEquiv
C/C++ Header116835490
63506
111461
1.00
111461
HTML386125216112513751.997612.5
C++16264017046331331.5150030.83
Javascript4732731598132141.4819556.72
CSS355441272012720
Object C635931214002.964144
Python1026018512064.25065.2
Shell11561572343.81891.54
Make33029932.5232.5
Assembly11539570.2514.25
Ruby1100544.2226.8
Yaml100120.910.8
SUM180247938892632170121.35293546.95
titanium_desktop模块(排除Kroll模块)
LanguageFilesBlankCommentCodeScaleEquiv
Javascript11858013276286781.4842443.44
C++12546905169273201.5141253.2
C/C++ Header15916473443768217682
HTML493473937151.87058.5
Ruby2967364332274.213553.4
CSS554241265512655
Python4560166426324.211054.4
C116723719250.771482.25
Shell13601582513.81956.31
PHP53711793.5626.5
XML5081511.9286.9
Object C231151192.96352.24
SUM5561459613694785341.65129404.14

4.2 核心对象的介绍

 

对象基类说明
AccessorBoundObjectStaticBoundObject settergetter的封装,当用户访问想访问XXX属性时,该对象会调用setXXX方法或者getXXX方法。目前Titanium中主要是JSTitanium对象使用AccessortBoundObject封装
AccessorBoundMethodStaticBoundMethod 用于通过属性的方式访问方法,由该对象封装的方法,会自动的导出settergetter方法
AccessorBoundListStaticBoundList 用于以属性的方式访问list对象,由该对象封装的list,会自动导出settergetter
ArgList 对参数列表对象的封装
Blob 对数据封装,可以描述任何数据
Tuplex 对元组对象的封装
DelegateStaticBoundObjectKObject 用于对全局访问对象的封装,目前Titanium中只有UITitanium JS对象使用该对象封装
KListKObject 封装List对象
KMethodKObject对方法的封装,所有扩展语言的函数,都需要用该对象封装
KEventObjectAccessorBoundObject 描述事件对象,JS中可以通过该对象,向主线程发送事件。比如重新载入页面、弹出对话框。
KEventMethodKEventObject 对事件方法的封装,目前只有ti.Process模块使用该对象
KObjectReferenceCounted所有的其他类型语言对象和方法都是继承该类,这样可以按照相同的方法处理不同语言对象和方法
StaticBoundListKList 静态列表,使用内部map绑定属性
StaticBoundMethodKMethod静态方法
StaticBoundObjectKObject 静态对象,继承该对象可以很方便的设置对象的属性、方法。

 

每个StaticBoundObject内部,都保存着一个StringShareValuemap成员属性。

ValueReferenceCounted描述对象类型
   

4.3 模块之间的关系

从整体框架结构上来看,可以将Titanium分成三个部分,最上层是WebKit以及针对WebKit的扩展(修改很少),中间层是kroll可以将其看成是一个中间件,最下层是个个模块的扩展。模块之间的关系如图所示:

以下从WebKitKroll和模块扩展三个部分来说明

WebKit: WebKit引擎解析页面数据发现<script>标签,或者当用户触发了页面中某个与脚本函数相关的控件时,WebCore会将相应的脚本代码片段传递给JavascriptCore解析执行。如果对比Tinanium修改的WebKit代码和原始的WebKit代码(http://www.webkit.org)会发现,tinaniumWebKit的修改是及小的。主要是作了两个方面的工作:首先,tinanium扩展了KURL的处理,增加了ti://, app://等私有协议的支持。再者,在WebKit/gtk/webkit/目录中,添加了几个接口函数(主要是用来处理扩展的协议和注册解析器),其中最重要的是webkit_titanium_add_script_evaluator,该接口在Kroll模块的script类中会被调用,用来向WebKit引擎注册一个Evaluator Proxy

KrollBase Module:这部分主要的职责是负责Javascript的方法、对象和PythonRubyPHP等语言之间相互转换、事件处理,以及模块动态加载。Kroll模块中,定义了一个host对象,这个对象是整个TestApp的主线程,UI初始化、WebKit初始化和事件处理都是在host中完成的。host对象中保存了一个全局对象表,该表会在WebKit引擎、Python引擎、Ruby引擎之间以KObject中间对象形式相互传递,最终达到不同语言之间的相互调用。

API Extension:这里扩展了大量的与系统平台功能相关的APIWeb应用使用。其中最重要的一个对象是ti.UI,该模块负责UI相关的资源、事件处理、GTK主界面的创建、Tininum JS对象的创建。

五、Titanium动态分析

下面从6个方面以TestApp为例,来分析Titanium的主要特性和功能。

5.1 TestApp初始化

TestApp的启动过程有个自启动过程。首先,TestApp启动后会创建一个Application对象,该对象会从Mainifest文件中获取App相关的资源,并且保存在一个全局变量中。然后,TestApp会设置几个系统环境变量:

KR_BOOTSTRAPPED: 描述是否已经初始化环境变量以及构建Application对象

KR_HOME:描述运行程序的HOME路径

KR_RUNTIME:描述运行时资源路径

KR_MODULES:描述需加载模块信息

LD_LIBRARY_PATH:描述模块所在的文件夹路径

最后,TestApp会使用exec系统调用将自己自启,然后通过之前设置 KR_BOOTSTRAPPED环境变量判断是否进入下一阶段的初始化过程。如果 KR_BOOTSTRAPPED设置为YES,则会首先将其unset,然后启动LinuxHost

titanium框架中,使用动态库的方式将模块之间的关系解耦合。在TestApp启动的第二阶段中,StartHost(kroll/boot/boot_linux.cpp)会根据之前设置的 KR_RUNTIME路径信息,找到libkhost.so动态库,然后从libkhost.so中获取Execute函数指针,并且调用(这里有个问题,如果多个实列同时运行,有可能KR_RUNTIME尚未unset就启动第二个应用,则会出现TestApp异常)。

libkhost.so动态库中的Execute方法,首先创建一个Host实例,在这里是LinuxHost对象,然后调用该对象的Run方法进入一个循环。这个循环是整个TestApp的主循环,主要负责模块的动态发现和加载,事件处理。在LinuxHost的实现中,会维护一个job队列,通过定时器的方式,每隔250ms的时间会去检测该job队列中是否有job存在(LinuxJob描述)。如果,事件存在则会一次性将所有的事件取出,并且清空事件队列,然后一个个的执行job对象的Execute方法。

TestApp的两次初始化过程如下图所示:

5.2 模块初始化

TestApp程序创建LinuxHost对象,并且执行Run方法之后,会首先扫描KR_MODULES环境变量中指定的模块,并且从 LD_LIBRARY_PATH定义的路径信息中寻找到这些动态库模块,并且加载(调用相应模块的Initialize方法)。LinuxHost首先加载的是基本模块(APIPythonModuleRubyModulePHPModuleJavascriptModule)。

pythonModule为例,描述一个完整的加载过程:

  1. Host::LoadModules从环境变量中获取到python动态库的路径信息
  2. 调用Host::FindBasicModules方法,将libpythonmodule.so文件加载进来,然后调用PythonModuleInitialize方法
  3. PythonModule::Initialize首先会向全局属性表中创建一个PythonPythonEaluator对象的关联。前面也说到,Host对象会保存一个全局属性表,这个表中使用KObject中间对象形式,将JAVASCRIPTPYTHONRUBYPHP等语言定义的对象封装,并且保存在该表中。运行时,可以使用Host对象的GetGlobalObject方法获取。
  4. 调用Script::AddScriptEvaluator静态方法将PythonEvaluator对象放入Script对象中维护的一个Ealuator列表中。当JavascriptCore引擎发现<script>标签会遍历这个evaluator链表,通过MIME类型找到相应的解析器实例,然后将代码片段传递给相应的解析器处理(这样就可以支持HTML代码中内嵌多种语言)。

这里还有一个模块比较特殊需要仔细说明,即ti.UI模块。该模块负责WebKit引擎初始化,GTK窗口创建以及UI事件的处理。加载过程类似PythonModule,首先Host对象找到libtiuimodule.so动态库,然后调用Initialize方法初始化,ti.UI模块中的Initialize方法只做了两件事情,创建了一个APIBinding对象,然后将“API”属性和APIBinding关联起来,保存在全局属性表中。接下来,当Host::LoadModules方法加载完毕所有的动态库后,会调用Host::StartModules来启动模块(在整个TestApp运行中,只有ti.UI模块的Start方法被重载了,而其他模块在StartModules方法被执行时,什么事情都没有做)。UIModule::Start方法做了三个非常重要的操作:1、创建GtkUIBinding对象,并且将“UI”和该对象绑定,存放在全局属性表中。2、调用ScriptEvaluator::Initialzie()使用WebKit扩展中导出的webkit_titanium_add_script_evaluator函数,将自己注册到JavascriptCore中。3、创建初始化WebView

至此,WebKit引擎已经初始化完毕、UI界面已经初始化完毕、相应语言的解析器以及JAVACRIPT API扩展对象已经添加到全局属性表中。但是至此,页面中是无法正常访问Titanium对象的。

整个过程如下图所示:

5.3 Titanium对象的注册

Javascript中的Titanium并没有通过硬编码的方式定义该名称,而是在构建的过程中通过变量的方式指定的。在构建规则中有GLOBAL_NS_VARNAME变量,该变量名称会作为编译参数传递,代码通过改变量的定义,来确定Javascript可见的Titanium主对象的名称是什么。

webView构建完毕后,Titanium Js主对象并不存在,只有当第一次WebKit遇到脚本代码时,这个对象才被创建。当WebKit引擎碰到脚本对象时,会调用JavascriptCoreinitScipt方法,初始化Javascript引擎。在此JavascriptCore会将我们之前调用webkit_titanium_add_script_evaluator增加的Evaluator代理和JavascriptCore解析关联起来。当引擎和资源都初始化完毕,会向FrameClient发送object avaliable通知,会调用FrameLoader::dispatchWindowObjectAvaliable()方法。这个方法会导致UserWindow::RegisterJSContext()方法的调用。然后UserWindow对象会创建一个DelegateStaticBoundObject对象来描述Titanium对象,并且将之前初始化完毕的Titanium API对象(KObject)与Titanium对象关联起来,然后将其放入到全局属性表中。这样,之后Web应用程序就可以从全局属性表中访问到Titanium对象了。但是Titanium对象中有哪些子属性是不知道的,这是运行时才去确定。

这个初始化过程如下图所示:

5.4 事件系统

Titanium的事件系统分成两个部分来说明,一部分是如何获取事件,另一部分是如何向事件系统中增加新事件。

linuxHost::RunLoop循环被调用后,立即会注册一个定时器,每隔250ms调用一次main_thread_job_handler函数。该函数首先通过GetJobs方法获取当前系统中未处理的事件,并且依次的调用事件对象(LinuxJob)的Execute方法执行。如果系统没有未决的事件存在,则立即返回。

事件的添加和触发均通过LinuxHost::InvokeMethodOnMainThread()方法完成。该函数会创建一个LinuxJob对象,并且插入到事件队列尾中。事件的触发有多种可能性,可以是由Javascript代码触发,也可以是内部事件触发,同样也有可能是UI事件触发。

由于Titanium扩展的JS APIC层上都会与相应的C方法对应,当Web应用程序调用相应的JS方法,对应的C方法会被调用,该方法则会使用LinuxHost::InvokeMethodOnMainThread()方法,向主线程发送事件处理请求。

对于UI来说,在Linux平台上所有的UI相关的事件首先是被GTK的应用框架截获,当有UI事件到来时,ti.UI模块会创建对应事件的KEvent对象(在event.h中定义),然后调用KEvent对象的Fire方法,触发事件。该方法会间接的使用LinuxHost::InvokeMethodOnMainThread方法向LinuxHost事件队列注册事件。

这个过程如下图所示:

5.5 访问Titanium对象属性和方法

比如,有一段Javascript脚本中调用Titanium.API获取Titanium对象的API属性,当JavascriptCore解析到这部分代码时,并不知道这个对象是什么类型的,完全当作一个抽象的JSValue对象来对待,因为对于Javascript引擎来说,并不需要时刻知道对象是什么,有那些属性和方法,只有到运行时才会去用查询该对象中是否存在指定的属性或者方法(JavascriptCore内部维护了一张属性表,通过查询方式获取相应属性或者函数的处理函数指针。这点V8做的就比较高明,用类的方式描述,动态将对应JS的方法和属性转换成C++的成员函数和成员变量,大量的减少了访问时间)。由于API这个对象是由Kroll创建的,是一个KObject对象,在挂载到Javascript 全局属性表之前,Titanium会调用KObjectToJSValue方法,将KObject对象转换成一个JSObject对象,并且设置了几个比较重要的回调函数:

  1. HasPropertyCallback: 当查询属性时候被调用
  2. GetPropertyCallback: 当获取指定属性时被调用
  3. SetPropertyCallback: 当设置指定属性时被调用

javascript访问用Titanium对象的API属性时,通过JSValue.Get方法会调用到GetPropertyCallback函数,该函数会查询KObject对象中(这里是说的Titanium)是否有API这个属性,如果有则转换成JSValue对象,并且返回给Javascript引擎。

如果这里访问的是一个属性的方法,过程和访问对象是一样的,只不过最后创建的是一个Function Js对象,而非Object对象。当Javascript访问这个返回的Function对象时,Javascript引擎会调用callAsFunction方法,而该方法会引发CallAsFunctionCallback回调函数被调用(该函数是静态函数在KMethodToJSValue函数中注册)。这样通过CallAsFunctionCallback这个回调接口,调用实际的KObjectCall方法,执行实际的处理函数。

属性访问过程如下图所示:

5.6 JavascriptPythonRuby动态语言间的相互调用

Titanium框架中引入了一个比较有意思的特性,即支持多种语言之间的相互调用。从实现技术角度来说,Titanium的多语言支持的设计思想,是在学习了WebKitBinding机制而发展过来的。主要用到了JavascriptCore引擎可以动态注册Evaluator的机制。HTML语言中定义了<script>标签,用于内嵌脚本语言,该标签有个子属性type,通过该属性可以让浏览器引擎区分是什么类型的脚本。加入我们有如下的脚本代码:

<script type=”text/python” src=”xxx.py”></script>

首先,WebCore引擎会解析HTML页面数据,当发现有<script>标签出现,则会创建HTMLScriptElement,对于script有两种处理情况,一种是如上代码通过src包含一个脚本路径,还有一种情况是定义一段代码,通过控件或者超连接的方式以事件方式触发。如果是第一种情况,则会在创建HTMLScriptElement的时引发ScriptElementData::requestScript方法的调用。如果是第二种情况,则会在触发相应事件时候调用FrameLoader::executeScript方法执行脚本。最终都会调用JavascriptCore中的EvaluatorAdapter::evaluate()。由于在初始化ti.UI模块时,我们已经注册了自己的evaluatorScriptEvaluator),因此会将获取的脚本信息传递给ScriptEvaluator,在该对象中,会通过Script::Evaluate()方法,根据传递下来脚本的MIME类型(也就是scripttext字段定义的类型)派发给注册的不同解析器去执行。

这里以Javascript调用Python代码,并且Python代码中又调用了ruby代码为例子说明其调用过程。

Python代码:

def abc():

ruby_fun()

Ruby代码:

def ruby_func()

end

pythonEvaluator::Evaluate()被调用后,首先将保存的全局JS对象表转换成Python可识别的对象字典(KMethodToPyObject完成,转换成PyKMethodTypePython对象)。这样在之后编译的Python代码中就可以访问到这些对象。然后将Python代码使用Python编译器编译,并且将编译后的函数对象转换成KObject对象,插入到全局的JS对象表中(abc)。这样,Javscript,和其他语言都可以识别该对象。同样,对Ruby函数的处理,也会首先将全局JS对象表中的KObject对象转换成Ruby的对象,然后对Ruby函数进行编译,将新生成的Ruby函数对象(ruby_fun)转换成KObject对象,然后从新更新JS全局对象转换表。至此,全局JS对象表中就新增了两个KObject对象:ruby_fun, abc

javascript访问该abc函数对象时,按照通常方式首先调用CaAsFunctionCallback,该函数会调用KObjectCall方法,由于该KObject实际上就是一个KPythonMethod对象,因此KPythonMethod对象的Call方法会被调用。之前我们注册的Python方法是一个PyKMethodType,该类型中定义了一个方法回调函数,当Python方法被调用时,该回调函数(PyKMethod_call)会被调用。这个例子中,在Python代码里调用了ruby_fun,因此当PyKMethod_call被调用时,首先将调用的KObject对象(实际上是一个对Ruby函数对象的封装)转换成KMethod对象,然后调用Call方法。这样就通过间接调用,调用到KRubyMethodCall方法,使得Ruby函数得到执行。

整个过程如下图所示:

六、参考资源

http://www.appcelerator.com/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值