Xpoded 模組開發教程
Xpoded模組開發教程
當然,你可以去學習如何建立一個Xposed模組。所以你可以閱讀這篇教程(官方教程)去學習怎樣解決這個問題。這不僅僅講解如何新建模組、如何編寫模組,我們要往更深處思考,為什麼按照這些步驟,為什麼要新建這個類。如果你是“TL博士”那樣的人,那麼可以直接閱讀"Making the project an Xposed module" 這一章節。如果你想看完整個教程那麼你需要很好的理解能力。你將會花費時間去閱讀這篇文章,因為你不能但靠自己解決任何的問題。
一、修正通告
你可以在Github中找到重新建立紅色時鐘的例子,它包括狀態列時鐘的顏色改為紅色和新增一個笑臉,我選擇這個例子,因為它是一個相當小,但很容易看到變化。此外,它使用了一些由框架提供的基本方法。
二、Xposed是如何工作的
在你修改之前,你應該思考一下Xposed是如何工作的(如果你覺得太無聊可以跳過這一節)。方法如下:
有個程序叫做“Zygote ”。這是Android執行環境的的核心。每一個應用程式啟動都是通過它fork出來的。當手機被啟動就會執行/init.rc這個指令碼,這個程序會啟動/system/bin/app_process ,然後他會調載入所需的類並呼叫初始化函式。
現在說說Xposed什麼時候開始啟動。當您安裝了Xposed,它會把修改的app_process可執行檔案複製到/system/bin中。這個修改過的啟動程序增加了一個額外的jar包到classpath路徑,並在某些地方呼叫那裡的方法。例如,在虛擬機器剛剛建立後,在
這個jar包位於:/data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar和它的原始碼可以在這裡找到(here)。綜觀類XposedBridge,你可以看到的main 方法。這就是我上面寫的,這個類會在程序啟動之前被呼叫。在那時候執行一些初始化和模組的載入(我會在後面講解模組的載入)。
三、Method hooking/replacing
Xposed真正的能力在於可以hook函式的呼叫。當您修改完反編譯後的APK,你可以直接插入命令或者修改命令。但是您將需要重新編譯
XposedBridge有一個私有的本地方法叫hookMethodNative。此方法同樣的會在擴充套件的app_process程序中應用。它會將方法型別改變為“native ”並且將這方法的實現替換為它自己本地的泛型方法。這意味著任何時候呼叫這個被hook的方法,方法呼叫者並不知道已經被native 方法給代替了java層的方法。在該方法中,在XposedBridge的方法handleHookedMethod會被呼叫,用來傳遞需要的引數和自身的引用。並且這個方法負責呼叫給這個函式註冊過的回撥方法。他可以改變傳進來的引數,可以更改例項變數和靜態變數、可以呼叫其他方法、處理一下返回值,或者跳過任何你想跳過的程式碼。這是非常靈活的。
好了,理論講解完了。現在,讓我們建立一個模組!
四、建立專案
模組是正常的應用程式,只需用一些特殊的元資料和檔案。因此,首先建立一個新的Android專案。我假設你之前已經做到了這一點。如果不是,官方文件非常詳細。當被問及對於SDK中,我選擇了4.0.3(API15)。我建議你試試這個,因為已經做過測試了。你不需要建立一個activity ,因為修改沒有任何使用者介面。回答完這個問題之後,你應該有一個空白的專案。
五、Making the project an Xposed module
現在我們開始講解如何載入Xposed 模組 。需要按以下幾個步驟來進行的。
1.AndroidManifest.xml
Xposed Installer根據模組列表查詢模組程式的詳細資訊是通過meta-data這個標籤的。您可以這樣建立它:AndroidManifest.xml => Application => Application Nodes (在底部) => Add => Meta Data。這個名字應該是xposedmodule和值為true。讓資源為空。然後重複相同的操作填寫xposedminversion(見下文)和xposeddescription(你的模組的一個非常簡短的描述)。 XML原始碼現在看起來像這樣:
2.XposedBridgeApi.jar
接下來,讓專案認識XposedBridge API 。你可以從這裡下載XposedBridgeApi-<version>.jar 。把它複製到子資料夾名為lib目錄下。然後在其上單擊右鍵,選擇Build Path => Add to Build Path 。你需要將版本名插入到xposedminversion的宣告清單中。
可以選擇庫引用的方式。但是確保你的API類被正確地編譯到APK檔案中 ,否則你會得到一個IllegalAccessError 。通過引用libs 檔案(有“s”),通過Eclipse的簡單設定可以不用把XposedBridgeApi-<version>.jar 包含進去。
3.模組的實現
現在,您可以建立一個類的模組。我的被命名為“Tutorial ”,並在包de.robv.android.xposed.mods.tutorial裡面:
用於第一步驟中,我們將只做一些記錄以顯示被載入的模組。一個模組可以有幾個入口點。選擇哪一個取決於您要修改的內容。您可以在Android系統啟動時、當一個app將要被載入時、一個app的資源將要被初始化時,通過Xposed呼叫你模組中的一個函式。
本教程往下一點 ,您將學習必要的更改需要在一個特定的應用程式中完成,所以讓我們一起去看看“當一個新的應用程式被載入的時候”的入口點。所有入口點都標有IXposedMod的子介面。在這種情況下,你需要實現IXposedHookLoadPackage這個函式。它實際上只是一個方法加上一個引數,它提供了有關環境的實現模組的更多資訊。在我們的例子中,我們記錄的載入應用程式的名稱:
這個log方法將日誌輸出到標準logcat視窗中和/data/data/de.robv.android.xposed.installer/log/debug.log檔案中。(通過它很容易瞭解Xposed Installer )
4.assets/xposed_init
現在需要關心的事情是仍然丟失了XposedBridge 的一些類包含的入口點的追蹤。這是通過一個名為xposed_init的檔案,在assets 資料夾中用它的名字建立一個新的文字檔案。在這個檔案中,每一行包含一個完全限定的類名,就像這樣,這裡的是:de.robv.android.xposed.mods.tutorial.Tutorial
六、Trying it out
儲存檔案。然後執行你的專案為Android應用程式。因為這是你第一次安裝它,在使用它之前您需要啟用它。開啟Xposed安裝的應用程式,並確保你已經安裝了框架。然後去了“模組”選項卡。你應該找到你的應用程式在裡面。選中該複選框來啟用它。然後重新啟動。你不會看到有任何的變化,但如果你檢查日誌,你應該看到的東西是這樣的:
瞧!它這工作了。現在你有一個Xposed模組。它僅僅只有寫log的作用。。。
七、探索你的目標,找到一個方法來修改它
好了,現在開始這一話題,可以是非常不同,這取決於你想要做什麼。如果你以前有修改過的APK,你可能知道我在說什麼。在一般情況下,你首先需要獲得有關目標的實現的一些細節。在本教程中,目標是在狀態列的那個時鐘。它有助於知道狀態列和一部分SystemUI的細節。因此,讓我們開始搜尋。
可能性之一:反編譯。這將給你準確的實現 ,但是它是難以閱讀和理解的,因為你得到smali格式的程式碼。可能性二:獲取AOSP源(here )並且瀏覽他。但這官方ROM可能與你的不一樣,但在這種情況下,它是一個類似甚至相同的實現。第一,我想看看AOSP,看看是否是一樣的。如果我需要更多的細節,看看實際的反編譯程式碼。
你可以尋找與“時鐘”類名稱或包含該字串的類。下一步就是,尋找他所使用的資源和佈局。如果您下載了官方AOSP的程式碼,就可以開始在這裡開始尋找:frameworks/base/packages/SystemUI 。你會發現不少地方出現“時鐘”。這是正常的,的確會有不同的方式來實現修改。請記住,你僅僅可以hook方法 。所以,你必須要找到一個可以在他之前、之後、或全部替換可以插入一些程式碼的地方。你應該hook 住儘可能具體的方法,而不是那些會被呼叫上千次的方法,去避免效能問題和意想不到的副作用。
在這種情況下,您可能會發現這個layout佈局res/layout/status_bar.xml 包含了一個自定義檢視類:com.android.systemui.statusbar.policy.Clock。多個想法可能會現在你的頭腦中。文字顏色的定義是通過textAppearance屬性,所以最簡單的方法就是改變它,將會改變外觀的定義 。然而,這可能有效也可能無效(因為它可能存在於更深的native 程式碼中)。更換佈局狀態列將是可能的,但是你們只可以做最小的變化去更改他,相反,看看這個類。有一個叫updateClock方法,它看上去會被每分鐘呼叫去更新時間
看起來完美的修改,因為它是這似乎是唯一設定文字時鐘的非常具體的方法。假如我們改變了這個clock的顏色或者字型,那麼任何呼叫這個方法的都會受此影響。就達成我們的需求了,我們立刻行動.
(單獨的文字顏色,這裡有一種更好的方式.看到“修改佈局”的例子在 "Replacing resources".)
八、使用反射來查詢和hook一個方法
什麼是我們已經知道的?我們找到一個方法:updateClock 在com.android.systemui.statusbar.policy.Clock,我們要攔截它。我們發現這個類是在SystemUI sources 裡面,所以它只能對SystemUI的程序有效。修改一些其他的歸屬於框架的類是會任何地方都有效的。如果我們試圖獲得任何資訊和直接引用此類在handleLoadPackage 方法中,這會失敗的,因為它們在不同的程序中。所以,當一個指定的包將被載入時,讓我們來實現一個執行特定程式碼的條件。
使用引數,我們可以很容易地檢查,我們是否選中正確的包。一旦我們證實,我們可以從包中獲得這個類通過也是從這個變數引用的類載入器。現在我們來看看在com.android.systemui.statusbar.policy.Clock類及其updateClock方法,可以告訴XposedBridge把它hook了:
findAndHookMethod是helper 類的一個方法。請注意,它是靜態匯入的,如果你配置了它描述的連結頁面就會自動新增 。此方法通過ClassLoader 在ClassLoader 包中查詢Clock類 。然後,它會在裡面尋找updateClock方法。如果這種方法有任何引數,那你就必須列出這些引數的型別。不同的情況不一樣的處理,但我們的方法沒有任何引數,可以跳過這個假設。作為最後一個引數,你需要提供XC_MethodHook類的實現。對於較小的改動,就可以使用一個匿名類。如果你有太多的程式碼,最好建立一個普通的類,只在這裡建立例項。隨後,helper 將盡一切方法hook住以上的函式。
你可以重寫XC_MethodHook的兩個方法。您可以同時覆蓋,甚至不做操作,但後者是完全沒有意義的。這兩個方法是beforeHookedMethod和afterHookedMethod。這不是太難猜測,這兩個方法會在原始的方法的之前和之後執行。您可以使用beforeHookedMethod 方法來評價/篡改方法呼叫的引數(通過param.args) ,甚至阻止呼叫原來的方法(傳送自己的結果)。afterHookedMethod 方法可以用來做基於原始方法的結果的事情。您還可以用它來操縱結果 。當然,你可以新增自己的程式碼,它將會準確地在原始方法的前或後執行。
(如果你想完全取代方法,看看子類XC_MethodReplacement相反,你只需要覆蓋replaceHookedMethod )
XposedBridge保留著一個記錄了每個已經hook了的函式的註冊回撥函式 的列表。那些具有最高優先順序(如hookMethod定義)會首先呼叫。原始方法始終是優先順序最低的。所以,假如你hook了一個函式並註冊了回撥A(PRIO高點)和B(PRIO預設值),那麼每當hook的方法被呼叫,控制流將是這樣的:A.before - > B.before - >原始的方法 - > B.after - > A.after。因此,A修改了的引數,B是可以看到的,這樣可以在傳遞給原始方法之前多步地改變它。原方法的結果首先會被B處理,但是這個原始方法最終返回的結果是由A來決定的。
九、最後一個步驟:執行自己的程式碼在方法呼叫之前/之後
好了,你現在有一個每次updateClock 呼叫時,都會被呼叫的方法,而且可以精確到原始方法的前後(你已經在SystemUI 的程序裡面了)。現在,讓我們來修改一些東西。
首先要檢查:我們有沒有得到具體的時鐘物件?是的,我們有,它在param.thisObject引數裡。因此,如果該方法被myClock.updateClock()呼叫,然後param.thisObject將會使myClock這個物件。
下一步:我們可以做什麼用的時鐘?這個Clock 類是不可以利用的,你可以不轉換param.thisObject變成類(甚至不要去嘗試)。然而,它繼承自TextView的。所以,你可以使用像的setText,gettext和setTextColor的方法,一旦你已經把Clock引用對映成TextView。這些改變應該在原始方法呼叫後去設定新的時間。由於在方法呼叫前沒有事做,我們就不考慮 beforeHookedMethod。呼叫 (empty) "super" 方法是沒有必要的。所以不要重寫這方法。
這是完整的原始碼 :
十、對結果滿意
現在安裝/重新啟動您的應用程式。正如你在執行之前已經在XposedInstaller 啟用了它,你就不需要再來一次了,重新啟動就足夠了。不過,如果你想使用它停用這個紅色時鐘的例子。兩者都使用預設的優先順序給他們的updateClock處理程式,那麼你不知道哪一個會勝出(它實際上取決於處理方法的字串表示形式,但並不依賴於此)。
十一、結論
我知道,這個教程很長。但我希望你現在不僅可以實現一個綠色的時鐘,還可以實現和這個完全不同的東西。找到好的方法來hook是一個經驗上的問題,所以開始的東西比較容易。嘗試剛開始就多使用日誌功能去確保被呼叫的是預期的事件。現在:玩得開心!