Android系统无源码App替换图标
需求
为了设计美观统一,就需要替换系统中部分内置应用的图标。
系统版本
Android 13
准备工作
准备替换后的图标,最好不同dpi都有,例如:ic_launcher_sogou.png,
知道替换应用的包名,例如:com.sohu.inputmethod.sogou
修改涉及到的文件
主要涉及到frameworks/base目录下的文件
/frameworks/base/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
/frameworks/base/core/res/res/values/symbols.xml
修改步骤
以下替换搜狗输入法图标作为例子说明。
- 添加图标
将不同分辨率的图标分别放在各个dpi的目录下。
/frameworks/base/core/res/res/mipmap-hdpi/ic_launcher_sogou.png
/frameworks/base/core/res/res/mipmap-ldpi/ic_launcher_sogou.png
/frameworks/base/core/res/res/mipmap-mdpi/ic_launcher_sogou.png
/frameworks/base/core/res/res/mipmap-xhdpi/ic_launcher_sogou.png
/frameworks/base/core/res/res/mipmap-xxhdpi/ic_launcher_sogou.png
/frameworks/base/core/res/res/mipmap-xxxhdpi/ic_launcher_sogou.png
- 声明资源
在symbols.xml文件中声明图标资源,以便其他地方通过com.android.internal.R引入该资源使用。
<java-symbol type="mipmap" name="ic_launcher_sogou" />
- 解析Application时替换图标
在ParsingPackageUtils文件中的parseBaseAppBasicFlags方法添加判断包名,然后替换图标的代码。
private void parseBaseAppBasicFlags(ParsingPackage pkg, TypedArray sa) {
......
......
......
int resIcon = getDefaultIcon(pkg.getPackageName());
if (resIcon != 0) {
pkg.setIconRes(resIcon);
}
}
public static int getDefaultIcon(String packageName) {
int resId = 0;
switch (packageName) {
case "com.sohu.inputmethod.sogou":
resId = com.android.internal.R.mipmap.ic_launcher_sogou;
break;
}
return resId;
}
- 解析Activity时替换图标
有些应用的launcher activity也声明了icon的话,就需要在ParsedActivityUtils文件中的parseActivityOrAlias方法也添加上上述替换图标的代码。
@NonNull
private static ParseResult<ParsedActivity> parseActivityOrAlias(ParsedActivityImpl activity,
ParsingPackage pkg, String tag, XmlResourceParser parser, Resources resources,
TypedArray array, boolean isReceiver, boolean isAlias, boolean visibleToEphemeral,
ParseInput input, int parentActivityNameAttr, int permissionAttr,
int exportedAttr) throws IOException, XmlPullParserException {
......
......
......
final int depth = parser.getDepth();
int type;
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
&& (type != XmlPullParser.END_TAG
|| parser.getDepth() > depth)) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final ParseResult result;
if (parser.getName().equals("intent-filter")) {
ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
!isReceiver, visibleToEphemeral, resources, parser, input);
if (intentResult.isSuccess()) {
ParsedIntentInfoImpl intentInfo = intentResult.getResult();
if (intentInfo != null) {
IntentFilter intentFilter = intentInfo.getIntentFilter();
activity.setOrder(Math.max(intentFilter.getOrder(), activity.getOrder()));
activity.addIntent(intentInfo);
if (LOG_UNSAFE_BROADCASTS && isReceiver
&& pkg.getTargetSdkVersion() >= Build.VERSION_CODES.O) {
int actionCount = intentFilter.countActions();
for (int i = 0; i < actionCount; i++) {
final String action = intentFilter.getAction(i);
if (action == null || !action.startsWith("android.")) {
continue;
}
if (!SAFE_BROADCASTS.contains(action)) {
Slog.w(TAG,
"Broadcast " + action + " may never be delivered to "
+ pkg.getPackageName() + " as requested at: "
+ parser.getPositionDescription());
}
}
}
+ if (intentFilter.hasAction(ACTION_MAIN)
+ && intentFilter.hasCategory(CATEGORY_LAUNCHER)
+ && activity.getIcon() != 0) {
+ int resIcon = ParsingPackageUtils.getDefaultIcon(pkg.getPackageName());
+ if (resIcon != 0) {
+ activity.setIcon(resIcon);
+ }
+ }
}
}
result = intentResult;
} else if (parser.getName().equals("meta-data")) {
result = ParsedComponentUtils.addMetaData(activity, pkg, resources, parser, input);
} else if (parser.getName().equals("property")) {
result = ParsedComponentUtils.addProperty(activity, pkg, resources, parser, input);
} else if (!isReceiver && !isAlias && parser.getName().equals("preferred")) {
ParseResult<ParsedIntentInfoImpl> intentResult = parseIntentFilter(pkg, activity,
true /*allowImplicitEphemeralVisibility*/, visibleToEphemeral,
resources, parser, input);
if (intentResult.isSuccess()) {
ParsedIntentInfoImpl intent = intentResult.getResult();
if (intent != null) {
pkg.addPreferredActivityFilter(activity.getClassName(), intent);
}
}
result = intentResult;
} else if (!isReceiver && !isAlias && parser.getName().equals("layout")) {
ParseResult<ActivityInfo.WindowLayout> layoutResult =
parseActivityWindowLayout(resources, parser, input);
if (layoutResult.isSuccess()) {
activity.setWindowLayout(layoutResult.getResult());
}
result = layoutResult;
} else {
result = ParsingUtils.unknownTag(tag, pkg, parser, input);
}
if (result.isError()) {
return input.error(result);
}
}
......
......
......
}
通过在循环中的intentFilter判断包含ACTION_MAIN,CATEGORY_LAUNCHER字段,且设置了icon的话,去替换图标。
总结
以上就是Android系统无源码App替换图标的方法。