Xposed的框架的使用

一、Xposed簡介

Xposed是一款優秀的android java層 hook 框架。它允許你在不修改apk源碼的情況下,通過編寫自己的模塊來改變apk的行爲。它的優點是採用了插件機制,模塊能夠適用不同版本的框架和rom。模塊改變apk行爲的操作發生在內存中,對源apk不進行任何修改。你只需要安裝編寫的模塊並重啓相應的設備即可。

二、相關資源

Xposed 官網:http://repo.xposed.info/
Xposed 項目 github 地址:https://github.com/rovo89
Xposed 官方教程 :https://github.com/rovo89/XposedBridge/wiki/Development-tutorial
Xposed Api 之XposedBridge.jar 下載:https://jcenter.bintray.com/de/robv/android/xposed/api/

三、使用Xposed

1.安裝Xposed 框架

首先你需要在手機設備上安裝Xposed 框架,官網上給我們提供了一個apk 來完成框架的安裝。

Xposed Installer官方下載:http://repo.xposed.info/module/de.robv.android.xposed.installer

由於Android系統5.0以上默認採用了ART模式,Android系統5.0 以下默認採用Davik模式。所以目前Xposed框架主要有兩個版本:一個是Android 4.0.3 to Android 4.4版本,一個是Android 5.0 以上版本。
我們以Android 4.0.3 to Android 4.4版本爲例。

這裏寫圖片描述

下載Xposed installer 成功後,將de.robv.android.xposed.installer.apk安裝在海馬模擬中或者已經root真機上

這裏寫圖片描述

選擇 框架 ,可看到app_process和XposedBridge.jar需要激活

這裏寫圖片描述

選擇 安裝/更新,會提示重啓

這裏寫圖片描述

重啓後,會發現app_process和XposedBridge.jar已經激活。

這裏寫圖片描述

2.編寫相關的模塊

現在我們來編寫Xposed模塊。

1.創建一個Android 工程

一個Xposed 模塊本質上是一個正常的apk,只是這個apk沒有與用戶交互的Activity界面,它僅僅包含一些mete 數據和文件,並且該apk安裝後在桌面應用中沒有圖標顯示。所以你只需要創建一個空的安卓工程,不需要添加任何Actitity.

2. 將Android 工程變成Xposed模塊

在工程中添加 Xposed Framework API

Xposed模塊爲了使用Xposed框架的api ,需要下載相應的 XposedBridge.jar 包。

Android Studio (Gradle-based)

Xposed Framework API 在下面網址上可進行查看:
https://bintray.com/rovo89/de.robv.android.xposed/api
在 Androdri Studio 工程中app/build.gradle文件中添加依賴項:

repositories {
    jcenter();
}

dependencies {
    provided 'de.robv.android.xposed:api:53'
}

重要的事情說三遍!使用 provided 不要使用 compile! compile 會將整個 API 類 編譯進你的apk中而導致出問題。provided 則只是提供了API 類的引用,API 類真正的實現則在 Xposed FramWork中。
在大多數情況下,repositories已經存在,並且已經有一些依賴,所以只需要將provided 這一行添加到存在的dependcies模塊中即可。
如果 需要查看api資源,則添加下面的兩行:

provided 'de.robv.android.xposed:api:53'
provided 'de.robv.android.xposed:api:53:sources'

這裏寫圖片描述

確保關閉Instant Run項File -> Settings -> Build, Execution, Deployment -> Instant Run,否者你的apk中將不包含這些類

這裏寫圖片描述

Eclipse
Eclipse創建的工程需要創建相應的jar包,jar包地址下載:https://jcenter.bintray.com/de/robv/android/xposed/api/

推薦將下載的api-XX.jar包放入到lib子目錄中,不要將jar包放入到libs 子目錄中。因爲libs 目錄中的jar包會使Eclipse將API類編譯到APK中,而事實上API只需要引用即可。

這裏寫圖片描述

