it-swarm-pt.tech

É possível carregar dinamicamente uma biblioteca em tempo de execução a partir de um aplicativo Android?

Existe alguma maneira de criar um aplicativo Android para baixar e usar uma biblioteca Java em tempo de execução?

Aqui está um exemplo:

Imagine que o aplicativo precise fazer alguns cálculos, dependendo dos valores de entrada. O aplicativo solicita esses valores de entrada e verifica se os Classes ou Methods necessários estão disponíveis.

Caso contrário, ele se conecta a um servidor, baixa a biblioteca necessária e a carrega em tempo de execução para chamar os métodos necessários usando técnicas de reflexão. A implementação pode mudar dependendo de vários critérios, como o usuário que está baixando a biblioteca.

66
lluismontero

Desculpe, estou atrasado e a pergunta já tem uma resposta aceita, mas sim , você pode baixar e executar bibliotecas externas. Aqui está o que eu fiz:

Fiquei me perguntando se isso era viável, então escrevi a seguinte aula:

package org.shlublu.Android.sandbox;

import Android.util.Log;

public class MyClass {
    public MyClass() {
        Log.d(MyClass.class.getName(), "MyClass: constructor called.");
    }

    public void doSomething() {
        Log.d(MyClass.class.getName(), "MyClass: doSomething() called.");
    }
}

E o empacotei em um arquivo DEX que salvei no cartão SD do meu dispositivo como /sdcard/shlublu.jar.

Então eu escrevi o "programa estúpido" abaixo, depois de remover MyClass do meu projeto Eclipse e limpá-lo:

public class Main extends Activity {

