Titanium架構(gòu)分析
一、分析的目標(biāo)
- 了解Titanium產(chǎn)品的基本框架結(jié)構(gòu)和特點
- 了解Titanium產(chǎn)品如何擴展本地API以及訪問方式
- 了解Titanium產(chǎn)品中的動態(tài)語言之間如何相互調(diào)用
二、Titanium概述
2.1 Titanium介紹
Titanium是一個Web應(yīng)用程序運行環(huán)境,它支持不同的系統(tǒng)平臺(Windows、Linux、Mac),并且支持Web應(yīng)用程序?qū)Ρ镜谹PIs的訪問。在基于Titanium平臺上,用戶可以快速開發(fā)和方便的部署應(yīng)用程序,并且這些應(yīng)用程序可以使用本地APIs實現(xiàn)許多普通Web應(yīng)用程序無法完成的功能和特性。
2.2 Titanium特點
Titanium框架具有如下幾個方面的特點:
- 支持多平臺(Linux、Mac、Windows、移動設(shè)備)
- 使用Web技術(shù)加快軟件開發(fā)速度
- 支持Web中內(nèi)嵌多種編程語言
- 支持對本地APIs的訪問
- 通過Appcelerator網(wǎng)絡(luò)云服務(wù),基于Titanium的應(yīng)用可以更容易的打包、測試和部署
- 本地功能的模塊化,可動態(tài)加載指定的功能模塊
- 強大靈活的語言擴展,用戶在Titanium框架中可以很方便的擴展多種動態(tài)語言
2.3 Titanium 框架結(jié)構(gòu)