右擊api-XX.jar文件選擇Build Path -> Add to Build Path 即可

Xposed Framework API

android 5.0以前的版本,推薦使用API 53

http://api.xposed.info/reference/packages.html

AndroidManifest.xml 文件配置

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="de.robv.android.xposed.mods.tutorial"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="15" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <meta-data
            android:name="xposedmodule"
            android:value="true" /><!--應用爲模塊-->
        <meta-data
            android:name="xposeddescription"
            android:value="Easy example which makes the status bar clock red and adds a smiley" /><!--模塊描述-->
        <meta-data
            android:name="xposedminversion"
            android:value="53" /> <!--版本信息-->
    </application>
</manifest>

模塊實現

在模塊中創建一個類,並命名爲 ’Tutorial‘,該類放在de.robv.android.xposed.mods.tutorial 包中。類名和包名是可以任意取的。

package de.robv.android.xposed.mods.tutorial;

public class Tutorial {

}

教程的第一步,我們只需要將加載的模塊信息打印出來。

一個模塊有一些進入點,這些進入點的選擇取決於你想修改的位置。你可以讓Xposed框架調用你模塊中的函數在下面幾個位置:安卓系統啓動的時候(使用 IXposedHookZygoteInit 接口)、一個新的app被加載的時候(使用 IXposedHookLoadPackage 接口)、一個資源被初始化的時候( 使用 IXposedHookInitPackageResources 接口)。

所有的進入點 都是 XposedMod 接口的子接口。在本示例中,進入點是“一個新的app被加載的時候”,我們需要實現
IXposedHookLoadPackage 接口。事實上該接口只有一個帶有一個參數的方法,該參數中給出了加載的應用的Context 信息:

package de.robv.android.xposed.mods.tutorial;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

public class Tutorial implements IXposedHookLoadPackage {
    public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable {
        XposedBridge.log("Loaded app: " + lpparam.packageName);
    }
}

log方法將寫信息到標準的ogcat中(/data/data/de.robv.android.xposed.installer/log/debug.log)

assets/xposed_init

XposedBridge 從assets 目錄中的xposed_init 文件中獲取入口點。xposed_init文件中每行配置一個進入點,使用完全限定名。在該例子中,進行如下配置

de.robv.android.xposed.mods.tutorial.Tutorial.

3.運行模塊

在設備上安裝該模塊,安裝成功後看不到模塊的界面。第一次安裝該模塊後,你需要在Xposed Install app中啓動模塊。打開Xposed Install app,選擇“Modules”tab 後。勾選模塊,並重啓Android系統。
這裏寫圖片描述

這時你打開Xposed Install app 日誌模塊,會發現下面的輸出:

這裏寫圖片描述

四、替換資源

替換Boolean, Color, Integer, int[], String and String[]類型的簡單資源

1.替換系統框架(Android Framwork)資源

替換系統框架資源(對所有app 起作用)需要實現 IXposedHookZygoteInit接口的 initZygote 方法,並在該方法中調用Resources.setSystemWideReplacement(...) 方法替換資源

package de.robv.android.xposed.mods.tutorial;

import android.content.res.XResources;
import de.robv.android.xposed.IXposedHookZygoteInit;

public class Tutorial2  implements IXposedHookZygoteInit{

    @Override
    public void initZygote(StartupParam arg0) throws Throwable {
        XResources.setSystemWideReplacement("android", "bool", "config_unplugTurnsOnScreen", false);
    }

}

2.替換app應用資源

替換app應用資源需要實現 IXposedHookInitPackageResources 類的
andleInitPackageResources方法,並在該方法中調用res.setReplacement(...)方法替換資源,注意在該方法中不要使用XResources.setSystemWideReplacement 方法

package de.robv.android.xposed.mods.tutorial;

import android.content.res.XResources;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;

public class Tutorial3 implements  IXposedHookInitPackageResources  {

