王 超 沈立煒 趙文耘
1(復(fù)旦大學(xué)軟件學(xué)院 上海 201203) 2(復(fù)旦大學(xué)計(jì)算機(jī)科學(xué)技術(shù)學(xué)院 上海 201203) 3(上海市數(shù)據(jù)科學(xué)重點(diǎn)實(shí)驗(yàn)室 上海 201203)
屏幕觸摸是移動(dòng)設(shè)備上最主要的人機(jī)交互渠道,它能提供例如下拉刷新、左右滑動(dòng)等應(yīng)用特性,這些特性豐富了界面的展示效果。為了實(shí)現(xiàn)這些特性,安卓應(yīng)用開發(fā)者需要基于最基本的onTouchEvent等事件機(jī)制集成應(yīng)用響應(yīng)邏輯,但依舊需要編寫相應(yīng)代碼以識(shí)別觸控的事件類型,如手指的下拉、釋放等。
谷歌從2011年開始提供安卓支持庫來輔助開發(fā)者,這些支持庫對(duì)開發(fā)者經(jīng)常需要的功能進(jìn)行了封裝,其中包括了對(duì)特定類型界面觸控事件的封裝,例如SwipRefreshLayout控件集成了下拉刷新的功能、DrawerLayout控件集成了左右滑動(dòng)的功能。使用這些支持庫不僅提高了開發(fā)者的開發(fā)效率,而且能夠在低版本的安卓平臺(tái)上使用高版本的功能。但許多開發(fā)者對(duì)安卓支持庫提供的功能缺乏了解,因此將已有代碼替換成支持庫需要花費(fèi)很多的精力去學(xué)習(xí),比如開發(fā)者需要對(duì)安卓支持庫中下拉刷新、左右側(cè)滑等控件的代碼結(jié)構(gòu)和實(shí)現(xiàn)機(jī)制有深入的了解。此外,對(duì)于維護(hù)采用基本事件處理機(jī)制編寫的安卓應(yīng)用程序,沒有相應(yīng)的技術(shù)來提醒安卓應(yīng)用維護(hù)者進(jìn)行支持庫的替換,因此維護(hù)人員需要花費(fèi)額外的精力在應(yīng)用程序的源碼中尋找可替換的功能代碼。
目前已經(jīng)有的代碼替換工作主要集中在API和Library庫的遷移。例如將項(xiàng)目中涉及的棄用API(Deprecated API) 用新的API進(jìn)行遷移[1]。再比如隨著軟件系統(tǒng)生命周期的變化,對(duì)于系統(tǒng)中已有的依賴庫需要用更相關(guān)的庫來進(jìn)行遷移[2]。而對(duì)于將已有功能代碼替換成安卓支持庫中相應(yīng)控件實(shí)現(xiàn)的代碼,目前還沒有很好的方法來支持。
針對(duì)上述目標(biāo),本文提出了一種面向安卓觸控類支持庫的應(yīng)用代碼替換技術(shù),其利用android.support.v4.widget支持庫下的觸摸控件并以自動(dòng)化的方式尋找目標(biāo)應(yīng)用中的可替換點(diǎn),最終為應(yīng)用開發(fā)者和維護(hù)者生成替換建議。具體而言,本文首先提出用以描述安卓事件回調(diào)方法代碼特征的元模型,以此為基礎(chǔ)將目標(biāo)代碼特征模型與安卓支持庫各控件的特征模型進(jìn)行匹配。當(dāng)匹配的結(jié)果能夠進(jìn)行替換時(shí),分別從功能的實(shí)現(xiàn)代碼、監(jiān)聽器的綁定、layout資源文件這三個(gè)方面生成替換建議。實(shí)驗(yàn)結(jié)果表明本文所提技術(shù)能夠?yàn)橹С謳炜丶鄳?yīng)的,且用基本觸控邏輯來實(shí)現(xiàn)的事件回調(diào)方法及其資源布局給出正確的替換建議。安卓開發(fā)者利用本文方法,可以分析出應(yīng)用程序中的哪些代碼可以進(jìn)行替換,以及用安卓支持庫中的哪一個(gè)觸摸控件進(jìn)行替換。
與本文相關(guān)的研究工作主要包括安卓應(yīng)用分析、代碼相似度檢測(cè)、代碼遷移技術(shù)這三個(gè)方面。
安卓應(yīng)用分析方法主要分為動(dòng)態(tài)分析和靜態(tài)分析兩大類。動(dòng)態(tài)分析是指當(dāng)應(yīng)用程序在運(yùn)行時(shí)對(duì)應(yīng)用程序的各種行為進(jìn)行分析,以獲取程序的動(dòng)態(tài)行為特征。靜態(tài)分析則是分析應(yīng)用程序的源碼或者字節(jié)碼,構(gòu)造應(yīng)用程序的整體結(jié)構(gòu)。對(duì)于安卓靜態(tài)分析,目前一些研究工具利用不同類型的圖來描繪程序的特征,如控制流圖、數(shù)據(jù)流圖等[3-4]。為了獲取安卓應(yīng)用程序的源碼或字節(jié)碼,需要對(duì)APK安裝包反編譯,常用的反編譯工具有Dexpler[5]、Smail[6]和AndroGuard[7]。本文方法也依賴于安卓靜態(tài)分析技術(shù),在安卓應(yīng)用程序的源碼層面上利用抽象語法樹分析結(jié)果來進(jìn)一步描繪程序的特征。
針對(duì)代碼的相似問題目前已有很多分析方法,如基于文本識(shí)別、基于度量值、基于代碼結(jié)構(gòu)和基于圖結(jié)構(gòu)的代碼相似分析?;谖谋咀R(shí)別的代碼相似分析方法是將代碼表示成文本或token序列的方式,進(jìn)而采用模式匹配技術(shù)來檢測(cè)代碼間是否相似。代碼克隆[8]就是基于文本識(shí)別的方法來檢測(cè)待分析代碼中的重復(fù)部分?;诙攘恐档拇a相似分析方法,不直接比較程序代碼,而是提取選定的度量值信息,形成一組包含這些信息的特征向量,通過比較這些特征向量來比較代碼的相似程度[9]。衡量代碼的度量值有很多,如圈復(fù)雜度、類耦合度和繼承深度等。基于代碼結(jié)構(gòu)的代碼相似分析方法是將待分析的代碼轉(zhuǎn)化成抽象語法樹,進(jìn)而在語法樹上使用匹配或搜索技術(shù)尋找相似的子樹[10-11]。基于圖結(jié)構(gòu)的代碼相似分析是將程序代碼表示成程序依賴圖來進(jìn)行分析[12-13]。從兩個(gè)特定的起始點(diǎn)開始,尋找兩幅圖中的最大相似子圖。本文方法也利用了基于代碼結(jié)構(gòu)相似的分析方法,將安卓應(yīng)用程序代碼構(gòu)造成抽象語法樹,進(jìn)而在抽象語法樹的基礎(chǔ)上利用樹匹配算法進(jìn)行相似分析。
代碼遷移目前主要分為API遷移和Library庫的遷移。對(duì)于API的遷移,可以通過比較兩個(gè)版本API在庫中的聲明變化以及兩個(gè)版本API在代碼中的使用方式來進(jìn)行遷移[14]。對(duì)于Library庫的遷移,通過對(duì)大量開源軟件項(xiàng)目的分析來識(shí)別相似庫之間的遷移變化,進(jìn)而根據(jù)大量的分析數(shù)據(jù)來為L(zhǎng)ibrary庫的遷移提供建議[2]。與上述工作不同,本文方法關(guān)注控件支持庫層面的遷移,涉及一組代碼語句與控制結(jié)構(gòu),其中的核心要素是識(shí)別目標(biāo)代碼與安卓支持庫中特定控件在功能和結(jié)構(gòu)上的相似性。
圖1為開發(fā)者使用基礎(chǔ)回調(diào)方法onTouch實(shí)現(xiàn)的下拉刷新功能:當(dāng)手指下拉并松開后,在屏幕上發(fā)送一條通知。該代碼在基礎(chǔ)回調(diào)方法的UP分支下實(shí)現(xiàn)發(fā)送通知的業(yè)務(wù)邏輯。
圖1 下拉刷新案例功能代碼
圖2為該應(yīng)用程序相應(yīng)的資源文件,其中子視圖由兩個(gè)文本框TextView構(gòu)成,這兩個(gè)文本框在LinearLayout布局的內(nèi)部。
圖2 下拉刷新案例資源文件
圖1的功能代碼和圖2的資源文件都可以用安卓支持庫中已經(jīng)封裝好的控件進(jìn)行替換。當(dāng)安卓應(yīng)用軟件開發(fā)者需要用安卓支持庫中已有的控件進(jìn)行替換時(shí),先要判斷是否能夠替換以及用什么控件進(jìn)行替換,然后還要分別從功能代碼和資源文件兩塊進(jìn)行替換。
對(duì)于開發(fā)者來說這將會(huì)面臨很多困難。首先許多安卓開發(fā)者對(duì)安卓支持庫下提供的功能并不了解,因此將已有代碼替換成支持庫需要一定的學(xué)習(xí)代價(jià)。雖然擁有了一定的安卓支持庫的儲(chǔ)備知識(shí),但沒有相應(yīng)的技術(shù)提醒開發(fā)者該應(yīng)用程序中的某些功能能夠用安卓支持庫中的控件進(jìn)行替換。此外,一些開發(fā)者知道該項(xiàng)目的有些功能可以使用支持庫的控件進(jìn)行替換,但是他們不知道如何在項(xiàng)目中進(jìn)行替換。
當(dāng)開發(fā)者使用基于本文方法所開發(fā)的工具時(shí),就可以根據(jù)工具生成的建議快速地在項(xiàng)目中進(jìn)行支持庫的替換。該工具將會(huì)從功能實(shí)現(xiàn)代碼、監(jiān)聽器的綁定和布局文件這三個(gè)方面給出替換建議。
圖3為根據(jù)替換建議用SwipeRefreshLayout這個(gè)支持庫控件進(jìn)行替換后的功能代碼。該代碼中,遷移的代碼被放置在SwipeRefreshLayout的onRefresh回調(diào)方法中,同時(shí)為SwipeRefreshLayout綁定OnRefresh-Listener監(jiān)聽器,用來監(jiān)聽事件。
圖3 用支持庫控件替換后的功能代碼
資源文件的替換如圖4所示。根據(jù)替換建議,使用SwipeRefreshLayout控件節(jié)點(diǎn)來替換原先的LinearLayout節(jié)點(diǎn)。
圖4 用支持庫替換后的資源文件
圖5所示的元模型用來描繪安卓事件回調(diào)方法的代碼特征信息。該元模型既可以表示支持庫中的回調(diào)方法,又可以用來描述目標(biāo)替換代碼的方法代碼結(jié)構(gòu)。
圖5 安卓事件回調(diào)方法的代碼特征元模型
Widget表示安卓支持庫中的一個(gè)觸摸控件,在該元模型中,控件包含了一組監(jiān)聽器。每一個(gè)監(jiān)聽器負(fù)責(zé)監(jiān)聽一個(gè)或多個(gè)事件并觸發(fā)相應(yīng)的回調(diào)方法(Callback)。我們使用一棵特征樹(FeatureTree)來表示Callback的具體代碼及其應(yīng)用邏輯,并以FeatureTreeRoot來表示該特征樹的根節(jié)點(diǎn)。根節(jié)點(diǎn)一般命名為基礎(chǔ)回調(diào)的方法名,例如onTouch、onTouchEvent等。
特征樹是由一系列表示代碼元素的節(jié)點(diǎn)構(gòu)成,即CodeElement。節(jié)點(diǎn)之間存在Order信息與Include關(guān)系。Order關(guān)系表示同層次代碼元素與同層次代碼元素之間的次序關(guān)系,Include關(guān)系表示外部代碼元素與內(nèi)部代碼元素之間的包含關(guān)系。另外,每一個(gè)節(jié)點(diǎn)包含KeyWordSet屬性,用以代表代碼元素中的關(guān)鍵字集合。該集合可包括方法名以及部分靜態(tài)常量名。這些關(guān)鍵字從SDK中提取得來,用以反映代碼的實(shí)現(xiàn)功能。
元模型中列舉了三種特定類型的代碼元素,即代碼控制結(jié)構(gòu)(ControlStructure)、代碼語句(Statement)和插樁點(diǎn)(Instrumentation)。
ControlStructure用來描述代碼的骨架結(jié)構(gòu),表示程序執(zhí)行的動(dòng)作順序。本文將控制結(jié)構(gòu)分成兩類,循環(huán)結(jié)構(gòu)(Loop)和分支結(jié)構(gòu)(Branch)。循環(huán)結(jié)構(gòu)包括For、While、Do等,分支結(jié)構(gòu)包括If、Switch等。
Statement用來記錄代碼結(jié)構(gòu)中的基本語句信息,例如方法調(diào)用信息,Statement就記錄了該方法調(diào)用是在代碼控制結(jié)構(gòu)中的哪個(gè)分支下進(jìn)行調(diào)用的。
Instrumentation記錄控件回調(diào)方法在代碼中的哪個(gè)位置被間接觸發(fā)。例如SwipeRefreshLayout控件中的onRefresh回調(diào)方法是在onTouchEvent這個(gè)基礎(chǔ)回調(diào)方法中的UP分支下被間接觸發(fā)的。在進(jìn)行代碼替換時(shí),用戶實(shí)現(xiàn)業(yè)務(wù)功能的代碼就放在該插樁點(diǎn)所記錄的回調(diào)方法的內(nèi)部。
算法1是目標(biāo)代碼特征模型的構(gòu)造方法。該算法的輸入是一個(gè)Callback方法,對(duì)Callback方法中的每一個(gè)結(jié)構(gòu)塊進(jìn)行迭代分析。算法的輸出是FeatureTree,即一個(gè)以樹狀結(jié)構(gòu)表示回調(diào)方法的特征模型。
算法1目標(biāo)代碼特征模型的構(gòu)造方法
Function: BuildFeatureTree
輸入: Callback。
輸出: FeatureTree。
begin
For each ControlBlock in Callback
begin
CNode=CreateNode(Loop/Branch)
CreateOrder(PreCNode, CNode)
SNode=CreateNode(Condition)
AddIncludeNode(CNode, SNode)
BNode=CreateNode(Statement)
AddIncludeNode(SNode, BNode)
PreCNode=CNode
end
return FeatureTree
end
在迭代過程中,對(duì)于每一個(gè)結(jié)構(gòu)塊,創(chuàng)建一個(gè)ControlStructure類型的節(jié)點(diǎn),節(jié)點(diǎn)記錄該控制結(jié)構(gòu)的類型是Loop還是Branch,并將此控制結(jié)構(gòu)節(jié)點(diǎn)與父控制結(jié)構(gòu)節(jié)點(diǎn)關(guān)聯(lián)起來。為了記錄控制結(jié)構(gòu)之間的Order關(guān)系,在關(guān)聯(lián)兩個(gè)控制結(jié)構(gòu)節(jié)點(diǎn)時(shí)以O(shè)rder屬性的邊來連接這兩個(gè)節(jié)點(diǎn)??刂平Y(jié)構(gòu)中的條件信息,用Statement類型的節(jié)點(diǎn)來保存,并將該節(jié)點(diǎn)以Include關(guān)系關(guān)聯(lián)到所屬的控制結(jié)構(gòu)節(jié)點(diǎn)上。對(duì)于某一個(gè)條件下的代碼語句,仍然用Statement類型節(jié)點(diǎn)來保存,并將該節(jié)點(diǎn)以Include關(guān)系關(guān)聯(lián)到所對(duì)應(yīng)的分支條件節(jié)點(diǎn)上。如果該控制結(jié)構(gòu)是分支結(jié)構(gòu),則需要對(duì)分支結(jié)構(gòu)下的每一個(gè)分支條件進(jìn)行分析。
首先需要為支持庫每一個(gè)觸摸控件中每一個(gè)監(jiān)聽器下的每一個(gè)回調(diào)方法構(gòu)造特征模型。為了獲取支持庫控件中的回調(diào)方法,可以通過參閱文檔或檢索支持庫中具有Listener的類來找到相應(yīng)的回調(diào)方法。為了找到觸發(fā)回調(diào)方法的調(diào)用語句,需要對(duì)回調(diào)方法進(jìn)行回溯,由于回調(diào)方法有時(shí)候并不能在某一個(gè)方法中找到對(duì)其的調(diào)用,因此回溯過程將分為以下兩種情形:
(1) 如果在某一個(gè)方法中找到觸發(fā)該回調(diào)方法的調(diào)用語句,則以該方法繼續(xù)向上回溯。
(2) 如果沒有找到觸發(fā)該回調(diào)方法的調(diào)用語句,則利用事先對(duì)安卓?jī)?nèi)部回調(diào)方法的總結(jié),找到觸發(fā)該回調(diào)方法的方法,然后以該方法繼續(xù)向上回溯。
算法2描述了回調(diào)函數(shù)調(diào)用鏈的回溯過程,該算法的輸入是一個(gè)支持庫控件中的回調(diào)方法,輸出是控件中的基礎(chǔ)回調(diào)方法。通過算法2,可以將控件中以回調(diào)方法為終點(diǎn),以基礎(chǔ)回調(diào)方法為起點(diǎn)的方法調(diào)用鏈構(gòu)建完成。接著采用算法1,對(duì)基礎(chǔ)回調(diào)方法進(jìn)行特征模型樹的構(gòu)造。此時(shí)的特征模型樹需要加上插樁類型節(jié)點(diǎn),該節(jié)點(diǎn)存儲(chǔ)了控件中的某個(gè)回調(diào)方法,并且將該節(jié)點(diǎn)連接到基礎(chǔ)回調(diào)方法對(duì)應(yīng)的代碼塊下。
算法2回調(diào)函數(shù)調(diào)用鏈的回溯算法
Function: SearchBaseMethod
輸入: Callback。
輸出: BaseMethod。
begin
if Callback是基礎(chǔ)回調(diào)方法 then
begin
BaseMethod=Caller
return BaseMethod
end
if 找到該回調(diào)方法的Caller then
begin
SearchBaseMethod(Caller)
end
else then
begin
通過整理的回調(diào)方法總結(jié)中找到Caller
SearchBaseMethod(Caller)
end
end
圖6給出了一個(gè)控件特征模型的例子。onTouchEvent是SwipeRefreshLayout控件內(nèi)部的一個(gè)基礎(chǔ)回調(diào)函數(shù)。
圖6 特征模型樹示例
可以看出,其內(nèi)部是一個(gè)分支類型的控制結(jié)構(gòu),用ControlStructure類型的節(jié)點(diǎn)來表示,節(jié)點(diǎn)存儲(chǔ)了該控制結(jié)構(gòu)類型為Branch類型。該結(jié)構(gòu)有三個(gè)分支,分別用三個(gè)Statement類型的節(jié)點(diǎn)保存分支條件的信息。每一個(gè)分支條件下用Statement類型節(jié)點(diǎn)保存該分支下的代碼語句片段信息。由于UP分支下間接調(diào)用了onRefresh這個(gè)回調(diào)方法,因此用一個(gè)插樁類型的節(jié)點(diǎn)來存儲(chǔ)該回調(diào)方法信息。
代碼替換技術(shù)的方法流程如圖7所示,該方法流程主要分成兩個(gè)階段,第一階段是安卓支持庫特征模型的準(zhǔn)備,第二階段是基于第一階段支持庫特征模型的代碼替換技術(shù)。
圖7 代碼替換技術(shù)的方法流程
安卓支持庫特征模型的準(zhǔn)備分成兩個(gè)部分:一是回調(diào)方法調(diào)用鏈的構(gòu)建,二是回調(diào)方法特征模型的構(gòu)建?;卣{(diào)方法調(diào)用鏈的構(gòu)建是對(duì)于安卓支持庫控件中的每一個(gè)回調(diào)方法,找到該方法是在哪個(gè)基礎(chǔ)回調(diào)方法中被觸發(fā)的?;卣{(diào)方法特征模型的構(gòu)建是對(duì)控件中的每一個(gè)回調(diào)方法構(gòu)建特征模型樹。
支持庫特征模型的代碼替換分成三個(gè)部分,分別是基于支持庫特征模型的待替換代碼的定位、匹配和替換。首先需要對(duì)待分析的安卓應(yīng)用進(jìn)行掃描,對(duì)應(yīng)用中每一個(gè)Activity中的每一個(gè)回調(diào)方法進(jìn)行定位。如果該回調(diào)方法名與支持庫特征模型中的某個(gè)基礎(chǔ)回調(diào)方法名一致,則該回調(diào)方法是一個(gè)潛在待替換代碼。如果方法名不一致,則繼續(xù)定位應(yīng)用中的下一個(gè)回調(diào)方法。對(duì)于定位到的一個(gè)潛在待替換代碼,采用樹匹配的算法將潛在待替換代碼特征模型樹與控件中相對(duì)應(yīng)的特征模型樹進(jìn)行匹配,根據(jù)匹配結(jié)果判斷能否進(jìn)行替換。當(dāng)可以替換時(shí),生成相應(yīng)的替換建議。
為了對(duì)目標(biāo)代碼特征模型樹和安卓支持庫控件的特征模型樹進(jìn)行匹配比較,本文采用一種樹匹配算法(TM)來尋找兩棵特征模型樹間的最大匹配。S(SA,S1,S2,…,Si)和T(TB、T1,T2,…,Tj)分別是目標(biāo)代碼特征模型樹和安卓支持庫控件特征模型樹。其中:Si為樹S的第一層子樹的第i個(gè)節(jié)點(diǎn);Tj為樹T的第一層子樹的第j個(gè)節(jié)點(diǎn);SA和SB分別為樹的根節(jié)點(diǎn)。M為兩個(gè)特征模型樹的匹配,最大匹配M就是擁有最多節(jié)點(diǎn)對(duì)間的匹配,其最大匹配節(jié)點(diǎn)個(gè)數(shù)為M+1,加1是因?yàn)楦?jié)點(diǎn)也是匹配的節(jié)點(diǎn)對(duì)。為了求出M(
1) 當(dāng)兩個(gè)子樹中有一個(gè)為空是,匹配數(shù)M為0。
2) 當(dāng)兩個(gè)子樹都不為空時(shí),可分為以下幾個(gè)情形:
(1) 匹配Si和Tj,這時(shí)M(
(2) 匹配Si,這時(shí)M(
(3) 匹配Tj,這時(shí)M(
最后根據(jù)樹匹配算法的結(jié)果,計(jì)算出兩個(gè)特征模型之間的相似度:
式中:Node(S)和Node(T)分別表示樹S和樹T節(jié)點(diǎn)的個(gè)數(shù);TM(S,T)表示通過樹匹配算法計(jì)算后返回的兩棵樹間的最大匹配的節(jié)點(diǎn)個(gè)數(shù)。
圖8所示為兩個(gè)特征模型樹S和T,節(jié)點(diǎn)中的標(biāo)號(hào)表示該節(jié)點(diǎn)的類型,具有相同標(biāo)號(hào)的節(jié)點(diǎn)看成是相同節(jié)點(diǎn)。利用TM算法可以求出S和T之間的最大匹配為9,因此它們之間的相似度為0.9。
圖8 兩棵特征模型樹
因此兩棵特征模型樹的最大匹配節(jié)點(diǎn)個(gè)數(shù)越多,則兩棵樹的相似度就越大,也就越相似。對(duì)于本文的場(chǎng)景,需要在目標(biāo)代碼的特征模型樹中匹配到一棵子樹,該子樹即控件基礎(chǔ)回調(diào)方法的特征模型樹。所以最大匹配樹TM(S,T)滿足以下關(guān)系時(shí),目標(biāo)代碼可以用安卓支持庫里的控件進(jìn)行替換:
TM(S,T)=Node(T)
式中:T表示的是安卓支持庫控件代碼的特征模型樹;S表示的是目標(biāo)代碼的特征模型樹;Node(T)表示樹T節(jié)點(diǎn)的個(gè)數(shù);TM(S,T)表示通過樹匹配算法計(jì)算后返回的兩棵樹間的最大匹配的節(jié)點(diǎn)個(gè)數(shù)。
1) 功能實(shí)現(xiàn)代碼和監(jiān)聽器的替換。算法3描述了功能實(shí)現(xiàn)代碼和監(jiān)聽器的替換方法。該算法的輸入是兩個(gè)特征模型樹,分別是目標(biāo)代碼的特征模型樹CallbackFeatureTree和能夠用來替換該目標(biāo)代碼的支持庫控件中的基礎(chǔ)回調(diào)方法的特征模型樹BaseMethodFeatureTree。該算法用CodeSegmentMap來保存能夠被遷移的代碼片段,用NotReplaceSet來保存未被遷移的代碼片段。
算法3功能實(shí)現(xiàn)代碼和監(jiān)聽器的替換算法
Function: CallbackReplace
輸入: CallbackFeatureTree, BaseMethodFeatureTree。
輸出: CodeSegmentMap, NotReplaceCodeSet。
begin
for each Statement in CallbackFeatureTree
begin
if Statement 對(duì)應(yīng)到BaseMethodFeatureTree中的一個(gè)插樁點(diǎn) then
begin
for each widget in WidgetSet
begin
for each FeatureTreeRoot in widget
begin
if FeatureTreeRoot==BaseMethodFeatureTree.root then
begin
find Listener where Listener.contain(FeatureTreeRoot)
CodeSegmentMap.put(控件+監(jiān)聽器+插樁點(diǎn), Statement)
end
end
end
end
if Statement 對(duì)應(yīng)不到BaseMethodFeatureTree中的一個(gè)節(jié)點(diǎn) then
begin
NotRepalceCodeSet.put(Statement)
end
end
return CodeSegmentMap, NotReplaceCodeSet
end
在算法3中,以CallbackFeatureTree為基準(zhǔn),遍歷該特征模型樹上的Statement類型節(jié)點(diǎn),如果該節(jié)點(diǎn)直接對(duì)應(yīng)到BaseMethodFeatureTree中的一個(gè)非插樁節(jié)點(diǎn),那么說明新控件已經(jīng)包含這些語句。如果對(duì)應(yīng)的是一個(gè)插樁類型的節(jié)點(diǎn),說明該代碼語句是可以進(jìn)行遷移的。如果沒有對(duì)應(yīng)到任何一個(gè)節(jié)點(diǎn),則將該代碼語句記錄到NotReplaceCodeSet中,該代碼語句無法被遷移。對(duì)于監(jiān)聽器的搜索,首先找到BaseMethodFeatureTree是在哪個(gè)widget下的,然后在該Widget下找到哪一個(gè)listener的孩子節(jié)點(diǎn)中有一個(gè)孩子節(jié)點(diǎn)是該BaseMethod的,則該listener就是需要用來替換的監(jiān)聽器。最后以插樁點(diǎn)、監(jiān)聽器和該控件作為key鍵,Statement作為值,記錄到CodeSegmentMap中。
2) layout資源文件的替換。layout資源文件的替換分成兩步:第一步需要找到待替換的控件是什么;第二步需要找到資源文件是哪一個(gè)。在Callback所處的Activity中,可以找到該回調(diào)的監(jiān)聽器,然后向上回溯找到是哪一個(gè)對(duì)象set了這個(gè)監(jiān)聽器,則該對(duì)象就是需要找的待替換控件。一個(gè)Activity的資源文件是在該Activity中的onCreate方法內(nèi)部的setContentView方法中被加載的,該方法中的參數(shù)就是layout資源文件的名字。例如方法參數(shù)是R.layout.activity_main,則需要的資源文件名稱就是activity_main.xml。
接著需要在該資源文件中搜索到用戶控件的節(jié)點(diǎn),當(dāng)找到該節(jié)點(diǎn)后,將該節(jié)點(diǎn)用支持庫控件進(jìn)行替換。算法4描述了節(jié)點(diǎn)搜索的方法。
算法4搜索節(jié)點(diǎn)的算法
Function: depthSearch
輸入: root//xml文件的根節(jié)點(diǎn)。
輸出: child//待替換的控件節(jié)點(diǎn)。
begin
nodeStack.add(root)
while(!node.isEmpty)
begin
node=nodeStack.pop()
list=node.getChildren()
for child ∈ list
begin
nodeStack.add(child)
if child.name==layout.name then
begin
return child
end
end
end
end
算法4的實(shí)質(zhì)是樹的深度優(yōu)先搜索算法,由于Activity是由一組ViewGroup組成,所以從根節(jié)點(diǎn)出發(fā),依次遍歷每一組ViewGroup。對(duì)于每一組ViewGroup,依次搜索其每一個(gè)孩子節(jié)點(diǎn)分支,然后再以該孩子節(jié)點(diǎn)為起點(diǎn),照此方法直到搜索到某一分支的葉子節(jié)點(diǎn)時(shí)返回上一層繼續(xù)搜索。當(dāng)搜索到節(jié)點(diǎn)的名字屬性與待替換控件的名字屬性一致時(shí)就停止搜索,那么該位置的節(jié)點(diǎn)就是需要用新的控件去替換的位置。
本文給用戶提供的生成建議包含以下幾個(gè)部分:
1) 對(duì)于安卓應(yīng)用中可以進(jìn)行遷移的代碼,需要將支持庫控件、監(jiān)聽器、回調(diào)方法、可遷移代碼組合成完整的代碼片段。為了方便用戶的理解,該建議還增加了一些文字描述,將這些描述與組合后的代碼片段作為完整的可遷移代碼的替換建議。圖9給出了可遷移代碼建議的模板。
圖9 可遷移代碼的建議模板
在替換建議中,圖9模板先給出了替換前的代碼,并用文字指明了該代碼片段位于哪個(gè)Activity的哪個(gè)基礎(chǔ)回調(diào)方法,然后給出了遷移后的代碼示例,也用文字介紹了使用安卓支持庫中的哪個(gè)控件進(jìn)行替換,以及綁定的監(jiān)聽器和回調(diào)方法的信息。
2) 對(duì)于安卓應(yīng)用中未被遷移的代碼,本文同樣將未被遷移代碼和一些文字描述作為替換建議展示給用戶。圖10給出了未被遷移代碼建議的模板,其既給出了未被遷移的代碼片段,又給出了該代碼片段是位于該應(yīng)用中的哪個(gè)Activity里的哪個(gè)基礎(chǔ)回調(diào)方法下的。
圖10 未被遷移代碼的建議模板
3) 對(duì)于資源文件的替換,本文依舊給出原資源文件和替換后的資源文件,同時(shí)也通過文字描述進(jìn)行解釋,方便用戶的定位和替換。圖11給出了資源文件替換建議的模板,文字描述告訴用戶資源文件的名稱,以及是用支持庫中的哪個(gè)控件進(jìn)行替換。
圖11 資源文件替換的建議模板
圖12為基于本文方法所設(shè)計(jì)的工具界面,該工具的界面分成上下兩個(gè)部分。上半部分是用戶的輸入部分,用戶需要提供待分析的安卓項(xiàng)目的地址。下半部分是替換建議生成部分,分別從可遷移代碼、資源文件替換和未被遷移代碼三個(gè)方面給出替換建議。界面中的三個(gè)文本框分別用來展示這三個(gè)方面的替換信息。
圖12 工具界面
本文主要通過兩組實(shí)驗(yàn)來對(duì)本文方法在實(shí)際開發(fā)中的實(shí)用性進(jìn)行分析,這兩組實(shí)驗(yàn)分別是針對(duì)用戶開發(fā)應(yīng)用的支持庫替換實(shí)驗(yàn)和針對(duì)開源應(yīng)用的支持庫替換實(shí)驗(yàn)。
1) 針對(duì)用戶開發(fā)應(yīng)用的支持庫替換實(shí)驗(yàn)。本實(shí)驗(yàn)邀請(qǐng)了三位具有初級(jí)安卓開發(fā)經(jīng)驗(yàn)的同學(xué),讓這三位同學(xué)基于基礎(chǔ)回調(diào)方法實(shí)現(xiàn)例如下拉操作、側(cè)滑操作等功能的安卓應(yīng)用程序。表1列舉了給這三位同學(xué)安排的開發(fā)任務(wù)和提供的開發(fā)建議。
表1 實(shí)驗(yàn)的開發(fā)任務(wù)與開發(fā)建議
當(dāng)三位同學(xué)完成應(yīng)用開發(fā)后,利用本文方法對(duì)這三個(gè)應(yīng)用進(jìn)行替換分析。最終的分析結(jié)果如表2所示。
表2 實(shí)驗(yàn)結(jié)果
其中有兩個(gè)應(yīng)用有替換建議,有一個(gè)應(yīng)用沒有替換建議。本節(jié)將選取實(shí)驗(yàn)1和實(shí)驗(yàn)3,即一個(gè)有替換建議的例子和一個(gè)沒有替換建議的例子來對(duì)該實(shí)驗(yàn)進(jìn)行分析。
(1) 有替換建議的例子分析。該例子用onTouchEvent這個(gè)基礎(chǔ)回調(diào)方法實(shí)現(xiàn)下拉刷新的功能。圖13是該功能實(shí)現(xiàn)代碼的片段。該例子在onTouchEvent的Up分支下寫了一段更新子視圖的邏輯,該邏輯是當(dāng)手指下拉松開后,在子視圖的ListView中添加一行List。
圖13 功能實(shí)現(xiàn)代碼片段
通過本文方法分析后,安卓支持庫里的下拉刷新控件SwipeRefreshLayout可以對(duì)例子中用基礎(chǔ)回調(diào)方法onTouchEvent實(shí)現(xiàn)的下拉刷新代碼進(jìn)行替換。圖14是可遷移代碼的替換建議,可待遷移代碼放置在SwipeRefreshLayout的onRefresh回調(diào)方法中,該回調(diào)方法是在手指下拉手松后被間接觸發(fā)的。同時(shí)為Swipe-RefreshLayout綁定一個(gè)setOnRefreshListener監(jiān)聽器,用來監(jiān)聽事件。
圖14 可遷移代碼的替換建議
該例子的資源文件如圖15中替換前的代碼所示,該資源文件有兩個(gè)視圖,分別是子視圖ListView和父視圖LinearLayout,子視圖ListView在父視圖LinearLayout的內(nèi)部。對(duì)于替換后的資源文件如圖15中替換后的代碼所示, ListView這個(gè)子視圖在SwipeRefreshLayout控件節(jié)點(diǎn)的內(nèi)部。
圖15 資源文件的替換建議
(2) 無替換建議的例子分析。該例子沒有替換建議是因?yàn)樵摾訉⑾吕⑿碌倪壿嬘靡粋€(gè)類封裝好,所以在Activity中只保留了更新子視圖的邏輯,對(duì)于下拉刷新的邏輯則在那個(gè)類中進(jìn)行實(shí)現(xiàn)。圖16展示了Activity中處理業(yè)務(wù)邏輯的代碼片段。PullRefreshLayout是封裝好的類,在Activity中直接創(chuàng)建該類的一個(gè)對(duì)象,然后綁定相應(yīng)的監(jiān)聽器來監(jiān)聽動(dòng)作,在該例子中使用的是setRefreshListener。
圖16 無替換建議的實(shí)現(xiàn)代碼
2) 針對(duì)開源應(yīng)用的支持庫替換實(shí)驗(yàn)。本部分實(shí)驗(yàn)從開源社區(qū)上找了五個(gè)與安卓支持庫觸摸控件功能相似的開源安卓應(yīng)用項(xiàng)目,然后利用本文的方法對(duì)這五個(gè)安卓應(yīng)用項(xiàng)目進(jìn)行替換分析。本實(shí)驗(yàn)所選取的開源應(yīng)用的來源信息如表3所示,最終分析結(jié)果如表4所示。
表3 開源項(xiàng)目來源
表4 開源項(xiàng)目實(shí)驗(yàn)結(jié)果
可以看出,這五個(gè)案例中有2個(gè)案例有替換建議輸出,有3個(gè)案例沒有替換建議輸出。其中能夠進(jìn)行替換的項(xiàng)目均包含與支持庫控件相應(yīng)的,且用基本觸控邏輯來實(shí)現(xiàn)的事件回調(diào)方法。相對(duì)地,在三個(gè)沒有替換建議輸出的應(yīng)用中,有一個(gè)是使用了封裝好的功能類來實(shí)現(xiàn)對(duì)應(yīng)的功能,有兩個(gè)是直接使用了安卓支持庫中的控件。對(duì)于將功能封裝成一個(gè)類的情形,本文給出的方法同樣也可以進(jìn)行分析替換,但這樣做的意義不大。由于用戶已經(jīng)將一個(gè)功能封裝好了,那就沒有必要再去用安卓支持庫里的控件進(jìn)行替換,因此本文設(shè)計(jì)的方法對(duì)封裝好的功能類是不進(jìn)行分析的。
本文提出了一種面向安卓觸控類支持庫的應(yīng)用代碼替換技術(shù)。首先設(shè)計(jì)了一種用于描述安卓事件回調(diào)方法中代碼特征的元模型,其不僅能夠表示支持庫中的回調(diào)函數(shù),也可以用來描述目標(biāo)替換代碼中的方法代碼結(jié)構(gòu);接著基于安卓支持庫的特征模型和目標(biāo)替換代碼的特征模型進(jìn)行匹配;最后對(duì)于可以進(jìn)行替換的目標(biāo)代碼,設(shè)計(jì)了一種涵蓋功能實(shí)現(xiàn)、頁面布局的代碼替換建議生成方法。
本文方法仍然有一些不足的地方,需要在后續(xù)工作中不斷完善。第一,目前只是針對(duì)安卓觸摸類控件進(jìn)行替換分析,對(duì)于支持庫中的其他功能目前還無法做到替換分析;第二,本文方法的實(shí)現(xiàn)與用戶交互的功能還不夠完善,后續(xù)將對(duì)該方法實(shí)現(xiàn)進(jìn)行擴(kuò)展,增加與用戶交互的功能。