web-dev-qa-db-fra.com

Est-il possible de charger dynamiquement une bibliothèque lors de l'exécution à partir d'une application Android?

Existe-t-il un moyen de créer une application Android pour télécharger et utiliser une bibliothèque Java à l'exécution)?

Voici un exemple:

Imaginez que l'application ait besoin de faire des calculs en fonction des valeurs d'entrée. L'application demande ces valeurs d'entrée, puis vérifie si les Classes ou Methods requis sont disponibles.

Sinon, il se connecte à un serveur, télécharge la bibliothèque requise et la charge lors de l'exécution pour appeler les méthodes requises à l'aide de techniques de réflexion. L'implémentation peut changer en fonction de divers critères tels que l'utilisateur qui télécharge la bibliothèque.

66
lluismontero

Désolé, je suis en retard et la question a déjà une réponse acceptée, mais oui , vous pouvez télécharger et exécuter des bibliothèques externes. Voici comment je l'ai fait:

Je me demandais si c'était faisable alors j'ai écrit le cours suivant:

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.");
    }
}

Et je l'ai emballé dans un fichier DEX que j'ai enregistré sur la carte SD de mon appareil en tant que /sdcard/shlublu.jar.

J'ai ensuite écrit le "programme stupide" ci-dessous, après avoir supprimé MyClass de mon projet Eclipse et l'avoir nettoyé:

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

Il charge essentiellement la classe MyClass de cette façon:

  • créer un DexClassLoader

  • utilisez-le pour extraire la classe MyClass de "/sdcard/shlublu.jar"

  • et stockez cette classe dans le répertoire privé de l'application "dex" (stockage interne du téléphone).

Ensuite, il crée une instance de MyClass et appelle doSomething() sur l'instance créée.

Et ça marche ... Je vois les traces définies dans MyClass dans mon LogCat:

enter image description here

J'ai essayé à la fois un émulateur 2.1 et mon téléphone portable HTC physique (qui fonctionne Android 2.2 et qui n'est PAS enraciné).

Cela signifie que vous pouvez créer des fichiers DEX externes pour que l'application les télécharge et les exécute. Ici, cela a été fait à la dure (laid Object casts, Method.invoke() ugly calls ...), mais il doit être possible de jouer avec Interfaces pour rendre quelque chose de plus propre.

Sensationnel. Je suis le premier surpris. Je m'attendais à un SecurityException.

Quelques faits pour aider à enquêter davantage:

  • Mon DEX shlublu.jar a été signé, mais pas mon application
  • Mon application a été exécutée à partir d'une connexion Eclipse/USB. Il s'agit donc d'un APK non signé compilé en mode DEBUG
88
Shlublu

La réponse de Shlublu est vraiment sympa. Quelques petites choses qui pourraient aider un débutant:

  • pour le fichier de bibliothèque "MyClass", créez un projet d'application Android Android qui a le fichier MyClass comme seul fichier dans le dossier src (d'autres choses, comme project.properties, manifest, res, etc. devraient également Soyez là)
  • dans le manifeste du projet de bibliothèque, assurez-vous d'avoir: <application Android:icon="@drawable/icon" Android:label="@string/app_name"> <activity Android:name=".NotExecutable" Android:label="@string/app_name"> </activity> </application> (".NotExecutable" n'est pas un mot réservé. C'est juste que j'ai dû mettre quelque chose ici)

  • Pour créer le fichier .dex, exécutez simplement le projet de bibliothèque en tant que Android (pour la compilation) et recherchez le fichier .apk dans le dossier bin du projet.

  • Copiez le fichier .apk sur votre téléphone et renommez-le en fichier shlublu.jar (un APK est en fait une spécialisation d'un pot, cependant)

Les autres étapes sont les mêmes que celles décrites par Shlublu.

  • Un grand merci à Shlublu pour sa coopération.
14
Pätris Halapuu

Si vous conservez vos fichiers .DEX dans la mémoire externe du téléphone, comme la carte SD (non recommandé! Toute application avec les mêmes autorisations peut facilement écraser votre classe et effectuer une attaque par injection de code) assurez-vous d'avoir donné le autorisation de l'application de lire la mémoire externe. L'exception qui est levée si c'est le cas est 'ClassNotFound' qui est assez trompeur, mettez quelque chose comme ceci dans votre manifeste (consultez Google pour la version la plus récente).

<manifest ...>

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

Je ne sais pas si vous pouvez y parvenir en chargeant dynamiquement Java code. Peut-être pouvez-vous essayer d'incorporer un moteur de script votre code comme rhino qui peut exécuter Java des scripts qui peuvent être téléchargés et mis à jour dynamiquement.

1
Naresh

bien sûr, c'est possible. apk qui n'est pas installé peut être invoqué par l'hôte Android application.généralement, résoudre le cycle de vie de la ressource et de l'activité, puis, peut charger jar ou apk dynamiquement. détail, veuillez vous référer à mes recherches open source sur github : https://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md

aussi, DexClassLoader et la réflexion sont nécessaires, regardez maintenant un code clé:

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

vos demandes ne sont que partiellement fonctionnelles dans le projet open source mentionné au début.

1
singwhatiwanna

Techniquement, cela devrait fonctionner, mais qu'en est-il des règles de Google? De: play.google.com/intl/en-GB/about/developer-content-policy-pr‌ int

Une application distribuée via Google Play ne peut pas se modifier, se remplacer ou se mettre à jour par une méthode autre que le mécanisme de mise à jour de Google Play. De même, une application ne peut pas télécharger de code exécutable (par exemple des fichiers dex, JAR, .so) à partir d'une source autre que Google Play. Cette restriction ne s'applique pas au code qui s'exécute sur une machine virtuelle et a un accès limité aux API Android (comme JavaScript dans une WebView ou un navigateur).

0
Marcin

Je pense que la réponse @Shlublu est correcte mais je veux juste souligner certains points clés.

  1. Nous pouvons charger toutes les classes à partir d'un fichier jar et apk externe.
  2. De toute façon, nous pouvons charger Activity depuis un pot externe mais nous ne pouvons pas le démarrer à cause du concept de contexte.
  3. Pour charger l'interface utilisateur à partir d'un pot externe, nous pouvons utiliser un fragment. Créez l'instance du fragment et incorporez-la à l'activité. Mais assurez-vous que fragment crée l'interface utilisateur dynamiquement comme indiqué ci-dessous.

    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