Markdown版本筆記 | 個人GitHub首頁 | 個人博客 | 個人微信 | 個人郵箱 |
---|---|---|---|---|
MyAndroidBlogs | baiqiantao | baiqiantao | bqt20094 | baiqiantao@sina.com |
官方教程
Xposed框架中文站
Xposed插件官方網站
FAQ,建議開發前仔細閱讀一遍php
framework下載地址
xposed依賴的maven地址 , 在jcenter上的下載地址
做者rovo89的GitHub主頁,裏面有5個項目,包括XposedInstaller、Xposed、XposedBridge、XposedTools、android_art。html
因爲Android 5.0以上採用ART,而5.0如下默認採用Dalvik,因此是有兩個版本的Xposed,一些下載連接:
Android 4.0.3-4.4官網地址 , Android 5.0以上官網地址(訪問很是慢)
Xposed_installer_3.1.4百度網盤下載地址 , Xposed_installer_3.1.5官網下載地址java
Xposed 是一款能夠在不修改APK的狀況下影響程序運行(修改系統)的框架服務。基於它能夠製做出許多功能強大的模塊,且在功能不衝突的狀況下同時運做。好比:直接把APP的界面改爲本身想要的樣子(好比修改文字、背景),去掉界面裏不喜歡的東西(好比廣告、彈窗),自動搶紅包,消息防撤回,步數修改、修改定位等等,簡直酷得不行,網上有不少插件做者開發出來的優秀插件。android
Xposed 理論上可以hook到系統任意一個Java進程,能夠說Xposed真的能夠隨心所欲,任何事情均可以作!git
重要提醒:使用Xposed是須要Root權限的!
PS:因爲Xposed是從底層hook,因此須要root權限
,而且每次更新都要從新啓動設備
。github
Xposed並非什麼新東西了,好幾年前就有了,之前看到搞機(基)的人都以爲很牛逼哄哄,刷系統,root下,改下系統UI,用各類各樣的插件模塊改什麼什麼,屌得不行。api
真正開始學的時候,其實Xposed並無想象中那麼複雜,原理和相關的API都很簡單,難的是逆向,怎麼去實現你要Hook的功能:反編譯,調試輸出,堆棧跟蹤,抓包等等,在這個過程當中你須要去分析不少不少東西,猜想調試,有時候折騰幾天可能毫無進展,不過也會收穫更多,好比你本身開發APP的時候也會慢慢開始考慮安全相關的東西。安全
The most important thing is finding good methods to hook微信
插件用起來是挺爽的,不過呢,由於Xposed擁有最高權限,若是不法分子在插件裏植入了惡意代碼,好比登陸劫持,偷偷採集你的帳號密碼發送到他們的手裏,若是涉及到了金錢,就很恐怖啦,因此在使用Xposed插件的時候,儘可能選那些開源的,並進行代碼review,看是否存在惡意代碼,再進行安裝體驗。架構
白話總結
Android基於Linux,第一個啓動的進程天然是init進程
,該進程會啓動全部Android進程的父進程——Zygote(孵化)進程
,該進程的啓動配置在/init.rc
腳本中,而Zygote進程對應的執行文件是/system/bin/app_process
,該文件完成類庫的加載以及一些函數的調用工做。在Zygote進程建立後,再fork出SystemServer進程和其餘進程。而Xposed Framework呢,就是用本身實現的app_process替換掉了系統本來提供的app_process,加載一個額外的jar包,而後入口從原來的com.android.internal.osZygoteInit.main()
被替換成了de.robv.android.xposed.XposedBridge.main()
,而後建立的Zygote進程就變成Hook的Zygote進程了,然後面Fork出來的進程也是被Hook過的。這個Jar包在/data/data/de.rbov.android.xposed.installer/bin/XposedBridge.jar
。
Before beginning with your modification, you should get a rough idea how Xposed works (you might skip this section though if you feel too bored).
在開始修改以前,你應該大體瞭解Xposed如何工做(若是你以爲太無聊,你能夠跳過這一部分)
There is a process that is called "Zygote". This is the heart of the Android runtime. Every application is started as a copy ("fork") of it. This process is started by an /init.rc
script when the phone is booted. The process start is done with /system/bin/app_process
, which loads the needed classes and invokes the initialization methods.
有一個叫作「Zygote」的進程。這是Android運行時的核心。每一個應用程序都做爲它的副本(「fork」)啓動。啓動手機時,此進程由
/init.rc
腳本啓動。進程啓動是使用/system/bin/app_process
完成的,它會加載所需的類並調用初始化方法。
This is where Xposed comes into play. When you install the framework, an extended app_process executable is copied to /system/bin
. This extended startup process adds an additional jar to the classpath and calls methods from there at certain places. For instance, just after the VM has been created, even before the main
method of Zygote has been called. And inside that method, we are part of Zygote and can act in its context.
這就是Xposed發揮做用的地方。安裝框架時,會將擴展的
app_process
可執行文件複製到/system/bin
。這個擴展的啓動過程在類路徑中添加了一個額外的jar,並在某些地方從那裏調用方法。例如,就在建立VM以後,甚至在調用Zygote的main
方法以前。在該方法中,咱們是Zygote的一部分,能夠在其context下行動。
The jar is located at /data/data/de.robv.android.xposed.installer/bin/XposedBridge.jar
and its source code can be found here. Looking at the class XposedBridge, you can see the main
method. This is what I wrote about above, this gets called in the very beginning of the process. Some initializations are done there and also the modules are loaded (I will come back to module loading later).
jar位於,其源代碼可在此處找到。查看類
XposedBridge
,您能夠看到main
方法。這就是我上面寫的內容,這個在進程一開始就被調用了。在那裏進行了一些初始化,而且還加載了模塊(稍後我將回到模塊加載)。
What really creates the power of Xposed is the possibility to "hook" method calls. When you do a modification by decompiling an APK, you can insert/change commands directly wherever you want. However, you will need to recompile/sign the APK afterwards and you can only distribute the whole package.
咱們創造Xposed真正具備的力量是"hook"方法調用具備無限可能性。經過反編譯APK進行修改時,能夠直接在任意位置插入/更改命令。可是,您須要在以後從新編譯/簽署APK,而且您只能分發整個包。
With the hooks you can place with Xposed, you can't modify the code inside methods (it would be impossible to define clearly what kind of changes you want to do in which place). Instead, you can inject your own code before and after methods, which are the smallest unit in Java that can be addressed clearly.
使用能夠放置在Xposed的hooks,你並不能修改
方法內
的代碼(由於你沒法清楚地定義你想在哪一個地方作什麼樣的改變)。相反,您能夠在方法以前和方法以後
注入本身的代碼,這是Java中能夠清楚解決的最小單元。
XposedBridge has a private, native method hookMethodNative
. This method is implemented in the extended app_process
as well. It will change the method type to "native" and link the method implementation to its own native, generic method. That means that every time the hooked method is called, the generic method will be called instead without the caller knowing about it.
XposedBridge有一個私有的本地方法
hookMethodNative
。此方法也在擴展的app_process
中實現。它會將方法類型更改成「native」,並將方法實現link到其本身的native、generic(泛型)方法。這意味着每次調用hooked方法時,都會調用generic方法,而不會讓調用者知道它。
In this method, the method handleHookedMethod
in XposedBridge is called, passing over the arguments to the method call, the this
reference etc. And this method then takes care of invoking callbacks that have been registered for this method call. Those can change the arguments for the call, change instance/static variables, invoke other methods, do something with the result... or skip anything of that. It is very flexible.
在此方法中,XposedBridge中的
handleHookedMethod
方法會被調用,將參數傳遞給方法調用,this
引用等。而後,此方法負責調用已爲此方法調用註冊的回調。這些能夠更改調用的參數,更改實例/靜態變量,調用其餘方法,對結果執行某些操做...或者跳過任何內容。它很是靈活。
Ok, enough theory.
好的,上面的理論已經足夠了。
Xposed is a framework for modules that can change the behavior of the system and apps without touching any APKs.
Xposed是一個能夠在不觸及任何APK的狀況下改變系統和應用程序的行爲的模塊框架。
That's great because it means that modules can work for different versions and even ROMs without any changes (as long as the original code was not changed too much). It's also easy to undo. As all changes are done in the memory, you just need to deactivate the module and reboot to get your original system back.
這很好,由於它意味着模塊能夠在不作任何更改的狀況下爲不一樣的版本甚至ROM工做(只要原始代碼沒有太多改變)。 撤消也很容易。 因爲全部更改都在內存中完成,您只須要停用模塊並從新啓動便可恢復原始系統。
There are many other advantages, but here is just one more: Multiple modules can do changes to the same part of the system or app. With modified APKs, you to decide for one. No way to combine them, unless the author builds multiple APKs with different combinations.
還有許多其餘優勢,但這裏只有一個:多個模塊能夠對系統或應用程序的同一部分進行更改。 使用通過修改的APK,您能夠選擇一個。 除非做者使用不一樣的組合構建多個APK,不然沒法組合它們。
Note that this only works with root access on Android 4.0.3 up to Android 4.4.
請注意,這僅適用於Android 4.0.3到Android 4.4 的具備root訪問權限的設備。
目前最新版原本到3.1.5,支持Android7.0、7.一、Android8.x框架安裝。
新版本的主要的功能更新是它帶來了適配於當前設備的Xposed框架直接下載安裝
的選項,固然也包括卸載選項,這樣就不用本身再去針對CPU和系統版本
手動篩選須要刷入的框架版本了,減小了出錯的概率,方便新手用戶。
新版本的xposed框架主程序增長一些檢查步驟,可以在出錯的時候給出更多的提示,因此強烈推薦更新,而且做者也鼓勵你們更新到新版本,各方面都會比舊版本要好一些。
參考了 官方教程 和 WrBug的簡書 和 慢啄網的文章 和 coder-pig的文章
簡單來講就是,須要如下幾個基本步驟:
meta-data
完整類名
下面是模擬器下的配置,和實體機的重要區別爲
- 模擬器爲 x86 架構,實體機通常爲 ARM 結構,其所需安裝的 Xposed framework 是不同的
- 模擬器通常具備 root 權限,實體機通常沒有 root 權限,且很難使用工具對手機 root,這是一個很棘手的問題
- 在模擬器上你能夠肆無忌憚的隨意折騰,可是在實體機上就要顧忌不少,由於使用 Xposed 有可能讓你的手機變磚
- 模擬器上安裝第三方插件不用擔憂隱式泄露、資金安全,可是在實體機上你可就要悠着點了
一、根據Android設備系統版本到 framework官網 下載對應的框架,選擇.zip
結尾的文件,例如 sdk22 系統 x86 環境最新版本下載地址爲 xposed-v89-sdk22-x86.zip,下載完成後運行模擬器,將 zip 包拖到模擬器界面便可刷入,完成後重啓模擬器
。
二、到 installer官網 下載安裝XposedInstaller
應用(可能比較慢),例如最新的 XposedInstaller_3.1.5.apk,也可百度搜索後下載安裝。安裝完畢後打開此app,會提示你沒有激活,再次重啓
後進入剛剛安裝的app,會提示已激活,即安裝成功。
咱們能夠經過Xposed installer右上角菜單中的"軟重啓"來重啓設備,固然此APP還提供了一切經常使用的其餘功能。
重要提示:刷機前爲了以防萬一,最好先備份數據,以避免刷入後手機沒法啓動等問題。
基本條件:手機已經root而且已經刷入第三方recovery
步驟和上面基本同樣,首先下載並安裝好XposedInstaller
而後下載和手機cpu對應的Framework,並放在手機存儲卡上
而後重啓到recovery
模式,刷入Framework
再重啓便可
因爲Xposed項目每次安裝都要從新啓動,在真機上是很是耗時間的,建議選擇
Genymotion
模擬器。
在模塊中添加依賴:
compileOnly 'de.robv.android.xposed:api:82' //xposed依賴,注意這個版本號和framework版本號並非一致的 compileOnly 'de.robv.android.xposed:api:82:sources' //非必須
Every Xposed modules needs to reference the API.
The Xposed Framework API is published on Bintray/jCenter,maven 地址在這裏
It is very important that you use compileOnly
instead of compile
! The latter would include the API classes in your APK, which can cause issues especially on Android 4.x. Using compileOnly
just makes the API classes usable from your module, but there will only be references to them in the APK. The actual implementation will be provided when the user installs the Xposed Framework.
使用
compileOnly
而不是compile
是很是重要的! 後者將在您的APK中包含API類,這可能會致使問題,特別是在Android 4.x上。 使用compileOnly
只是使API類能夠從您的模塊中使用,但在APK中只會引用它們。當用戶安裝 Xposed Framework 時,將提供實際的實現。
Please make sure to disable Instant Run (File -> Settings -> Build, Execution, Deployment -> Instant Run
), otherwise your classes aren't included directly in the APK, but loaded via a stub application which Xposed can't handle.
請確保禁用Instant Run,不然您的類不會直接包含在APK中,而是經過Xposed沒法處理的stub應用程序加載。
API versions
Generally, the API version
equals the Xposed version
that it was built on. However, only some framework
changes actually result in API
changes, as you can see in the change log. I only publish a new API version
when there were API
changes, and I try to keep them compatible with existing modules as good as possible. So when you build a module with API version 82
, it will most likely also work withXposed version 90
.
一般。可是,只有個別一些 framework 更改實際上會致使API更改,您能夠在更改日誌中看到。我只在API更改時發佈了新的API版本,並嘗試儘量地使它們與現有模塊兼容。所以,當您使用API版本82構建模塊時,它極可能也適用於Xposed版本90。
I always recommend that end-users use the latest Xposed version, so there's nothing wrong with using the highest API version that's available. You should usually set the xposedminversion
in your AndroidManifest.xml
to the API version that you use. If you depend on a framework change that didn't cause an API change (e.g. because a certain bug has been fixed), feel free to set you xposedminversion
to the least Xposed version that your module requires.
我老是建議終端用戶使用最新的Xposed版本,所以使用可用的最高API版本沒有任何問題。您一般應該將AndroidManifest.xml中的xposedminversion設置爲您使用的API版本。若是您依賴於不會致使API更改的framework更改(例如,由於已修復某個錯誤),請隨意將xposedminversion設置爲模塊所需的最少Xposed版本。
If you want to support ROMs before Lollipop, you can only use API version 53, as the latest Xposed version for Android 4.x was 54. Note that the sources jar provided for this version doesn't match the actual implementation, it only makes the documentation available.
若是你想在Lollipop以前支持ROM,你只能使用API版本53,由於Android 4.x的最新Xposed版本是54.請注意,爲此版本提供的源jar與實際實現不匹配,它只會使可用的文件。
在AndroidManifest.xml中添加以下配置:
<meta-data android:name="xposedmodule" android:value="true"/> <meta-data android:name="xposeddescription" android:value="這是對你使用xposed能完成功能的簡要描述"/> <meta-data android:name="xposedminversion" android:value="89"/>
xposedmodule:是不是一個xpose模塊(是否啓用)
xposeddescription:a very short description of your module
xposedminversion:the API version from the previous step,注意這裏的版本號要和安裝的framework版本號一致
配置完成後,安裝到模擬器,狀態欄彈出以下提示:
咱們點擊軟重啓便可。
例如咱們有這麼一個簡單的Activity
public class XposedActivity extends Activity { TextView textView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); textView = new TextView(this); setContentView(textView); } }
下面經過 xposed 給 textView 設置文本 Hello Xposed。
思路:Xposed hook onCreate方法,在該方法執行完後獲取TextView的實例,經過setText方法設置文本。
A module can have a few entry points. Which one(s) you choose depends on the what you want to modify. You can have Xposed call a function in your module when the Android system boots, when a new app is about to be loaded, when the resources for an app are initialised and so on.
一個模塊能夠有幾個入口點。 您選擇哪個取決於您要修改的內容。 當Android系統啓動,即將加載新應用程序,應用程序的資源等初始化時,您可讓Xposed調用模塊中的方法。
Keep in mind that you can "only" hook methods. So you have to find a place where you can insert some code to do the magic either before, after or replacing a method. You should hook methods that are as specific as possible, not ones that are called thousands of times to avoid performance issues and unintended side-effects.
請記住,您「僅」能夠hook方法。所以,您必須找到一個位置,以便您能夠在方法以前、以後或替換方法時在其中插入一些代碼執行magic。您應該hook儘量具體的方法,而不是那些被調用數千次的方法,以免性能問題和意外的反作用。
Using reflection to find and hook a method
新建一個類XposedInit實現IXposedHookLoadPackage
:
public class XposedInit implements IXposedHookLoadPackage { private static String HOOK_PACKAGE_NAME = "com.my.bqt"; private static String HOOK_CLASS_NAME = "com.my.bqt.xposed.XposedActivity"; @Override public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) { Log.i("bqt", "【handleLoadPackage】" + lpparam.packageName);//任何一個app啓動時都會調用 if (lpparam.packageName.equals(HOOK_PACKAGE_NAME)) { //匹配指定的包名 //參數:String className, ClassLoader classLoader, String methodName, Object... parameterTypesAndCallback XposedHelpers.findAndHookMethod(lpparam.packageName, lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Log.i("bqt", "【afterHookedMethod】" + param.method.getName()); //當Hook成功後回調 Class c = lpparam.classLoader.loadClass(HOOK_CLASS_NAME);//不能經過Class.forName()來獲取Class,在跨應用時會失效 Field field = c.getDeclaredField("textView"); field.setAccessible(true); TextView textView = (TextView) field.get(param.thisObject);//param.thisObject爲執行該方法的對象,在這裏指Activity textView.setText("Hello Xposed"); //能夠調用param.setResult()設置方法的返回值! } }); } } }
官方文檔相關介紹
XposedHelpers.findAndHookMethod
is a helper function. This method looks up the class_A
using the ClassLoader_B
for the package_C
. Then it looks for the method_D
in it. If there were any parameters_E
to this method, you would have to list the types (classes) of these parameters afterwards. As the last argument, you need to provide an implementation of the XC_MethodHook_F
class. For smaller modifications, you can use a anonymous class. If you have much code, it's better to create a normal class and only create the instance here. The helper will then do everything necessary to hook the method as described above.
XposedHelpers.findAndHookMethod
是一個helper 函數。 這個方法使用classLoader_B
爲package_C
查找class_A
。 而後它在其中查找method_D
。 若是此方法有任何parameters_E
,則必須在以後列出這些參數的類型(classes)。 做爲最後一個參數,您須要提供XC_MethodHook_F
類的實現。 對於較小的修改,您可使用匿名類。 若是你有不少代碼,最好建立一個普通的類,只在這裏建立實例。 而後,幫助程序將執行hook方法所需的全部操做,如上所述。
There are two methods in XC_MethodHook
that you can override. You can override both or even none, but the latter makes absolutely no sense These methods are beforeHookedMethod
and afterHookedMethod
. It's not too hard to guess that the are executed before/after the original method.
XC_MethodHook
中有兩種方法能夠覆蓋。 你能夠覆蓋二者,甚至沒有,但若是兩個方法都不重寫那沒任何意義。 這些方法是beforeHookedMethod
和afterHookedMethod
。 猜想在原始方法以前/以後執行並非很難。
You can use the "before" method to evaluate/manipulate the parameters of the method call (via param.args
) and even prevent the call to the original method (sending your own result). The "after" method can be used to do something based on the result of the original method. You can also manipulate the result at this point. And of course, you can add your own code which should be executed exactly before/after the method call.
您可使用「before」方法評估/操做方法調用的參數(經過
param.args
),甚至阻止調用原始方法(發送您本身的結果)。 「after」方法可用於根據原始方法的結果執行某些操做。 您也能夠在此處操縱結果。 固然,您能夠添加本身的代碼,這些代碼應該在方法調用以前/以後執行。
If you want to replace a method completely, have a look at the subclass XC_MethodReplacement
instead, where you just need to override replaceHookedMethod
.
若是你想徹底替換方法,使請用子類
XC_MethodReplacement
代替,你只須要覆蓋replaceHookedMethod
。
XposedBridge keeps a list of registered callbacks for each hooked method. Those with highest priority (as defined in hookMethod) are called first. The original method has always the lowest priority. So if you have hooked a method with callbacks A (prio high) and B (prio default), then whenever the hooked method is called, the control flow will be this: A.before -> B.before -> original method -> B.after -> A.after
. So A could influence the arguments B gets to see, which could further change them before passing them on. The result of the original method can be processed by B first, but A has the final word what the original caller gets.
XposedBridge爲每一個掛鉤方法保留一個已註冊的回調列表。 優先級最高的那些(在hookMethod中定義)首先被調用。 原始方法始終具備最低優先級。 所以,若是您使用回調A(高優先級)和B(默認優先級)hook了一個方法,那麼每當調用hooked方法時,控制流將是:
A.before -> B.before -> original method -> B.after -> A.after
。 因此A能夠影響B看到的參數,這能夠在傳遞它們以前進一步改變它們。原始方法的結果由B首先處理,但A具備原始調用者所能得到結果的最終話語權。
在上面的案例中,咱們沒有用Class.forName()
來獲取class,是什麼緣由呢?咱們先來看看Class.forName()的源碼:
public static Class<?> forName(String className) throws ClassNotFoundException { return forName(className, true, VMStack.getCallingClassLoader()); //注意ClassLoader的獲取方式 }
public static Class<?> forName(String name, boolean initialize, ClassLoader loader) throws ClassNotFoundException { if (loader == null) { loader = BootClassLoader.getInstance(); } Class<?> result; try { result = classForName(name, initialize, loader); } catch (ClassNotFoundException e) { Throwable cause = e.getCause(); if (cause instanceof LinkageError) { throw (LinkageError) cause; } throw e; } return result; }
static native Class<?> classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException;
在一個參數的方法中,ClassLoader是經過VMStack.getCallingClassLoader()
獲取的。VMStack是一個虛擬機棧,在Android系統中,每一個應用都有一個獨立的虛擬機,因此VMStack.getCallingClassLoader()是獲取當前應用的ClassLoader
,即xposed項目的ClassLoader,因此,若是使用Class.forName("xxx.xxx.xxxActivity")
獲取不一樣應用的類會提示找不到,這就是須要經過lpparam.classLoader.loadClass()
獲取的緣由。
Provide a hint for XposedBridge which classes contain such entry points.
新建assets
文件夾,文件夾下新建xposed_init
文件(文件名固定),在文件中填寫 XposedInit 的 fully qualified class name:
com.my.bqt.xposed.XposedInit
XposedBridge會從assets目錄中的xposed_init文件中獲取入口點
而後安裝到模擬器上,而後重啓模擬器
,重啓後打開app,這時textView將顯示Hello Xposed
Save your files. Then run your project as Android application. As this is the first time you install it, you need to enable it before you can use it. Open the Xposed Installer app and make sure you have installed the framework. Then go to the "Modules" tab. You should find your app in there. Check the box to enable it. Then reboot.
保存文件。 而後將您的項目做爲Android應用程序運行 因爲這是您第一次安裝它,所以您須要啓用它才能使用它。 打開Xposed Installer應用程序並確保已安裝框架。 而後轉到「模塊」選項卡。 你應該在那裏找到你的應用程序。 選中此框以啓用它。 而後重啓。
一個基本的、也是Xposed最主要的功能已經演示完成了。
類結構:
//Marker interface for Xposed modules. Cannot be implemented directly interface IXposedMod {}
IXposedMod 接口有四個子接口,咱們可使用的有三個,經常使用的也就兩個,具體以下:
IXposedHookZygoteInit:在Zygote啓動時調用,用於系統服務的Hook回調方法initZygote()
handleLoadPackage
方法會在執行Application.onCreate()
方法前調用,而且攜帶一個XC_LoadPackage.LoadPackageParam
類型的參數返回過來。android.content.pm.ApplicationInfo
對象。IXposedHookLoadPackage的定義
//Get notified when an app ("Android package") is loaded. //This is especially useful to hook some app-specific methods. //This interface should be implemented by the module's main class. Xposed will take care of registering it as a callback automatically. public interface IXposedHookLoadPackage extends IXposedMod { //This method is called when an app is loaded. It's called very early, even before Application#onCreate is called. //Modules can set up their app-specific hooks here. //@param lpparam Information about the app. //@throws Throwable Everything the callback throws is caught and logged. void handleLoadPackage(LoadPackageParam lpparam) throws Throwable; }
XC_LoadPackage.LoadPackageParam的定義
//This class is only used for internal purposes, except for the LoadPackageParam subclass. public abstract class XC_LoadPackage extends XCallback implements IXposedHookLoadPackage { /Wraps information about the app being loaded. public static final class LoadPackageParam extends XCallback.Param { public String packageName; //The name of the package being loaded public String processName; // The process in which the package is executed public ClassLoader classLoader; //The ClassLoader used for this package public ApplicationInfo appInfo; //More information about the application being loaded public boolean isFirstApplication; //Set to true if this is the first (and main) application for this process } }
//Callback class for method hooks. //Usually, anonymous subclasses of this class are created which override beforeHookedMethod and/or afterHookedMethod. public abstract class XC_MethodHook extends XCallback { //Creates a new callback with default priority. public XC_MethodHook() { super(); } //Creates a new callback with a specific priority. public XC_MethodHook(int priority) { super(priority); } //Called before the invocation of the method. //You can use MethodHookParam#setResult and MethodHookParam#setThrowable to prevent the original method from being called. //Note that implementations shouldn't call super(param), it's not necessary. //@param param Information about the method call. //@throws Throwable Everything the callback throws is caught and logged. protected void beforeHookedMethod(MethodHookParam param) throws Throwable {} //Called after the invocation of the method. protected void afterHookedMethod(MethodHookParam param) throws Throwable {} //Wraps information about the method call and allows to influence it. public static final class MethodHookParam extends XCallback.Param { public Member method; //The hooked method/constructor public Object thisObject; //The 【this】 reference for an instance method, or null for static methods public Object[] args; //Arguments to the method call private Object result = null; //the result of the method call。 經過 get/set 方法訪問 private Throwable throwable = null; //the exception thrown of the method call, 經過 get/set/has 方法訪問 boolean returnEarly = false; //Returns the result of the method call, or throws the Throwable caused by it. public Object getResultOrThrowable() throws Throwable { if (throwable != null) throw throwable; return result; } } //An object with which the method/constructor can be unhooked. public class Unhook implements IXUnhook<XC_MethodHook> { private final Member hookMethod; // the method/constructor that has been hooked @Override public XC_MethodHook getCallback() { return XC_MethodHook.this; } @Override public void unhook() { XposedBridge.unhookMethod(hookMethod, XC_MethodHook.this); } } }
MethodHookParam中包含與調用方法有關的信息,比較關注的是這個thisObject
,表明調用該方法的對象實例,若是是靜態方法的話,返回一個Null,好比調用onCreate()方法的是MainActivity,得到的天然是MainActivity的實例。
這個是在資源佈局初始化
時進行hook,須要實現handleInitPackageResources()
方法,在初始化時調用,resparam有兩個字段,一個是應用包名,另外一個是資源相關的android.content.res.XResources
,XResources繼承自Resources,裏面包含了不少資源的信息。
IXposedHookInitPackageResources的定義
//Get notified when the resources for an app are initialized. //In handleInitPackageResources(), resource replacements can be created. //This interface should be implemented by the module's main class. Xposed will take care of registering it as a callback automatically. public interface IXposedHookInitPackageResources extends IXposedMod { //This method is called when resources for an app are being initialized. //Modules can call special methods of the XResources class in order to replace resources. //@param resparam Information about the resources. //@throws Throwable Everything the callback throws is caught and logged. void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable; }
XC_InitPackageResources.InitPackageResourcesParam的定義
//This class is only used for internal purposes, except for the InitPackageResourcesParam subclass. public abstract class XC_InitPackageResources extends XCallback implements IXposedHookInitPackageResources { //Wraps information about the resources being initialized. public static final class InitPackageResourcesParam extends XCallback.Param { public String packageName; //The name of the package for which resources are being loaded public XResources res; //Reference to the resources that can be used for calls to XResources#setReplacement(String, String, String, Object) } }
有了這個XResource對象,就能夠拿到佈局資源樹了,你能夠拿到遍歷,拿到某個特定控件,而後作一些騷操做。
//Callback for hooking layouts. Such callbacks can be passed to {@link XResources#hookLayout} and its variants. public abstract class XC_LayoutInflated extends XCallback { //Creates a new callback with default priority. public XC_LayoutInflated() { super(); } //Creates a new callback with a specific priority. See XCallback#priority. public XC_LayoutInflated(int priority) { super(priority); } //Wraps information about the inflated layout. public static final class LayoutInflatedParam extends XCallback.Param { public View view; //The view that has been created from the layout. public ResourceNames resNames; //Container with the ID and name of the underlying resource. public String variant; //Directory from which the layout was actually loaded (e.g. "layout-sw600dp"). public XResources res; //Resources containing the layout. } //This method is called when the hooked layout has been inflated. //@param liparam Information about the layout and the inflated view. public abstract void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable; //An object with which the callback can be removed. public class Unhook implements IXUnhook<XC_LayoutInflated> { private final String resDir; private final int id; //the resource ID of the hooked layout @Override public XC_LayoutInflated getCallback() { return XC_LayoutInflated.this; } @Override public void unhook() { XResources.unhookLayout(resDir, id, XC_LayoutInflated.this); } } }
There are many helper methods in Xposed that can make developing a module much easier.
XposedBridge
This class contains most of Xposed's central logic, such as initialization and callbacks used by the native side. It also includes methods to add new hooks.
log方法:
The log
method is an easy way of logging debug output to the standard logcat
and a file called /data/xposed/debug.log
. It can take the log message or a Throwable
. In the latter case, it will print the stack trace.
XposedBridge.log(「日誌內容」):輸入日誌和寫入到/data/xposed/debug.log,Xposed Installer的日誌那裏能夠看到!
hookAllMethods、hookAllConstructors方法:
You can use these methods if you want to hook all methods with a specific name or all constructors in a class. This is useful if there are different variants, but you want to execute some code before/after any of them has been called. Keep in mind that other ROMs might have additional variants that will also be hooked by this. Especially, be careful about the args
you get in the callback.
若是要hook具備特定名稱的全部方法或類中的全部構造函數,則可使用這兩種方法。 若是存在不一樣的變量,可是您但願在調用任何代碼以前/以後執行某些代碼,這將很是有用。 請記住,其餘ROM可能還有其餘變量,也會被hook。 特別是要當心你在回調中獲得的args
XposedHelpers
Helpers that simplify hooking and calling methods/constructors, getting and settings fields, ...
XposeHelpers提供了一些輔助方法
咱們再建立一個Activity測試在資源佈局初始化
時進行hook的效果:
public class XposedActivity2 extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Log.i("bqt", "【setContentView前】"); setContentView(R.layout.activity_main); Log.i("bqt", "【setContentView後】"); Log.i("bqt", "【inflate前】"); getLayoutInflater().inflate(R.layout.fragment_tab, null); Log.i("bqt", "【inflate後】"); } }
接下來在XposenInit裏面實現IXposedHookInitPackageResources接口,而且實現handleInitPackageResources方法,代碼以下:
public class XposedInit implements IXposedHookInitPackageResources { private static String HOOK_PACKAGE_NAME = "com.my.bqt"; private static String HOOK_LAYOUT_NAME = "activity_main"; private static String HOOK_LAYOUT_NAME2 = "fragment_tab"; @Override public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) { Log.i("bqt", "【handleInitPackageResources】" + resparam.packageName);//任何一個app的包資源初始化時都會調用 if (resparam.packageName.equals(HOOK_PACKAGE_NAME)) { resparam.res.hookLayout(resparam.packageName, "layout", HOOK_LAYOUT_NAME, new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) { Log.i("bqt", "【handleLayoutInflated】" + liparam.variant + "," + liparam.resNames.fullName); printView((ViewGroup) liparam.view, 1); TextView textView = liparam.view.findViewById(R.id.tv_title); textView.setText("Hello Xposed"); } }); resparam.res.hookLayout(resparam.packageName, "layout", HOOK_LAYOUT_NAME2, new XC_LayoutInflated() { @Override public void handleLayoutInflated(LayoutInflatedParam liparam) { Log.i("bqt", "【handleLayoutInflated2】" + liparam.variant + "," + liparam.resNames.fullName); } }); } } //遍歷資源佈局樹,並打印出來 private void printView(ViewGroup view, int deep) { StringBuilder builder = new StringBuilder(); String viewDeepFormat; for (int i = 0; i < deep - 1; i++) { builder.append("\t"); } viewDeepFormat = builder + "\t"; Log.i("bqt", "【printView】" + builder.toString() + view.toString()); int count = view.getChildCount(); for (int i = 0; i < count; i++) { if (view.getChildAt(i) instanceof ViewGroup) { printView((ViewGroup) view.getChildAt(i), deep + 1); } else { Log.i("bqt", "【printView】" + viewDeepFormat + view.getChildAt(i).toString()); } } } }
安裝重啓後,打開demo,查看打印的日誌:
【handleInitPackageResources】com.my.bqt 【setContentView前】 【handleLayoutInflated】layout,com.my.bqt:activity_main/layout 【printView】android.widget.FrameLayout{2077e3cd V.E..... ......I. 0,0-0,0 #1020002 android:id/content} 【printView】 android.widget.LinearLayout{1802a482 V.E..... ......I. 0,0-0,0} 【printView】 android.widget.TextView{5cbd793 V.ED.... ......ID 0,0-0,0 #7f07009f app:id/tv_title} 【printView】 android.view.View{222bc3d0 V.ED.... ......ID 0,0-0,0} 【printView】 android.widget.FrameLayout{252befc9 V.E..... ......I. 0,0-0,0 #7f070044 app:id/id_container} 【printView】 android.view.View{353a86ce V.ED.... ......ID 0,0-0,0} 【printView】 android.widget.LinearLayout{105259ef V.E..... ......I. 0,0-0,0 #7f07005a app:id/ly_main_tab_bottom} 【printView】 android.widget.TextView{8514cfc V.ED.... ......ID 0,0-0,0 #7f07009e app:id/tv_tab_bottom_weixin} 【printView】 android.widget.TextView{16fabf85 V.ED.... ......ID 0,0-0,0 #7f07009c app:id/tv_tab_bottom_friend} 【printView】 android.widget.TextView{14fc41da V.ED.... ......ID 0,0-0,0 #7f07009b app:id/tv_tab_bottom_contact} 【printView】 android.widget.TextView{1414a60b V.ED.... ......ID 0,0-0,0 #7f07009d app:id/tv_tab_bottom_setting} 【setContentView後】 【inflate前】 【handleLayoutInflated2】layout,com.my.bqt:fragment_tab/layout 【inflate後】
Tips:
handleInitPackageResources
會在setContentView
和getLayoutInflater().inflate
時調用。inflate
方法。liparam.view
的字段,經過日誌能夠看出setContentView方法的是一個FrameLayout,下面包含了LinearLayout,這個LinearLayout也就是咱們activity_main佈局最外層的view,獲取到這個view之後就能夠進行一系列的操做了。2019-4-13