上圖來自于Appcelerator官網(wǎng),該圖以iPhone和Android兩個移動平臺為例,描述了Titanium的總體框架結(jié)構(gòu)。在Titanium框架中,Web應(yīng)用程序可以很方便的訪問設(shè)備UI組件。比如,可以在頁面中使用Titanium提供的API控制導(dǎo)航條、工具欄、菜單,以及可以動態(tài)的向用戶彈出對話框、警告框等。除此,之外Titanium API還支持本地功能模塊的訪問,即用戶可以使用Titanium提供的APIs接口訪問數(shù)據(jù)庫、定位功能、文件系統(tǒng)功能、網(wǎng)絡(luò)功能、媒體功能等。
不過該框架圖,并沒有將Titanium中對多種腳本語言的相互訪問機制很好的表現(xiàn)出來。但是,這一機制卻又是Titanium框架的一個比較重要的功能特性。
三、Titanium構(gòu)建
Titanium的構(gòu)建過程使用scons管理(http://www.scons.org/)。scons是一個開源的軟件構(gòu)建工具,使用Python語言來描述軟件構(gòu)建規(guī)則。通過Titanium的源碼級構(gòu)建和Titanium的構(gòu)建規(guī)則兩個方面,可以了解Titanium運行環(huán)境由那些部分組成、這些模塊和模塊之間的關(guān)系是什么。
[注]以下所有的測試和分析內(nèi)容均是以Linux平臺上Desktop版本的Titanium代碼為基礎(chǔ)。
構(gòu)建Titanium所依賴的庫和環(huán)境
- Ruby 1.8.x 開發(fā)包
- Python 2.5.x開發(fā)包
- scons構(gòu)建工具
- git 版本管理工具
Ubuntu 9.04上構(gòu)建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
獲取Titanium源碼
- git clone git://github.com/marshall/titanium
- cd titanium
獲取Kroll源碼
- git submodule init
- git submodule update
- cd kroll
- git checkout master
構(gòu)建Titanium測試程序
- cd ..
- scons debug=1
運行
- scons testapp debug=1 run=1
有關(guān)Titanium構(gòu)建相關(guān)的信息,可以訪問以下頁面獲得:
http://wiki.github.com/marshall/titanium/build-instructions
3.2 Titanium構(gòu)建規(guī)則分析
3.2.1 版本需求
構(gòu)建過程所需的庫/程序版本 | |
Python | 2.5 |
Ruby | 1.8 |
Scons | 1.2 |
kroll 源碼版本 | 12/30/99 |
titanium_desktop 源碼版本 | 12/30/99 |
WebKit版本 | libwebkittitanium-1.0.so.2.7.0 |
3.2.2 默認配置項
默認配置項 | ||
配置 | 值 | 備注 |
PRODUCT_VERSION | 0.7.0 | |
INSTALL_PREFIX | /usr/local | |
PRODUCT_NAME | Titanium | |
CONFIG_FILENAME | tiapp.xml | |
BUILD_DIR | build | |
THIRD_PARTY_DIR | kroll/thirdparty | |
DISTRIBUTION_URL | api.appcelerator.net | |
CRASH_REPORT_URL | api.appcelerator.net/p/v1/app-crash-report | |
GLOBAL_NS_VARNAME | Titanium | 定義了全局Titanium對象名稱 |
3.2.3 scons編譯參數(shù)
Scons編譯參數(shù) | |
debug | 0表示release版本,1表示debug版本 |
clean | 清除構(gòu)建的工程 |
qclean | 清除構(gòu)建的工程 |
run | 運行TestApp |
run_with | 帶參數(shù)運行TestApp,好像Linux平臺上沒用 |
3.2.4 構(gòu)建規(guī)則文件
構(gòu)建規(guī)則文件 | |
kroll/SConscript.thirdparty | Titanium所需的第三方支持文件規(guī)則 |
installation/SConscript | Titanium安裝器構(gòu)建規(guī)則 |
kroll/SConscript | 構(gòu)建kroll庫規(guī)則 |
modules/SConscript | 構(gòu)建語言支持模塊規(guī)則 |
apps/SConscript | 構(gòu)建TestApp規(guī)則 |
SConscript.dist | 構(gòu)建SDK規(guī)則 |
SConscript.docs | 構(gòu)建APIs文檔規(guī)則 |
SConscript.test | 構(gòu)建測試程序規(guī)則 |
3.2.5 核心庫和程序構(gòu)建規(guī)則
庫/程序 | 規(guī)則 |
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靜態(tài)分析
該部分主要是說明整個Titanium的閱讀工作量、弄清楚Titanium中定義的核心對象的功能作用,以及各個模塊之間的關(guān)系是什么。
4.1 代碼統(tǒng)計
這里,將Titanium項目代碼分成kroll和功能模塊擴展兩部分代碼來統(tǒng)計,數(shù)據(jù)如下兩表所示:
Kroll模塊代碼量統(tǒng)計 | ||||||
Language | Files | Blank | Comment | Code | Scale | Equiv |
C/C++ Header | 1168 | 35490 |
63506 |
111461 |
1.00 |
111461 |
HTML | 386 | 1252 | 16112 | 51375 | 1.9 | 97612.5 |
C++ | 162 | 6401 | 7046 | 33133 | 1.51 | 50030.83 |
Javascript | 47 | 3273 | 1598 | 13214 | 1.48 | 19556.72 |
CSS | 3 | 554 | 41 | 2720 | 1 | 2720 |
Object C | 6 | 359 | 312 | 1400 | 2.96 | 4144 |
Python | 10 | 260 | 185 | 1206 | 4.2 | 5065.2 |
Shell | 11 | 56 | 157 | 234 | 3.81 | 891.54 |
Make | 3 | 30 | 29 | 93 | 2.5 | 232.5 |
Assembly | 1 | 15 | 39 | 57 | 0.25 | 14.25 |
Ruby | 1 | 10 | 0 | 54 | 4.2 | 226.8 |
Yaml | 1 | 0 | 0 | 12 | 0.9 | 10.8 |
SUM | 1802 | 47938 | 89263 | 217012 | 1.35 | 293546.95 |
titanium_desktop模塊(排除Kroll模塊) | ||||||
Language | Files | Blank | Comment | Code | Scale | Equiv |
Javascript | 118 | 5801 | 3276 | 28678 | 1.48 | 42443.44 |
C++ | 125 | 4690 | 5169 | 27320 | 1.51 | 41253.2 |
C/C++ Header | 159 | 1647 | 3443 | 7682 | 1 | 7682 |
HTML | 49 | 347 | 39 | 3715 | 1.8 | 7058.5 |
Ruby | 29 | 673 | 643 | 3227 | 4.2 | 13553.4 |
CSS | 5 | 542 | 41 | 2655 | 1 | 2655 |
Python | 45 | 601 | 664 | 2632 | 4.2 | 11054.4 |
C | 1 | 167 | 237 | 1925 | 0.77 | 1482.25 |
Shell | 13 | 60 | 158 | 251 | 3.81 | 956.31 |
PHP | 5 | 37 | 1 | 179 | 3.5 | 626.5 |
XML | 5 | 0 | 8 | 151 | 1.9 | 286.9 |
Object C | 2 | 31 | 15 | 119 | 2.96 | 352.24 |
SUM | 556 | 14596 | 13694 | 78534 | 1.65 | 129404.14 |
4.2 核心對象的介紹
對象 | 基類 | 說明 |
AccessorBoundObject | StaticBoundObject | 對setter和getter的封裝,當(dāng)用戶訪問想訪問XXX屬性時,該對象會調(diào)用setXXX方法或者getXXX方法。目前Titanium中主要是JS的Titanium對象使用AccessortBoundObject封裝 |
AccessorBoundMethod | StaticBoundMethod | 用于通過屬性的方式訪問方法,由該對象封裝的方法,會自動的導(dǎo)出setter和getter方法 |
AccessorBoundList | StaticBoundList | 用于以屬性的方式訪問list對象,由該對象封裝的list,會自動導(dǎo)出setter和getter |
ArgList | 對參數(shù)列表對象的封裝 | |
Blob | 對數(shù)據(jù)封裝,可以描述任何數(shù)據(jù) | |
Tuplex | 對元組對象的封裝 | |
DelegateStaticBoundObject | KObject | 用于對全局訪問對象的封裝,目前Titanium中只有UI和Titanium JS對象使用該對象封裝 |
KList | KObject | 封裝List對象 |
KMethod | KObject | 對方法的封裝,所有擴展語言的函數(shù),都需要用該對象封裝 |
KEventObject | AccessorBoundObject | 描述事件對象,JS中可以通過該對象,向主線程發(fā)送事件。比如重新載入頁面、彈出對話框。 |
KEventMethod | KEventObject | 對事件方法的封裝,目前只有ti.Process模塊使用該對象 |
KObject | ReferenceCounted | 所有的其他類型語言對象和方法都是繼承該類,這樣可以按照相同的方法處理不同語言對象和方法 |
StaticBoundList | KList | 靜態(tài)列表,使用內(nèi)部map綁定屬性 |
StaticBoundMethod | KMethod | 靜態(tài)方法 |
StaticBoundObject | KObject | 靜態(tài)對象,繼承該對象可以很方便的設(shè)置對象的屬性、方法。
每個StaticBoundObject內(nèi)部,都保存著一個String到ShareValue的map成員屬性。 |
Value | ReferenceCounted | 描述對象類型 |
4.3 模塊之間的關(guān)系
從整體框架結(jié)構(gòu)上來看,可以將Titanium分成三個部分,最上層是WebKit以及針對WebKit的擴展(修改很少),中間層是kroll可以將其看成是一個中間件,最下層是個個模塊的擴展。模塊之間的關(guān)系如圖所示:

點擊查看大圖
以下從WebKit、Kroll和模塊擴展三個部分來說明
1、WebKit: 當(dāng)WebKit引擎解析頁面數(shù)據(jù)發(fā)現(xiàn)
首先,WebCore引擎會解析HTML頁面數(shù)據(jù),當(dāng)發(fā)現(xiàn)有標(biāo)簽,或者當(dāng)用戶觸發(fā)了頁面中某個與腳本函數(shù)相關(guān)的控件時,WebCore會將相應(yīng)的腳本代碼片段傳遞給JavascriptCore解析執(zhí)行。如果對比Tinanium修改的WebKit代碼和原始的WebKit代碼(http://www.webkit.org)會發(fā)現(xiàn),tinanium對WebKit的修改是及小的。主要是作了兩個方面的工作:首先,tinanium擴展了KURL的處理,增加了ti://, app://等私有協(xié)議的支持。再者,在WebKit/gtk/webkit/目錄中,添加了幾個接口函數(shù)(主要是用來處理擴展的協(xié)議和注冊解析器),其中最重要的是webkit_titanium_add_script_evaluator,該接口在Kroll模塊的script類中會被調(diào)用,用來向WebKit引擎注冊一個Evaluator Proxy。
2、Kroll和Base Module:這部分主要的職責(zé)是負責(zé)Javascript的方法、對象和Python、Ruby、PHP等語言之間相互轉(zhuǎn)換、事件處理,以及模塊動態(tài)加載。Kroll模塊中,定義了一個host對象,這個對象是整個TestApp的主線程,UI初始化、WebKit初始化和事件處理都是在host中完成的。host對象中保存了一個全局對象表,該表會在WebKit引擎、Python引擎、Ruby引擎之間以KObject中間對象形式相互傳遞,最終達到不同語言之間的相互調(diào)用。
3、API Extension:這里擴展了大量的與系統(tǒng)平臺功能相關(guān)的API共Web應(yīng)用使用。其中最重要的一個對象是ti.UI,該模塊負責(zé)UI相關(guān)的資源、事件處理、GTK主界面的創(chuàng)建、Tininum JS對象的創(chuàng)建。
五、Titanium動態(tài)分析
下面從6個方面以TestApp為例,來分析Titanium的主要特性和功能。
5.1 TestApp初始化
TestApp的啟動過程有個自啟動過程。首先,TestApp啟動后會創(chuàng)建一個Application對象,該對象會從Mainifest文件中獲取App相關(guān)的資源,并且保存在一個全局變量中。然后,TestApp會設(shè)置幾個系統(tǒng)環(huán)境變量:
- KR_BOOTSTRAPPED: 描述是否已經(jīng)初始化環(huán)境變量以及構(gòu)建Application對象
- KR_HOME:描述運行程序的HOME路徑
- KR_RUNTIME:描述運行時資源路徑
- KR_MODULES:描述需加載模塊信息
- LD_LIBRARY_PATH:描述模塊所在的文件夾路徑
***,TestApp會使用exec系統(tǒng)調(diào)用將自己自啟,然后通過之前設(shè)置 KR_BOOTSTRAPPED環(huán)境變量判斷是否進入下一階段的初始化過程。如果 KR_BOOTSTRAPPED設(shè)置為YES,則會首先將其unset,然后啟動LinuxHost。
在titanium框架中,使用動態(tài)庫的方式將模塊之間的關(guān)系解耦合。在TestApp啟動的第二階段中,StartHost(kroll/boot/boot_linux.cpp)會根據(jù)之前設(shè)置的 KR_RUNTIME路徑信息,找到libkhost.so動態(tài)庫,然后從libkhost.so中獲取Execute函數(shù)指針,并且調(diào)用(這里有個問題,如果多個實列同時運行,有可能KR_RUNTIME尚未unset就啟動第二個應(yīng)用,則會出現(xiàn)TestApp異常)。
libkhost.so動態(tài)庫中的Execute方法,首先創(chuàng)建一個Host實例,在這里是LinuxHost對象,然后調(diào)用該對象的Run方法進入一個循環(huán)。這個循環(huán)是整個TestApp的主循環(huán),主要負責(zé)模塊的動態(tài)發(fā)現(xiàn)和加載,事件處理。在LinuxHost的實現(xiàn)中,會維護一個job隊列,通過定時器的方式,每隔250ms的時間會去檢測該job隊列中是否有job存在(LinuxJob描述)。如果,事件存在則會一次性將所有的事件取出,并且清空事件隊列,然后一個個的執(zhí)行job對象的Execute方法。
TestApp的兩次初始化過程如下圖所示:

點擊查看大圖
5.2 模塊初始化
TestApp程序創(chuàng)建LinuxHost對象,并且執(zhí)行Run方法之后,會首先掃描KR_MODULES環(huán)境變量中指定的模塊,并且從 LD_LIBRARY_PATH定義的路徑信息中尋找到這些動態(tài)庫模塊,并且加載(調(diào)用相應(yīng)模塊的Initialize方法)。LinuxHost首先加載的是基本模塊(API,PythonModule、RubyModule、PHPModule和JavascriptModule)。
以pythonModule為例,描述一個完整的加載過程:
- Host::LoadModules從環(huán)境變量中獲取到python動態(tài)庫的路徑信息
- 調(diào)用Host::FindBasicModules方法,將libpythonmodule.so文件加載進來,然后調(diào)用PythonModule的Initialize方法
- PythonModule::Initialize首先會向全局屬性表中創(chuàng)建一個Python和PythonEaluator對象的關(guān)聯(lián)。前面也說到,Host對象會保存一個全局屬性表,這個表中使用KObject中間對象形式,將JAVASCRIPT、PYTHON、RUBY、PHP等語言定義的對象封裝,并且保存在該表中。運行時,可以使用Host對象的GetGlobalObject方法獲取。
- 調(diào)用Script::AddScriptEvaluator靜態(tài)方法將PythonEvaluator對象放入Script對象中維護的一個Ealuator列表中。當(dāng)JavascriptCore引擎發(fā)現(xiàn)<script>標(biāo)簽會遍歷這個evaluator鏈表,通過MIME類型找到相應(yīng)的解析器實例,然后將代碼片段傳遞給相應(yīng)的解析器處理(這樣就可以支持HTML代碼中內(nèi)嵌多種語言)。
這里還有一個模塊比較特殊需要仔細說明,即ti.UI模塊。該模塊負責(zé)WebKit引擎初始化,GTK窗口創(chuàng)建以及UI事件的處理。加載過程類似PythonModule,首先Host對象找到libtiuimodule.so動態(tài)庫,然后調(diào)用Initialize方法初始化,ti.UI模塊中的Initialize方法只做了兩件事情,創(chuàng)建了一個APIBinding對象,然后將“API”屬性和APIBinding關(guān)聯(lián)起來,保存在全局屬性表中。接下來,當(dāng)Host::LoadModules方法加載完畢所有的動態(tài)庫后,會調(diào)用Host::StartModules來啟動模塊(在整個TestApp運行中,只有ti.UI模塊的Start方法被重載了,而其他模塊在StartModules方法被執(zhí)行時,什么事情都沒有做)。UIModule::Start方法做了三個非常重要的操作:1、創(chuàng)建GtkUIBinding對象,并且將“UI”和該對象綁定,存放在全局屬性表中。2、調(diào)用ScriptEvaluator::Initialzie()使用WebKit擴展中導(dǎo)出的webkit_titanium_add_script_evaluator函數(shù),將自己注冊到JavascriptCore中。3、創(chuàng)建初始化WebView。
至此,WebKit引擎已經(jīng)初始化完畢、UI界面已經(jīng)初始化完畢、相應(yīng)語言的解析器以及JAVACRIPT API擴展對象已經(jīng)添加到全局屬性表中。但是至此,頁面中是無法正常訪問Titanium對象的。
整個過程如下圖所示:

點擊查看大圖
5.3 Titanium對象的注冊
Javascript中的Titanium并沒有通過硬編碼的方式定義該名稱,而是在構(gòu)建的過程中通過變量的方式指定的。在構(gòu)建規(guī)則中有GLOBAL_NS_VARNAME變量,該變量名稱會作為編譯參數(shù)傳遞,代碼通過改變量的定義,來確定Javascript可見的Titanium主對象的名稱是什么。
當(dāng)webView構(gòu)建完畢后,Titanium Js主對象并不存在,只有當(dāng)***次WebKit遇到腳本代碼時,這個對象才被創(chuàng)建。當(dāng)WebKit引擎碰到腳本對象時,會調(diào)用JavascriptCore的initScipt方法,初始化Javascript引擎。在此JavascriptCore會將我們之前調(diào)用webkit_titanium_add_script_evaluator增加的Evaluator代理和JavascriptCore解析關(guān)聯(lián)起來。當(dāng)引擎和資源都初始化完畢,會向FrameClient發(fā)送object avaliable通知,會調(diào)用FrameLoader::dispatchWindowObjectAvaliable()方法。這個方法會導(dǎo)致UserWindow::RegisterJSContext()方法的調(diào)用。然后UserWindow對象會創(chuàng)建一個DelegateStaticBoundObject對象來描述Titanium對象,并且將之前初始化完畢的Titanium API對象(KObject)與Titanium對象關(guān)聯(lián)起來,然后將其放入到全局屬性表中。這樣,之后Web應(yīng)用程序就可以從全局屬性表中訪問到Titanium對象了。但是Titanium對象中有哪些子屬性是不知道的,這是運行時才去確定。
這個初始化過程如下圖所示:

點擊查看大圖
5.4 事件系統(tǒng)
Titanium的事件系統(tǒng)分成兩個部分來說明,一部分是如何獲取事件,另一部分是如何向事件系統(tǒng)中增加新事件。
當(dāng)linuxHost::RunLoop循環(huán)被調(diào)用后,立即會注冊一個定時器,每隔250ms調(diào)用一次main_thread_job_handler函數(shù)。該函數(shù)首先通過GetJobs方法獲取當(dāng)前系統(tǒng)中未處理的事件,并且依次的調(diào)用事件對象(LinuxJob)的Execute方法執(zhí)行。如果系統(tǒng)沒有未決的事件存在,則立即返回。
事件的添加和觸發(fā)均通過LinuxHost::InvokeMethodOnMainThread()方法完成。該函數(shù)會創(chuàng)建一個LinuxJob對象,并且插入到事件隊列尾中。事件的觸發(fā)有多種可能性,可以是由Javascript代碼觸發(fā),也可以是內(nèi)部事件觸發(fā),同樣也有可能是UI事件觸發(fā)。
由于Titanium擴展的JS API在C層上都會與相應(yīng)的C方法對應(yīng),當(dāng)Web應(yīng)用程序調(diào)用相應(yīng)的JS方法,對應(yīng)的C方法會被調(diào)用,該方法則會使用LinuxHost::InvokeMethodOnMainThread()方法,向主線程發(fā)送事件處理請求。
對于UI來說,在Linux平臺上所有的UI相關(guān)的事件首先是被GTK的應(yīng)用框架截獲,當(dāng)有UI事件到來時,ti.UI模塊會創(chuàng)建對應(yīng)事件的KEvent對象(在event.h中定義),然后調(diào)用KEvent對象的Fire方法,觸發(fā)事件。該方法會間接的使用LinuxHost::InvokeMethodOnMainThread方法向LinuxHost事件隊列注冊事件。
這個過程如下圖所示:

點擊查看大圖
5.5 訪問Titanium對象屬性和方法
比如,有一段Javascript腳本中調(diào)用Titanium.API獲取Titanium對象的API屬性,當(dāng)JavascriptCore解析到這部分代碼時,并不知道這個對象是什么類型的,完全當(dāng)作一個抽象的JSValue對象來對待,因為對于Javascript引擎來說,并不需要時刻知道對象是什么,有那些屬性和方法,只有到運行時才會去用查詢該對象中是否存在指定的屬性或者方法(JavascriptCore內(nèi)部維護了一張屬性表,通過查詢方式獲取相應(yīng)屬性或者函數(shù)的處理函數(shù)指針。這點V8做的就比較高明,用類的方式描述,動態(tài)將對應(yīng)JS的方法和屬性轉(zhuǎn)換成C++的成員函數(shù)和成員變量,大量的減少了訪問時間)。由于API這個對象是由Kroll創(chuàng)建的,是一個KObject對象,在掛載到Javascript 全局屬性表之前,Titanium會調(diào)用KObjectToJSValue方法,將KObject對象轉(zhuǎn)換成一個JS的Object對象,并且設(shè)置了幾個比較重要的回調(diào)函數(shù):
- HasPropertyCallback: 當(dāng)查詢屬性時候被調(diào)用
- GetPropertyCallback: 當(dāng)獲取指定屬性時被調(diào)用
- SetPropertyCallback: 當(dāng)設(shè)置指定屬性時被調(diào)用
當(dāng)javascript訪問用Titanium對象的API屬性時,通過JSValue.Get方法會調(diào)用到GetPropertyCallback函數(shù),該函數(shù)會查詢KObject對象中(這里是說的Titanium)是否有API這個屬性,如果有則轉(zhuǎn)換成JSValue對象,并且返回給Javascript引擎。
如果這里訪問的是一個屬性的方法,過程和訪問對象是一樣的,只不過***創(chuàng)建的是一個Function Js對象,而非Object對象。當(dāng)Javascript訪問這個返回的Function對象時,Javascript引擎會調(diào)用callAsFunction方法,而該方法會引發(fā)CallAsFunctionCallback回調(diào)函數(shù)被調(diào)用(該函數(shù)是靜態(tài)函數(shù)在KMethodToJSValue函數(shù)中注冊)。這樣通過CallAsFunctionCallback這個回調(diào)接口,調(diào)用實際的KObject的Call方法,執(zhí)行實際的處理函數(shù)。
屬性訪問過程如下圖所示:

點擊查看大圖
5.6 Javascript、Python、Ruby動態(tài)語言間的相互調(diào)用
Titanium框架中引入了一個比較有意思的特性,即支持多種語言之間的相互調(diào)用。從實現(xiàn)技術(shù)角度來說,Titanium的多語言支持的設(shè)計思想,是在學(xué)習(xí)了WebKit的Binding機制而發(fā)展過來的。主要用到了JavascriptCore引擎可以動態(tài)注冊Evaluator的機制。HTML語言中定義了<script>標(biāo)簽,用于內(nèi)嵌腳本語言,該標(biāo)簽有個子屬性type,通過該屬性可以讓瀏覽器引擎區(qū)分是什么類型的腳本。加入我們有如下的腳本代碼:
- <script type=”text/python” src=”xxx.py”></script>
首先,WebCore引擎會解析HTML頁面數(shù)據(jù),當(dāng)發(fā)現(xiàn)有<script>標(biāo)簽出現(xiàn),則會創(chuàng)建HTMLScriptElement,對于script有兩種處理情況,一種是如上代碼通過src包含一個腳本路徑,還有一種情況是定義一段代碼,通過控件或者超連接的方式以事件方式觸發(fā)。如果是***種情況,則會在創(chuàng)建HTMLScriptElement的時引發(fā)ScriptElementData::requestScript方法的調(diào)用。如果是第二種情況,則會在觸發(fā)相應(yīng)事件時候調(diào)用FrameLoader::executeScript方法執(zhí)行腳本。最終都會調(diào)用JavascriptCore中的EvaluatorAdapter::evaluate()。由于在初始化ti.UI模塊時,我們已經(jīng)注冊了自己的evaluator(ScriptEvaluator),因此會將獲取的腳本信息傳遞給ScriptEvaluator,在該對象中,會通過Script::Evaluate()方法,根據(jù)傳遞下來腳本的MIME類型(也就是script中text字段定義的類型)派發(fā)給注冊的不同解析器去執(zhí)行。
這里以Javascript調(diào)用Python代碼,并且Python代碼中又調(diào)用了ruby代碼為例子說明其調(diào)用過程。
Python代碼:
- def abc():
- ruby_fun()
Ruby代碼:
- def ruby_func()
- …
- end
當(dāng)pythonEvaluator::Evaluate()被調(diào)用后,首先將保存的全局JS對象表轉(zhuǎn)換成Python可識別的對象字典(KMethodToPyObject完成,轉(zhuǎn)換成PyKMethodType的Python對象)。這樣在之后編譯的Python代碼中就可以訪問到這些對象。然后將Python代碼使用Python編譯器編譯,并且將編譯后的函數(shù)對象轉(zhuǎn)換成KObject對象,插入到全局的JS對象表中(abc)。這樣,Javscript,和其他語言都可以識別該對象。同樣,對Ruby函數(shù)的處理,也會首先將全局JS對象表中的KObject對象轉(zhuǎn)換成Ruby的對象,然后對Ruby函數(shù)進行編譯,將新生成的Ruby函數(shù)對象(ruby_fun)轉(zhuǎn)換成KObject對象,然后從新更新JS全局對象轉(zhuǎn)換表。至此,全局JS對象表中就新增了兩個KObject對象:ruby_fun, abc。
當(dāng)javascript訪問該abc函數(shù)對象時,按照通常方式首先調(diào)用CaAsFunctionCallback,該函數(shù)會調(diào)用KObject的Call方法,由于該KObject實際上就是一個KPythonMethod對象,因此KPythonMethod對象的Call方法會被調(diào)用。之前我們注冊的Python方法是一個PyKMethodType,該類型中定義了一個方法回調(diào)函數(shù),當(dāng)Python方法被調(diào)用時,該回調(diào)函數(shù)(PyKMethod_call)會被調(diào)用。這個例子中,在Python代碼里調(diào)用了ruby_fun,因此當(dāng)PyKMethod_call被調(diào)用時,首先將調(diào)用的KObject對象(實際上是一個對Ruby函數(shù)對象的封裝)轉(zhuǎn)換成KMethod對象,然后調(diào)用Call方法。這樣就通過間接調(diào)用,調(diào)用到KRubyMethod的Call方法,使得Ruby函數(shù)得到執(zhí)行。
整個過程如下圖所示:

點擊查看大圖
六、參考資源
http://www.appcelerator.com/