Xposed进阶之app资源替换

替换Boolean, Color, Integer, int[], String and String[]类型的简单资源

1.替换系统框架(Android Framwork)资源

替换系统框架资源(对所有app 起作用)需要实现 IXposedHookZygoteInit接口的 initZygote 方法,并在该方法中调用Resources.setSystemWideReplacement(...) 方法替换资源

1
2
3
4
5
6
7
8
9
10
11
12
13
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 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

动画资源布局

1
2
3
4
5
6
7
8
9
10
11
12
<?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>
123456789101112

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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很难确定下来。推荐使用下面的方法修改布局:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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方法就会被调用执行,为了让我们在正确的进程中执行,需要先判断被加载的包是不是正确的包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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来访问该进程中加载的类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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);
}
});


}


}

搭建本文为作者原创 转载时请注明出处 谢谢

乱码三千 – 点滴积累 ,欢迎来到乱码三千技术博客站

0%