    @SuppressWarnings("unchecked")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        try {
            final String libPath = Environment.getExternalStorageDirectory() + "/shlublu.jar";
            final File tmpDir = getDir("dex", 0);

            final DexClassLoader classloader = new DexClassLoader(libPath, tmpDir.getAbsolutePath(), null, this.getClass().getClassLoader());
            final Class<Object> classToLoad = (Class<Object>) classloader.loadClass("org.shlublu.Android.sandbox.MyClass");

            final Object myInstance  = classToLoad.newInstance();
            final Method doSomething = classToLoad.getMethod("doSomething");

            doSomething.invoke(myInstance);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Basicamente, carrega a classe MyClass dessa maneira:

  • crie um DexClassLoader

  • use-o para extrair a classe MyClass de "/sdcard/shlublu.jar"

  • e armazene essa classe no diretório privado "dex" do aplicativo (armazenamento interno do telefone).

Em seguida, cria uma instância de MyClass e chama doSomething() na instância criada.

E funciona ... Vejo os traços definidos em MyClass no meu LogCat:

enter image description here

Eu tentei em um emulador 2.1 e no meu celular HTC físico (que está sendo executado Android 2.2 e que NÃO está enraizado).

Isso significa que você pode criar arquivos DEX externos para o aplicativo fazer o download e executá-los. Aqui foi feito da maneira mais difícil (feio Object lança, Method.invoke() chama feio ...), mas deve ser possível brincar com Interfaces para tornar algo mais limpo.

Uau. Eu sou a primeira surpresa. Eu estava esperando um SecurityException.

Alguns fatos para ajudar a investigar mais:

  • Meu DEX shlublu.jar foi assinado, mas não meu aplicativo
  • Meu aplicativo foi executado a partir da conexão Eclipse/USB. Portanto, este é um APK não assinado compilado no modo DEBUG
88
Shlublu

A resposta de Shlublu é realmente agradável. Algumas pequenas coisas que ajudariam um iniciante:

  • para o arquivo de biblioteca "MyClass", crie um projeto de aplicativo Android separado que tenha o arquivo MyClass como único arquivo na pasta src (outras coisas, como project.properties, manifest, res, etc. também devem estar lá)
  • no manifesto do projeto da biblioteca, verifique se você possui: <application Android:icon="@drawable/icon" Android:label="@string/app_name"> <activity Android:name=".NotExecutable" Android:label="@string/app_name"> </activity> </application> (".NotExecutable" não é uma palavra reservada. É só que eu precisei colocar algo aqui)

  • Para criar o arquivo .dex, basta executar o projeto da biblioteca como aplicativo Android (para a compilação) e localize o arquivo .apk na pasta bin do projeto.

  • Copie o arquivo .apk para o seu telefone e renomeie-o como arquivo shlublu.jar (um APK é, na verdade, uma especialização de um jar)

Outras etapas são as mesmas descritas por Shlublu.

  • Muito obrigado a Shlublu pela cooperação.
14
Pätris Halapuu

Se você estiver mantendo seus arquivos .DEX na memória externa do telefone, como o cartão SD (não recomendado! Qualquer aplicativo com as mesmas permissões pode substituir sua classe com facilidade e executar um ataque de injeção de código). permissão do aplicativo para ler memória externa. A exceção que é lançada, se esse for o caso, é 'ClassNotFound', o que é bastante enganador, coloque algo como o seguinte em seu manifesto (consulte o Google para obter a versão mais atualizada).

<manifest ...>

    <uses-permission Android:name="Android.permission.WRITE_EXTERNAL_STORAGE"
                 Android:maxSdkVersion="18" />
    ...
</manifest>
1
user4356087

Não tenho certeza se você pode conseguir isso carregando dinamicamente Java. Pode ser que você possa tentar incorporar um mecanismo de script ao seu código como o rhino, que pode executar Java scripts que podem ser baixados e atualizados dinamicamente.

1
Naresh

claro, é possível. apk que não está instalado pode ser invocado pelo Host Android application.generally, resolva o ciclo de vida do recurso e da atividade, então, pode carregar jar ou apk dinamicamente. detalhe, consulte a minha pesquisa de código aberto no github : https://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md

além disso, é necessário o DexClassLoader e a reflexão, agora observe alguns códigos-chave:

/**
 * Load a apk. Before start a plugin Activity, we should do this first.<br/>
 * NOTE : will only be called by Host apk.
 * @param dexPath
 */
public DLPluginPackage loadApk(String dexPath) {
    // when loadApk is called by Host apk, we assume that plugin is invoked by Host.
    mFrom = DLConstants.FROM_EXTERNAL;

    PackageInfo packageInfo = mContext.getPackageManager().
            getPackageArchiveInfo(dexPath, PackageManager.GET_ACTIVITIES);
    if (packageInfo == null)
        return null;

    final String packageName = packageInfo.packageName;
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    if (pluginPackage == null) {
        DexClassLoader dexClassLoader = createDexClassLoader(dexPath);
        AssetManager assetManager = createAssetManager(dexPath);
        Resources resources = createResources(assetManager);
        pluginPackage = new DLPluginPackage(packageName, dexPath, dexClassLoader, assetManager,
                resources, packageInfo);
        mPackagesHolder.put(packageName, pluginPackage);
    }
    return pluginPackage;
}

suas demandas funcionam apenas parcialmente no projeto de código aberto mencionado no início.

1
singwhatiwanna

Tecnicamente deve funcionar, mas e as regras do Google? De: play.google.com/intl/pt-BR/about/developer-content-policy-pr‌ int

Um aplicativo distribuído pelo Google Play não pode modificar, substituir ou atualizar-se usando outro método que não seja o mecanismo de atualização do Google Play. Da mesma forma, um aplicativo não pode baixar código executável (por exemplo, arquivos dex, JAR, .so) de uma fonte diferente do Google Play. Esta restrição não se aplica ao código que é executado em uma máquina virtual e tem acesso limitado a Android APIs (como JavaScript em um WebView ou navegador).

0
Marcin

Acho que a resposta do @Shlublu está correta, mas só quero destacar alguns pontos-chave.

  1. Podemos carregar quaisquer classes do jar externo e do arquivo apk.
  2. De qualquer forma, podemos carregar Activity de um jar externo, mas não podemos iniciá-lo por causa do conceito de contexto.
  3. Para carregar a interface do usuário do jar externo, podemos usar fragmento. Crie a instância do fragmento e a incorpore na Atividade. Mas verifique se o fragmento cria a interface do usuário dinamicamente, conforme indicado abaixo.

    public class MyFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup 
      container, @Nullable Bundle savedInstanceState)
     {
      super.onCreateView(inflater, container, savedInstanceState);
    
      LinearLayout layout = new LinearLayout(getActivity());
      layout.setLayoutParams(new 
     LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.MATCH_PARENT));
    Button button = new Button(getActivity());
    button.setText("Invoke Host method");
    layout.addView(button, LinearLayout.LayoutParams.MATCH_PARENT,
            LinearLayout.LayoutParams.WRAP_CONTENT);
    
    return layout;
     }
    }
    
0
User10001