java - Substituindo Recursos em Tempo de Execução




android android-activity (2)

Depois de pesquisar por um longo tempo, finalmente encontrei uma excelente solução.

protected void redefineStringResourceId(final String resourceName, final int newId) {
        try {
            final Field field = R.string.class.getDeclaredField(resourceName);
            field.setAccessible(true);
            field.set(null, newId);
        } catch (Exception e) {
            Log.e(getClass().getName(), "Couldn't redefine resource id", e);
        }
    }

Para um teste de amostra,

private Object initialStringValue() {
                // TODO Auto-generated method stub
                 return getString(R.string.initial_value);
            }

E dentro da atividade principal,

before.setText(getString(R.string.before, initialStringValue()));

            final String resourceName = getResources().getResourceEntryName(R.string.initial_value);
            redefineStringResourceId(resourceName, R.string.evil_value);

            after.setText(getString(R.string.after, initialStringValue()));

Esta solução foi originalmente publicada por Roman Zhilich

ResourceHackActivity

O problema

Gostaria de substituir os recursos de meus aplicativos, como R.colour.brand_colour ou R.drawable.ic_action_start em tempo de execução. Meu aplicativo se conecta a um sistema CMS que fornecerá cores e imagens de marca. Depois que o aplicativo fizer o download dos dados do CMS, ele precisará ser restaurado.

Eu sei o que você está prestes a dizer - não é possível substituir recursos em tempo de execução.

Exceto que meio que é. Em particular, eu encontrei esta Tese de Bacharel em 2012, que explica o conceito básico - A classe Activity no android estende o ContextWrapper , que contém o método attachBaseContext. Você pode substituir o attachBaseContext para agrupar o Contexto com sua própria classe personalizada, que substitui métodos como getColor e getDrawable. Sua própria implementação de getColor pode procurar a cor da maneira que desejar. A biblioteca de caligrafia usa uma abordagem semelhante para injetar um LayoutInflator personalizado, que pode lidar com o carregamento de fontes personalizadas.

O código

Eu criei uma atividade simples que usa essa abordagem para substituir o carregamento de uma cor.

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;
        }
    }
}

O problema é que não funciona! O registro mostra chamadas para carregar recursos como layout / activity_main e mipmap / ic_launcher, no entanto, color / theme_colour nunca é carregado. Parece que o contexto está sendo usado para criar a janela e a barra de ação, mas não a exibição do conteúdo da atividade.

Minhas perguntas são - De onde o inflator de layout carrega recursos, se não o contexto das atividades? Eu também gostaria de saber - Existe uma maneira viável de substituir o carregamento de cores e desenhos em tempo de execução?

Uma palavra sobre abordagens alternativas

Eu sei que é possível tema de um aplicativo a partir de dados do CMS de outras maneiras - por exemplo, podemos criar um método getCMSColour(String key) e, em nosso onCreate() , temos um monte de código ao longo das linhas de:

myTextView.setTextColour(getCMSColour("heading_text_colour"))

Uma abordagem semelhante pode ser adotada para drawables, strings, etc. No entanto, isso resultaria em uma grande quantidade de código padrão - todos os quais precisam ser mantidos. Ao modificar a interface do usuário, seria fácil esquecer de definir a cor em uma exibição específica.

O agrupamento do contexto para retornar nossos próprios valores personalizados é 'mais limpo' e menos propenso a quebras. Gostaria de entender por que não funciona antes de explorar abordagens alternativas.


Embora "substituir recursos dinamicamente" possa parecer a solução direta para o seu problema, acredito que uma abordagem mais limpa seria usar a implementação oficial de ligação de dados https://developer.android.com/tools/data-binding/guide.html pois não implica invadir a maneira android.

Você pode passar suas configurações de marca usando um POJO. Em vez de usar estilos estáticos como @color/button_color você pode escrever @{brandingConfig.buttonColor} e vincular suas visualizações aos valores desejados. Com uma hierarquia de atividades adequada, não deve adicionar muitos clichês.

Isso também permite alterar elementos mais complexos em seu layout, ou seja: incluir layouts diferentes em outro layout, dependendo das configurações da marca, tornando sua interface do usuário altamente configurável sem muito esforço.







android-activity