    @Override
    public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
        //只替換systemui應用的資源
        if (!resparam.packageName.equals("com.android.systemui"))
            return;

        // 替換資源的不同方式
        resparam.res.setReplacement(0x7f080083, "YEAH!"); // WLAN toggle text. You should not do this because the id is not fixed. Only for framework resources, you could use android.R.string.something
        resparam.res.setReplacement("com.android.systemui:string/quickpanel_bluetooth_text", "WOO!");
        resparam.res.setReplacement("com.android.systemui", "string", "quickpanel_gps_text", "HOO!");
        resparam.res.setReplacement("com.android.systemui", "integer", "config_maxLevelOfSignalStrengthIndicator", 6);
        resparam.res.setReplacement("com.android.systemui", "drawable", "status_bar_background", new XResources.DrawableLoader() {
            @Override
            public Drawable newDrawable(XResources res, int id) throws Throwable {
                return new ColorDrawable(Color.WHITE);
            }
        });//你不能直接使用Drawble類進行替換,因爲Drawble類可以影響其他引用Ddrawble類實例的ImageView ,最好使用一個包裝器。
    }

}

替換複雜的資源

對於複製的資源,如動畫資源 ,我們也能夠替換,下面我們來替換battery icon

動畫資源佈局

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
     android:oneshot="true" >
    <item android:drawable="@drawable/icon1" android:duration="150"></item>
    <item android:drawable="@drawable/icon2" android:duration="150"></item>
    <item android:drawable="@drawable/icon3" android:duration="150"></item>
    <item android:drawable="@drawable/icon4" android:duration="150"></item>
    <item android:drawable="@drawable/icon5" android:duration="150"></item>
    <item android:drawable="@drawable/icon6" android:duration="150"></item>

</animation-list>

代碼:

package de.robv.android.xposed.mods.tutorial;

import com.example.xposedmoduletest.R;

import android.content.res.XModuleResources;
import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;

public class Tutorial4 implements IXposedHookZygoteInit, IXposedHookInitPackageResources {

    private static String MODULE_PATH = null;

    @Override
    public void initZygote(StartupParam startupParam) throws Throwable {
        MODULE_PATH = startupParam.modulePath;

    }

    @Override
    public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
        if (!resparam.packageName.equals("com.android.systemui"))
            return;

        XModuleResources modRes = XModuleResources.createInstance(MODULE_PATH, resparam.res);
        resparam.res.setReplacement("com.android.systemui", "drawable", "stat_sys_battery",
                modRes.fwd(R.drawable.animation));
        resparam.res.setReplacement("com.android.systemui", "drawable", "stat_sys_battery_charge",
                modRes.fwd(R.drawable.animation));
    }
}

Xposed框架會將模塊請求資源的請求指向你模塊中的資源
替換的效果:
這裏寫圖片描述

替換佈局

你可以用替換資源的方法來替換佈局文件,但這樣你不得不將目標apk中的整個layout文件複製出來進行修改,這樣會使模塊的Rom兼容性降低。並且如果兩個以上的模塊修改佈局後,最後修改佈局的模塊會起作用。更重要的是,佈局中指向其它資源的ID很難確定下來。推薦使用下面的方法修改佈局:

package de.robv.android.xposed.mods.tutorial;

import android.graphics.Color;
import android.widget.TextView;
import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_InitPackageResources.InitPackageResourcesParam;
import de.robv.android.xposed.callbacks.XC_LayoutInflated;
import de.robv.android.xposed.callbacks.XC_LayoutInflated.LayoutInflatedParam;

public class Tutorial5 implements   IXposedHookInitPackageResources{

    @Override
    public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
         if (!resparam.packageName.equals("com.android.systemui"))
                return;

