在运行时覆盖资源问题代码关于替代方法的一句话

2022-08-31 15:57:38

问题

我希望能够在运行时覆盖我的应用资源,例如R.colour.brand_colour或R.drawable.ic_action_start。我的应用程序连接到CMS系统,该系统将提供品牌颜色和图像。一旦应用程序下载了CMS数据,它需要能够重新蒙皮。

我知道你要说什么 - 在运行时覆盖资源是不可能的。

除了它有点。特别是,我发现了2012年的学士论文,它解释了基本概念 - Android中的活动类扩展,其中包含attlebaseContext方法。您可以重写 attachBaseContext 以使用您自己的自定义类包装 Context,该自定义类将覆盖 getColor 和 getDrawable 等方法。你自己的getColor实现可以按照它想要的方式查找颜色。书法库使用类似的方法来注入自定义布局膨胀器,该膨胀器可以处理加载自定义字体。ContextWrapper

代码

我创建了一个简单的活动,它使用这种方法来覆盖颜色的加载。

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void attachBaseContext(Context newBase) {
        super.attachBaseContext(new CmsThemeContextWrapper(newBase));
    }

    private class CmsThemeContextWrapper extends ContextWrapper{

        private Resources resources;

        public CmsThemeContextWrapper(Context base) {
            super(base);
            resources = new Resources(base.getAssets(), base.getResources().getDisplayMetrics(), base.getResources().getConfiguration()){
                @Override
                public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException {
                    Log.i("ThemeTest", "Getting value for resource " + getResourceName(id));
                    super.getValue(id, outValue, resolveRefs);
                    if(id == R.color.theme_colour){
                        outValue.data = Color.GREEN;
                    }
                }

                @Override
                public int getColor(int id) throws NotFoundException {
                    Log.i("ThemeTest", "Getting colour for resource " + getResourceName(id));
                    if(id == R.color.theme_colour){
                        return Color.GREEN;
                    }
                    else{
                        return super.getColor(id);
                    }
                }
            };
        }

        @Override
        public Resources getResources() {
            return resources;
        }
    }
}

问题是,它不起作用!日志记录显示对加载资源(如布局/activity_main和 mipmap/ic_launcher但从不加载 color/theme_colour 的调用。上下文似乎用于创建窗口和操作栏,而不是活动的内容视图。

我的问题是 - 布局膨胀器从哪里加载资源,如果不是活动上下文?我还想知道 - 有没有可行的方法来覆盖运行时颜色和可绘制对象的加载?

关于替代方法的一句话

我知道可以通过其他方式从CMS数据中主题化应用程序 - 例如,我们可以创建一个方法,然后在我们的内部有一堆代码,如下所示:getCMSColour(String key)onCreate()

myTextView.setTextColour(getCMSColour("heading_text_colour"))

对于可绘制对象、字符串等,可以采用类似的方法。但是,这将导致大量的样板代码 - 所有这些都需要维护。修改 UI 时,很容易忘记在特定视图上设置颜色。

包装上下文以返回我们自己的自定义值是“更清晰”的,并且不易损坏。在探索替代方法之前,我想了解为什么它不起作用。


答案 1

虽然“动态覆盖资源”似乎是解决问题的直接方法,但我相信一个更简洁的方法是使用官方数据绑定实现 https://developer.android.com/tools/data-binding/guide.html 因为它并不意味着以Android的方式进行黑客攻击

您可以使用 POJO 传递品牌设置。而不是像编写视图那样使用静态样式,而是使用所需的值绑定视图。使用适当的活动层次结构,它不应该添加太多的样板。@color/button_color@{brandingConfig.buttonColor}

这也使您能够更改布局上更复杂的元素,即:根据品牌设置在其他布局上包含不同的布局,使您的UI高度可配置,而无需太多努力。


答案 2

由于与 Luke Sleeman 存在基本相同的问题,因此我了解了在解析 XML 布局文件时如何创建视图。我专注于检查为什么分配给布局内s的文本属性的字符串资源没有被自定义返回的对象覆盖。同时,在通过 或 以编程方式设置文本或提示时,字符串将按预期被覆盖。

这是文本作为构造函数内部接收的方式 (sdk v 23.0.1):LayoutInflaterTextViewResourcesContextWrapperTextView.setText()TextView.setHint()CharSequenceTextView

// android.widget.TextView.java, line 973
text = a.getText(attr);

其中 是较早获得的:aTypedArray

 // android.widget.TextView.java, line 721
 a = theme.obtainStyledAttributes(attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes);

该方法在 上调用本机方法:Theme.obtainStyledAttributes()AssetManager

// android.content.res.Resources.java line 1593
public TypedArray obtainStyledAttributes(AttributeSet set,
            @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
...
        AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes,
                parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices);

...

这是该方法的声明:AssetManager.applyStyle()

// android.content.res.AssetManager.java, line 746
/*package*/ native static final boolean applyStyle(long theme,
        int defStyleAttr, int defStyleRes, long xmlParser,
        int[] inAttrs, int[] outValues, int[] outIndices);


总之,即使 使用正确的扩展上下文,在膨胀 XML 布局和创建视图时,也永远不会调用方法(在自定义返回的资源上)来获取 text 属性的字符串,因为 的构造函数直接使用 the 来加载属性的资源。这同样可能适用于其他视图和属性。LayoutInflaterResources.getText()ContextWrapperTextViewAssetManager


推荐