該方案基于android 9.x驗(yàn)證
問題描述
客戶想要我們的主應(yīng)用MainApp,但是想通過值改變資源應(yīng)用ResApp疾宏,達(dá)到改變應(yīng)用多語言翻譯的需求
也就是,我們提供給客戶MainApp的apk文件,和ResApp的源碼文件,客戶通過改變ResApp狼忱,來控制MainApp的顯示膨疏。
解決方案 1
- 將兩個apk shared到同一個進(jìn)程中去
//主應(yīng)用 MainApp
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jdf.res.main"
android:sharedUserId="com.jdf.res">
//資源應(yīng)用 ResApp
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.jdf.res.resapp"
android:sharedUserId="com.jdf.res">
- 將兩個應(yīng)用新增同樣名稱,不同值的資源字段
//主應(yīng)用 MainApp-默認(rèn)資源
<resources>
<string name="app_name">MainApp</string>
<string name="test1">Main App test1</string>
<string name="test2">Main App test2</string>
<string name="test3">Main App test3</string>
<string name="test4">Main App test4</string>
<string name="test5">Main App test5</string>
<string name="test6">Main App test6</string>
</resources>
//資源應(yīng)用 ResApp--默認(rèn)資源
<resources>
<string name="app_name">ResApp</string>
<string name="test1">Res app test1</string>
<string name="test2">Res app test2</string>
<string name="test3">Res app test3</string>
<string name="test4">Res app test4</string>
<string name="test5">Res app test5</string>
<string name="test6">Res app test6</string>
</resources>
//資源應(yīng)用 ResApp--中文資源
<resources>
<string name="app_name">資源應(yīng)用</string>
<string name="test1">資源應(yīng)用 test1</string>
<string name="test2">資源應(yīng)用 test2</string>
<string name="test3">資源應(yīng)用 test3</string>
<string name="test4">資源應(yīng)用 test4</string>
<string name="test5">資源應(yīng)用 test5</string>
<string name="test6">資源應(yīng)用 test6</string>
</resources>
- 然后在主應(yīng)用钻弄,覆蓋resource值為資源應(yīng)用的resource
//主應(yīng)用 MainApp
@Override
public Resources getResources() {
Context context = null;
try {
context = createPackageContext("com.jdf.res.resapp", Context.CONTEXT_IGNORE_SECURITY);
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
if (context != null) {
return context.getResources();
} else {
return super.getResources();
}
}
通過以上方式覆蓋后佃却,我們再訪問資源時,訪問的就是資源應(yīng)用的
Log.d("res", "Main app res string:" + getString(R.string.test1));
Log.d("res", "Main app res string:" + getString(R.string.test2));
Log.d("res", "Main app res string:" + getString(R.string.test3));
Log.d("res", "Main app res string:" + getString(R.string.test4));
Log.d("res", "Main app res string:" + getString(R.string.test5));
Log.d("res", "Main app res string:" + getString(R.string.test6));
- 測試結(jié)果
2010-01-01 08:37:02.153 8481-8481/com.jdf.res.main D/res: Main app res string:資源應(yīng)用 test1
2010-01-01 08:37:02.156 8481-8481/com.jdf.res.main D/res: Main app res string:資源應(yīng)用 test2
2010-01-01 08:37:02.157 8481-8481/com.jdf.res.main D/res: Main app res string:資源應(yīng)用 test3
2010-01-01 08:37:02.159 8481-8481/com.jdf.res.main D/res: Main app res string:資源應(yīng)用 test4
2010-01-01 08:37:02.161 8481-8481/com.jdf.res.main D/res: Main app res string:資源應(yīng)用 test5
2010-01-01 08:37:02.162 8481-8481/com.jdf.res.main D/res: Main app res string:資源應(yīng)用 test6
為什么能夠訪問到資源應(yīng)用的字符串
原因是通過 android:sharedUserId將兩個應(yīng)用share到同一個進(jìn)程窘俺,兩個apk的同一個資源屬性的資源id是相同的饲帅;然后通過覆蓋resource,就能夠通過資源id瘤泪,訪問到到另外一個應(yīng)用的資源了
通過反編譯灶泵,我們看下,兩個應(yīng)用的对途,新增的字符串屬性的資源id編號
//主應(yīng)用的資源id
/* renamed from: com.jdf.res.main.R$string */
public static final class string {
public static final int test1 = 2131492894;
public static final int test2 = 2131492895;
public static final int test3 = 2131492896;
public static final int test4 = 2131492897;
public static final int test5 = 2131492898;
public static final int test6 = 2131492899;
}
//資源應(yīng)用的資源id
/* renamed from: com.jdf.res.resapp.R$string */
public static final class string {
.....
public static final int test1 = 2131492894;
public static final int test2 = 2131492895;
public static final int test3 = 2131492896;
public static final int test4 = 2131492897;
public static final int test5 = 2131492898;
public static final int test6 = 2131492899;
}
這種方式赦邻,在xml引用資源,同樣適用实檀,因?yàn)橘Y源文件惶洲,也是通過調(diào)用處的上下文ressource加載的。
有一個缺點(diǎn)膳犹,主應(yīng)用MainApp的桌面app名稱恬吕,還是應(yīng)用自己的,而不是ResApp的须床。
下面我們針對其他應(yīng)用獲取應(yīng)用方式铐料,針對性的解決應(yīng)用名稱不能替換的問題
應(yīng)用名稱獲取方式 1
public static String getApplicationNameByPackageName(Context context, String packageName) {
PackageManager pm = context.getPackageManager();
String Name;
try {
Name = pm.getApplicationLabel(pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA)).toString();
} catch (PackageManager.NameNotFoundException e) {
Name = "";
}
return Name;
}
如上所示,桌面是通過獲取各個應(yīng)用的getApplicationLabel獲取應(yīng)用名稱的侨颈,所以余赢,如果達(dá)到MainApp顯示的應(yīng)用名稱也是ResApp的,我們還需要修改系統(tǒng)代碼哈垢,覆蓋MainApp的名稱
系統(tǒng)修改修改方法:
diff --git a/base/core/java/android/app/ApplicationPackageManager.java b/base/core/java/android/app/ApplicationPackageManager.java
index b1a5651..40db061
--- a/base/core/java/android/app/ApplicationPackageManager.java
+++ b/base/core/java/android/app/ApplicationPackageManager.java
@@ -1755,7 +1755,10 @@ public class ApplicationPackageManager extends PackageManager {
@Override
public CharSequence getApplicationLabel(ApplicationInfo info) {
- return info.loadLabel(this);
+
+ return LabelManager.replaceLabel(this, info);
+
+// return info.loadLabel(this);
}
應(yīng)用名稱獲取方式2
//LauncherActivityInfo.getLabel
public CharSequence getLabel() {
// TODO: Go through LauncherAppsService
return mActivityInfo.loadLabel(mPm);
}
//PackageItemInfo
public @NonNull CharSequence loadLabel(@NonNull PackageManager pm) {
if (sForceSafeLabels) {
return loadSafeLabel(pm);//里面也會調(diào)用loadUnsafeLabel方法
} else {
return loadUnsafeLabel(pm);
}
}
/** {@hide} */
public CharSequence loadUnsafeLabel(PackageManager pm) {
if (nonLocalizedLabel != null) {
CharSequence replacedLabel = LabelManager.loadUnsafeLabel(pm, packageName, labelRes, getApplicationInfo());
return nonLocalizedLabel;
}
if (labelRes != 0) {
CharSequence label = pm.getText(packageName, labelRes, getApplicationInfo());
if (label != null) {
return label.toString().trim();
}
}
if (name != null) {
return name;
}
return packageName;
}
考慮到子類覆蓋的情況
xxx@ubuntu:~/xxx/android/xxx/frameworks/base/core/java$ grep -nr "loadUnsafeLabel(PackageManager pm)"
android/content/pm/ComponentInfo.java:100: @Override public CharSequence loadUnsafeLabel(PackageManager pm) {
android/content/pm/PackageItemInfo.java:199: public CharSequence loadUnsafeLabel(PackageManager pm) {
需要對ComponentInfo.java和PackageItemInfo.java兩個類的loadUnsafeLabel方法進(jìn)行處理
//ComponentInfo.java
/** @hide */
@Override public CharSequence loadUnsafeLabel(PackageManager pm) {
if (nonLocalizedLabel != null) {
return nonLocalizedLabel;
}
ApplicationInfo ai = applicationInfo;
CharSequence label;
if (labelRes != 0) {
- label = pm.getText(packageName, labelRes, ai);
+ /* add for res replace */
+ label = LabelManager.loadUnsafeLabel(pm, packageName, labelRes, ai);
+ //label = pm.getText(packageName, labelRes, ai);
if (label != null) {
return label;
}
@@ -126,7 +130,10 @@ public class ComponentInfo extends PackageItemInfo {
return ai.nonLocalizedLabel;
}
if (ai.labelRes != 0) {
- label = pm.getText(packageName, ai.labelRes, ai);
+ /* add for res replace */
+ label = LabelManager.loadUnsafeLabel(pm, packageName, ai.labelRes, ai);
+ //label = pm.getText(packageName, ai.labelRes, ai);
if (label != null) {
return label;
}
//PackageItemInfo.java
/** {@hide} */
public CharSequence loadUnsafeLabel(PackageManager pm) {
if (nonLocalizedLabel != null) {
return nonLocalizedLabel;
}
if (labelRes != 0) {
- CharSequence label = pm.getText(packageName, labelRes, getApplicationInfo());
+ /* add for camera res replace */
+ CharSequence label = LabelManager.loadUnsafeLabel(pm, packageName, labelRes, getApplicationInfo());
+ //CharSequence label = pm.getText(packageName, labelRes, getApplicationInfo());
if (label != null) {
return label.toString().trim();
}
LabelManager的實(shí)現(xiàn)
package android.app;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.util.Log;
public class LabelManager {
public static final String TAG = "LabelManager";
public static final boolean IS_SUPPORT_LABEL_MAR = true;
private static final String[] REPLACE_FROM_PKGS = new String[] {
"com.jdf.res.main",
"com.android.camera"
};
private static final String[] REPLACE_TO_PKGS = new String[] {
"com.jdf.res.resapp",
"test.android.camera"
};
private static String getReplacedPkg(String pkg) {
if (pkg != null) {
for (int i = 0; i < REPLACE_FROM_PKGS.length; i++) {
if (pkg.equals(REPLACE_FROM_PKGS[i])) {
return REPLACE_TO_PKGS[i];
}
}
}
return null;
}
public static CharSequence replaceLabel(PackageManager pm, ApplicationInfo info) {
if (IS_SUPPORT_LABEL_MAR) {
final String replacedPkg = getReplacedPkg(info.packageName);
if (replacedPkg != null) {//屬于label要被替代的應(yīng)用
ApplicationInfo replacedInfo;
try {
replacedInfo = pm.getApplicationInfo(replacedPkg, PackageManager.GET_META_DATA);
if (replacedInfo != null) {
final CharSequence loadLabel = replacedInfo.loadLabel(pm);
Log.d(TAG, "replace from [" + info.packageName + "] to [" + replacedPkg + "]");
Log.d(TAG, "replace from [" + info.loadLabel(pm) + "] to [" + loadLabel + "]");
return loadLabel;
}
} catch (NameNotFoundException e) {
e.printStackTrace();
}
}
}
return info.loadLabel(pm);
}
public static CharSequence loadUnsafeLabel(PackageManager pm, String fromPkgName, int labelRes,
ApplicationInfo appinfo) {
String toPkgName = getReplacedPkg(fromPkgName);
Log.d(LabelManager.TAG, "loadUnsafeLabel toPkgName:" +toPkgName);
if (toPkgName != null) {//改應(yīng)用屬于是要被替代的應(yīng)用
ApplicationInfo applicationInfo = null;
try {
//使用替代的ApplicationInfo
applicationInfo = pm.getApplicationInfo(toPkgName, PackageManager.GET_META_DATA);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
Log.d(LabelManager.TAG, "loadUnsafeLabel applicationInfo:" +applicationInfo);
if (applicationInfo != null) {
//獲取目標(biāo)App的應(yīng)用名稱
CharSequence toLabel = pm.getText(toPkgName, labelRes, applicationInfo);
Log.d(LabelManager.TAG, "loadUnsafeLabel replace[" + fromPkgName + "] to " + toPkgName + " label:" + toLabel);
if (toLabel != null) {
return toLabel.toString().trim();
}
}
}
return pm.getText(fromPkgName, labelRes, appinfo);
}
}
應(yīng)用名稱映射效果圖
解決方案 2
通過動態(tài)資源覆蓋的方式妻柒,實(shí)現(xiàn),參考章節(jié):
優(yōu)缺點(diǎn):
方案一:
優(yōu)點(diǎn):每次修改資源耘分,只需修改和編譯ResApp举塔,方便改動和發(fā)布
缺點(diǎn):ResApp中的資源文件屬性,需要跟MainApp保持一次求泰,不能刪除和增加屬性字段央渣;
需要修改系統(tǒng),雖然改動不大
方案二:
優(yōu)點(diǎn):只需添加要覆蓋的資源文件即可
缺點(diǎn):每次資源改動渴频,需要重新編譯系統(tǒng)芽丹,改動和發(fā)布比較麻煩