这是Xposed在github上的最后一篇教程。原文地址
Xposed使得替换资源,比如:图像和字符串之类的事变得容易。下面是做法:
# 简单的资源
@Override
public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) throws Throwable {
XResources.setSystemWideReplacement("android", "bool", "config_unplugTurnsOnScreen", false);
}
@Override
public void handleInitPackageResources(InitPackageResourcesParam resparam) throws Throwable {
// replacements only for SystemUI
if (!resparam.packageName.equals("com.android.systemui"))
return;
// different ways to specify the resources to be replaced
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);
}
这就是“简单”的替换,通过这种方式你可以直接替换值。这种方式可以用于:Boolean, Color, Integer, int[], String and String[]。
如你所见,有几种不同的设置替换资源的方式。对于构成安卓框架一部分的所有地方都应该被替换的资源(对所有的app都可用),你应在initZygote
中调用XResources.setSystemWideReplacement(...)
方法。对于特定app的资源,你需要核实过你确实在正确的app之后在hookInitPackageResources
中调用res.setReplacement
。这时你不应使用setSystemWideReplacement
因为可能会产生你无法预料的副作用。
替换Drawable也采用相似的办法。然而你不能只使用Drawable作为替换物,因为这可能导致同一个Drawable实例被不同的ImageViews引用。因此,你需要使用包装器:
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);
}
});
# 复杂资源
更加复杂的资源(比如动画类型的Drawable)必须从你的模块资源当中被引用。我们假设你想要替换电池图标。下面是代码:
package de.robv.android.xposed.mods.coloredcirclebattery;
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 ColoredCircleBattery 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.battery_icon));
resparam.res.setReplacement("com.android.systemui", "drawable", "stat_sys_battery_charge", modRes.fwd(R.drawable.battery_icon_charge));
}
}
你可以随意命名你的替换资源。我选择 batterry_icon 替代 stat_sys_battery 让它们在本文中更好区分。
之后把 “battery_icon” 和 “battery_icon_charge” 这两个Drawable添加到你的模块当中。最简单的情况是添加 “res/drawables/battery_icon.png” 和 “res/drawables/battery_icon_charge.png”。但是你可以使用Android提供的所有方式来定义资源。所以对于动画图标,你可使用带有animation-list
和其它Drawable资源引用的XML文件。当然这个XML文件也必须放在你的模块内。
通过这些替换,你能要求Xposed将所有指向特定资源的请求跳到你自己的模块。这同样意味着你可以利用qualifier,比如:如果你对landscape或更低的屏幕密度需要不同的资源。翻译也可以以相同的方式提供。同样,你也许需要这么做如果原来的资源使用qualifier。你不能仅仅替换一段文字的西班牙语版本。正如前面提到的,请求是向前的,所以它会完全被你的模块资源处理,并且不会察觉其它翻译的存在。
这个技巧基本上对所有资源类型都有效。除了极个别的像主题之类的。
# 修改布局
尽管理论上你可以用之前提到的技巧彻底替换布局,但这有很多坏处。你必须从原有布局中复制整个布局,这会降低对其它ROM的兼容性。主题也许回丢失。只有一个模块可以代替布局。如果两个模块都尝试这么做,后者将会胜出。最重要的是,其它资源的ID和引用是很难定义的。因此,我不推荐这样做。
作为一种好的选择,你可以使用后填充的hook。下面是你怎么做:
@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);
}
});
}
每当“status_bar”布局被填充时,回调方法handleLayoutInflated
就被调用。在你以参数形式得到的LayoutInflatedParam对象中,你可以找到刚被创建的View并在需要时修改它。你同样会得到resNames
用来鉴别这个方法因哪个布局而被调用(万一你对多个布局使用同一种方法),还有variant
,比如可能包含layout-land
,如果它就是被加载的布局的版本的话。res
帮助你以布局从同一个来源得到ID或额外的资源。