Xposed從入門到棄坑:0x0二、IXposedHook相關接口解析

在上一篇文章中咱們經過一個簡單的例子開發了一款Xposed框架,感覺到了Xposed的強大功能,在demo中咱們新建了一個XposedInit的類實現了IXposedHookLoadPackage 接口,在handleLoadPackage中進行hook,最終達到了咱們的目的,那IXposedHookLoadPackage是幹什麼的呢?還有handleLoadPackage何時會調用呢?還有IXposedHookInitPackageResources和IXposedHookZygoteInit的左右是什麼?這期會作一個講解。對上一篇文章有遺忘的能夠回過頭在看一遍:Xposed從入門到棄坑:1、Xposed初探
php

IXposedHookLoadPackage

從字面上翻譯就是在加載包時開始hook。接口須要實現handleLoadPackage方法,該方法會在執行Application.onCreate()方法前調用,而且攜帶一個XC_LoadPackage.LoadPackageParam lpparam返回過來,lpparam包含了hook到的應用的一些信息,具體經過表格來講明 (表格的description均爲hook到的應用相關信息,不是Xposed項目的信息)java

fields type description
packageName String 應用包名
processName String 應用加載後的進程名
classLoader ClassLoader 應用的classloader
appInfo ApplicationInfo 應用的信息,包括verisonCode,uid等

表格只是簡單的介紹,具體須要再從此的開發中再講解,在上篇文章中,在hook到方法後,使用反射獲取textview,再來回顧下代碼:android

//不能經過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()的源碼:git

@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()獲取的緣由。github

IXposedHookInitPackageResources

這個是在資源佈局初始化時進行hook,須要實現handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) 方法,在初始化時調用,resparam有如下兩個字段:vim

field type description
packageName String 應用包名
res XResources 資源相關

resparam.res是一個很是重要的字段,裏面包含了不少資源的信息,而且繼承Resources。下面經過一個例子作個簡要的說明。仍是使用上期的demo,項目地址:github.com/WrBug/Xpose… , 命令切換到提交:app

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:ide

@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 += "\t";
        }
        viewDeepFormat = viewgroupDeepFormat + "\t";
        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

相關文章
相關標籤/搜索