            resparam.res.hookLayout("com.android.systemui", "layout", "status_bar", new XC_LayoutInflated() {
                @Override
                public void handleLayoutInflated(LayoutInflatedParam liparam) throws Throwable {
                    TextView clock = (TextView) liparam.view.findViewById(
                            liparam.res.getIdentifier("clock", "id", "com.android.systemui"));
                    clock.setTextColor(Color.RED);
                    XposedBridge.log("layout  resNames.fullname:"+liparam.resNames.fullName);
                    XposedBridge.log("layout  resNames.id:"+liparam.resNames.id);
                    XposedBridge.log("layout  resNames.name:"+liparam.resNames.name);
                    XposedBridge.log("layout  resNames.pkg:"+liparam.resNames.pkg);
                    XposedBridge.log("layout  resNames.type:"+liparam.resNames.type);

                    XposedBridge.log("layout  resNames.variant:"+liparam.variant);
                    XposedBridge.log("layout  resNames.view:"+liparam.view);




                }
            }); 
    }

}

回調方法handleLayoutInflated 會在layout文件被填充後回調,在方法的 LayoutInflatedParam 對象 參數中,你可以找到你想修改的View組件。你也可以通過調用resNames來確定加載的那一個佈局文件。用 variant來確定加載的佈局的目錄’layout-land‘。res 同時也會幫你獲取資源的ID和其它的資源。

這裏寫圖片描述

五、用反射來hook方法

每當應用加載的時候,IXposedHookLoadPackPage接口的handLoadPackage方法就會被調用執行,爲了讓我們在正確的進程中執行,需要先判斷被加載的包是不是正確的包

package de.robv.android.xposed.mods.tutorial;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

public class Tutorial6 implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(LoadPackageParam param) throws Throwable {
        if(!param.packageName.equals("com.android.systemui"))
            return;



        }


}

一旦我們進入到正確的進程進後,我們就能用param變中的ClassLoad來訪問該進程中加載的類

package de.robv.android.xposed.mods.tutorial;

import android.webkit.WebView.FindListener;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class Tutorial6 implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(LoadPackageParam param) throws Throwable {
        if(!param.packageName.equals("com.android.systemui"))
            return;
         findAndHookMethod("com.android.systemui.statusbar.policy.Clock",param.classLoader, "updateClock", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    // this will be called before the clock was updated by the original method
                }
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    // this will be called after the clock was updated by the original method
                }
        });


        }


}

XposedHelpers是一個重要的工具類,推薦用Eclipse的同學靜態導入該類中的方法 import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;。該類能夠通過反射機制來訪問方法、構造器、域。
findAndHookMehthod(String packageName,Class<?> clazz, String methodName, Object... args))方法來對函數進行Hook。如果在方法前和方法後Hook,該方法最後一個參數需要實現XC_MethodHook類的beforeHookedMethodafterHookedMethod方法,如果想要替換整個方法,則需要實現XC_MethodReplacement類的replaceHookedMethod方法
XposedBridge保存了每個Hook方法的回調方法。優先級高的回調方法被優先調用A.before -> B.before -> original method -> B.after -> A.after

package de.robv.android.xposed.mods.tutorial;

import android.graphics.Color;
import android.webkit.WebView.FindListener;
import android.widget.TextView;
import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XC_MethodHook.MethodHookParam;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

import static de.robv.android.xposed.XposedHelpers.findAndHookMethod;
public class Tutorial6 implements IXposedHookLoadPackage {

    @Override
    public void handleLoadPackage(LoadPackageParam param) throws Throwable {
        if(!param.packageName.equals("com.android.systemui"))
            return;
         findAndHookMethod("com.android.systemui.statusbar.policy.Clock",param.classLoader, "updateClock", new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    // this will be called before the clock was updated by the original method
                }
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    TextView tv = (TextView) param.thisObject;//獲取調用該方法類的對象
                    String text = tv.getText().toString();
                    tv.setText(text + " :)");
                    tv.setTextColor(Color.RED);
                }
        });


        }


}
發佈了45 篇原創文章 · 獲贊 31 · 訪問量 12萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章