在上一篇文章中我們通過一個簡單的例子開發了一款Xposed框架,感受到了Xposed的強大功能,在demo中我們新建了一個XposedInit的類實現了IXposedHookLoadPackage 介面,在handleLoadPackage中進行hook,最終達到了我們的目的,那IXposedHookLoadPackage是幹什麼的呢?還有handleLoadPackage什麼時候會呼叫呢?還有IXposedHookInitPackageResources和IXposedHookZygoteInit的左右是什麼?這期會做一個講解。對上一篇文章有遺忘的可以回過頭在看一遍:Xposed從入門到棄坑:一、Xposed初探
IXposedHookLoadPackage
從字面上翻譯就是在載入包時開始hook。介面需要實現handleLoadPackage方法,該方法會在執行Application.onCreate()方法前呼叫,並且攜帶一個XC_LoadPackage.LoadPackageParam lpparam返回過來,lpparam包含了hook到的應用的一些資訊,具體通過表格來說明 (表格的description均為hook到的應用相關資訊,不是Xposed專案的資訊)
fields | type | description |
---|---|---|
packageName | String | 應用包名 |
processName | String | 應用載入後的程式名 |
classLoader | ClassLoader | 應用的classloader |
appInfo | ApplicationInfo | 應用的資訊,包括verisonCode,uid等 |
表格只是簡單的介紹,具體需要再今後的開發中再講解,在上篇文章中,在hook到方法後,使用反射獲取textview,再來回顧下程式碼:
//不能通過Class.forName()來獲取Class ,在跨應用時會失效
Class c=lpparam.classLoader.loadClass("com.wrbug.xposeddemo.MainActivity");
Field field=c.getDeclaredField("textView");
field.setAccessible(true);
//param.thisObject 為執行該方法的物件,在這裡指MainActivity
TextView textView= (TextView) field.get(param.thisObject);
textView.setText("Hello Xposed");複製程式碼
在第一行中 class沒有用Class.forName()來獲取,是什麼原因呢?我們先來看看Class.forName()的原始碼:
@CallerSensitive
public static Class<?> forName(String className) throws ClassNotFoundException {
return forName(className, true, VMStack.getCallingClassLoader());
}
@CallerSensitive
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;
}
/** Called after security checks have been made. */
static native Class<?> classForName(String className, boolean shouldInitialize, ClassLoader classLoader) throws ClassNotFoundException;複製程式碼
在三個引數的方法中,有一個需要傳一個ClassLoader進去,在一個引數的方法中,ClassLoader是通過VMStack.getCallingClassLoader()獲取的。VMStack是一個虛擬機器棧,在Android系統中,每個應用都有一個獨立的虛擬機器,所以VMStack.getCallingClassLoader()是獲取當前應用的ClassLoader,即xposed專案的ClassLoader,所以,如果使用Class.forName("xxx.xxx.xxxActivity")
獲取不同應用的類會提示找不到,這就是需要通過lpparam.classLoader.loadClass()
獲取的原因。
IXposedHookInitPackageResources
這個是在資源佈局初始化時進行hook,需要實現handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam)
方法,在初始化時呼叫,resparam有以下兩個欄位:
field | type | description |
---|---|---|
packageName | String | 應用包名 |
res | XResources | 資源相關 |
resparam.res是一個非常重要的欄位,裡面包含了很多資源的資訊,並且繼承Resources。下面通過一個例子做個簡要的說明。還是使用上期的demo,專案地址:github.com/WrBug/Xpose… , 命令切換到提交:
git checkout 0be008e複製程式碼
demo為在R.layout.activity_main佈局初始化時進行hook,列印出hook到的view。佈局做些修改,多加幾個控制元件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.wrbug.xposeddemo.MainActivity">
<TextView
android:id="@+id/textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<Button
android:id="@+id/button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Button"/>
<RatingBar
android:id="@+id/ratingBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<Switch
android:id="@+id/switch1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Switch"/>
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
</LinearLayout>複製程式碼
在Activity的onCreate裡面加入兩個log:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.i("Xposed", "before setcontent");
setContentView(R.layout.activity_main);
Log.i("Xposed", "after setcontent");
textView = (TextView) findViewById(R.id.textview);
textView.setText("WrBug");
Log.i("Xposed", "before inflate");
getLayoutInflater().inflate(R.layout.view_demo, null);
Log.i("Xposed", "after inflate");
}複製程式碼
也就是在setContentView兩邊加了before setcontent和after setcontent兩個log,新增一個inflate佈局的方法,接下來在XposenInit裡面實現IXposedHookInitPackageResources介面,並且實現handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam)
方法,程式碼如下:
public class XposedInit implements IXposedHookLoadPackage, IXposedHookInitPackageResources {
@Override
public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) {
if (lpparam.packageName.equals("com.wrbug.xposeddemo")) {
XposedHelpers.findAndHookMethod("com.wrbug.xposeddemo.MainActivity", lpparam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
//不能通過Class.forName()來獲取Class ,在跨應用時會失效
Class c = lpparam.classLoader.loadClass("com.wrbug.xposeddemo.MainActivity");
Field field = c.getDeclaredField("textView");
field.setAccessible(true);
//param.thisObject 為執行該方法的物件,在這裡指MainActivity
TextView textView = (TextView) field.get(param.thisObject);
textView.setText("Hello Xposed");
}
});
}
}
@Override
public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) throws Throwable {
if (resparam.packageName.equals("com.wrbug.xposeddemo")) {
resparam.res.hookLayout(resparam.packageName, "layout", "activity_main", new XC_LayoutInflated() {
@Override
public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable {
printView((ViewGroup) liparam.view, 1);
}
});
resparam.res.hookLayout(resparam.packageName, "layout", "view_demo", new XC_LayoutInflated() {
@Override
public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable {
XposedBridge.log("hook view_demo");
}
});
}
}
//遍歷資源佈局樹,並列印出來
private void printView(ViewGroup view, int deep) {
String viewgroupDeepFormat = "";
String viewDeepFormat = "";
for (int i = 0; i < deep - 1; i++) {
viewgroupDeepFormat += " ";
}
viewDeepFormat = viewgroupDeepFormat + " ";
XposedBridge.log(viewgroupDeepFormat + 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 {
XposedBridge.log(viewDeepFormat + view.getChildAt(i).toString());
}
}
}
}複製程式碼
安裝重啟後,開啟demo,檢視列印的日誌:
04-26 23:24:52.123 1818-1818/com.wrbug.xposeddemo I/Xposed: before setcontent
04-26 23:24:52.183 1818-1818/com.wrbug.xposeddemo I/Xposed: android.support.v7.widget.ContentFrameLayout{2fb5cbd7 V.E..... ......I. 0,0-0,0 #1020002 android:id/content}
04-26 23:24:52.183 1818-1818/com.wrbug.xposeddemo I/Xposed: android.widget.LinearLayout{33cd7cc4 V.E..... ......I. 0,0-0,0}
04-26 23:24:52.183 1818-1818/com.wrbug.xposeddemo I/Xposed: android.support.v7.widget.AppCompatTextView{2e1a07ad V.ED.... ......ID 0,0-0,0 #7f0b005e app:id/textview}
04-26 23:24:52.183 1818-1818/com.wrbug.xposeddemo I/Xposed: android.support.v7.widget.AppCompatButton{2c3a2ae2 VFED..C. ......I. 0,0-0,0 #7f0b005f app:id/button}
04-26 23:24:52.183 1818-1818/com.wrbug.xposeddemo I/Xposed: android.support.v7.widget.AppCompatRatingBar{1aa14e73 VFED.... ......ID 0,0-0,0 #7f0b0060 app:id/ratingBar}
04-26 23:24:52.183 1818-1818/com.wrbug.xposeddemo I/Xposed: android.widget.LinearLayout{1c87a130 V.E..... ......I. 0,0-0,0}
04-26 23:24:52.184 1818-1818/com.wrbug.xposeddemo I/Xposed: android.widget.Switch{13fc71a9 VFED..C. ......I. 0,0-0,0 #7f0b0061 app:id/switch1}
04-26 23:24:52.184 1818-1818/com.wrbug.xposeddemo I/Xposed: android.widget.ListView{3ed4132e V.ED.VC. ......I. 0,0-0,0}
04-26 23:24:52.184 1818-1818/com.wrbug.xposeddemo I/Xposed: after setcontent
04-26 23:24:52.184 1818-1818/com.wrbug.xposeddemo I/Xposed: before inflate
04-26 23:24:52.184 1818-1818/com.wrbug.xposeddemo I/Xposed: hook view_demo
04-26 23:24:52.184 1818-1818/com.wrbug.xposeddemo I/Xposed: after inflate複製程式碼
日誌可以看出handleInitPackageResources會在setContentView(R.layout.activity_main);
和getLayoutInflater().inflate(R.layout.view_demo, null);
時呼叫。對setContentView有了解的都明白setContentView也會呼叫inflate方法。所以,也可以看成是hook了inflate方法。在返回的資料XC_InitPackageResources.InitPackageResourcesParam resparam
中,有一個 liparam.view
的欄位,通過日誌可以看出setContentView方法的是一個ContentFrameLayout,下面包含了LinearLayout,這個LinearLayout也就是我們activity_main佈局最外層的view,獲取到這個view以後就可以進行一系列的操作了。
更多精彩內容可以關注我的部落格:www.wrbug.com