Ist es möglich, eine Bibliothek zur Laufzeit dynamisch aus einer Android-Anwendung zu laden?

73

Gibt es eine Möglichkeit, eine Android-Anwendung zum Herunterladen und Verwenden einer Java-Bibliothek zur Laufzeit zu erstellen?

Hier ist ein Beispiel:

Stellen Sie sich vor, die Anwendung muss abhängig von den Eingabewerten einige Berechnungen durchführen. Die Anwendung fragt nach diesen Eingabewerten und prüft dann, ob die erforderlichen Classes oder Methods verfügbar sind.

Wenn nicht, stellt es eine Verbindung zu einem Server her, lädt die erforderliche Bibliothek herunter und lädt sie zur Laufzeit, um die erforderlichen Methoden mithilfe von Reflektionstechniken aufzurufen. Die Implementierung kann sich abhängig von verschiedenen Kriterien ändern, z. B. dem Benutzer, der die Bibliothek herunterlädt.

lluismontero
quelle

Antworten:

94

Entschuldigung, ich bin spät dran und die Frage hat bereits eine akzeptierte Antwort, aber ja , Sie können externe Bibliotheken herunterladen und ausführen. So habe ich es gemacht:

Ich habe mich gefragt, ob dies machbar ist, also habe ich die folgende Klasse geschrieben:

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

Und ich habe es in eine DEX-Datei gepackt, die ich auf der SD-Karte meines Geräts gespeichert habe /sdcard/shlublu.jar.

Dann schrieb ich das "dumme Programm" unten, nachdem ich es MyClassaus meinem Eclipse-Projekt entfernt und bereinigt hatte:

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

Grundsätzlich wird die Klasse folgendermaßen MyClassgeladen:

  • ein ... kreieren DexClassLoader

  • Verwenden Sie es, um die Klasse MyClassaus zu extrahieren"/sdcard/shlublu.jar"

  • und speichern Sie diese Klasse im "dex"privaten Verzeichnis der Anwendung (interner Speicher des Telefons).

Anschließend wird eine Instanz von erstellt MyClassund doSomething()die erstellte Instanz aufgerufen.

Und es funktioniert ... Ich sehe die MyClassin meinem LogCat definierten Spuren :

Geben Sie hier die Bildbeschreibung ein

Ich habe sowohl einen Emulator 2.1 als auch mein physisches HTC-Handy (auf dem Android 2.2 ausgeführt wird und das NICHT gerootet ist) ausprobiert.

Dies bedeutet, dass Sie externe DEX-Dateien erstellen können, die von der Anwendung heruntergeladen und ausgeführt werden können. Hier wurde es auf die harte Tour gemacht (hässliche ObjectCasts, Method.invoke()hässliche Calls ...), aber es muss möglich sein, mit Interfaces zu spielen , um etwas sauberer zu machen.

Beeindruckend. Ich bin der erste überrascht. Ich hatte a erwartet SecurityException.

Einige Fakten zur weiteren Untersuchung:

  • Mein DEX shlublu.jar wurde signiert, aber nicht meine App
  • Meine App wurde über eine Eclipse / USB-Verbindung ausgeführt. Dies ist also eine nicht signierte APK, die im DEBUG-Modus kompiliert wurde
Shlublu
quelle
4
Toll! Das ist wirklich was ich gesucht habe !! Wie Sie hier sehen können ( groups.google.com/group/android-security-discuss/browse_thread/… ), gibt es kein wirkliches Sicherheitsproblem, da der Code mit der Erlaubnis Ihrer App ausgeführt wird.
lluismontero
Das macht durchaus Sinn, wenn auch auf den ersten Blick überraschend. Danke für die Warnung!
Shlublu
3
Schauen Sie sich das auch an: android-developers.blogspot.com/2011/07/…
1
Großartig, vielen Dank! Ja, das ist genau die gleiche Logik. Es muss ziemlich ungewöhnlich sein, 64k-Methoden zu überschreiten, daher sind Plugins definitiv die gültige Verwendung dafür.
Shlublu
1
Ich weiß, dass dies ein alter Thread ist, aber ich hatte gehofft, dass jemand hier helfen könnte. Ich versuche das Gleiche zu tun (ein Plugin dynamisch laden). Hat jemand diese Arbeit durch Casting auf eine Schnittstelle gemacht?
Cstrutton
15

Shlublus Antwort ist wirklich nett. Einige kleine Dinge, die einem Anfänger helfen würden:

  • Für die Bibliotheksdatei "MyClass" erstellen Sie ein separates Android-Anwendungsprojekt, das die MyClass-Datei als einzige Datei im src-Ordner enthält (andere Dinge wie project.properties, manifest, res usw. sollten ebenfalls vorhanden sein).
  • <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".NotExecutable" android:label="@string/app_name"> </activity> </application> Stellen Sie im Manifest des Bibliotheksprojekts sicher, dass Sie Folgendes haben: (".NotExecutable" ist kein reserviertes Wort. Ich musste nur etwas hier einfügen.)

  • Um die .dex-Datei zu erstellen, führen Sie einfach das Bibliotheksprojekt als Android-Anwendung (zum Kompilieren) aus und suchen Sie die .apk-Datei aus dem bin-Ordner des Projekts.

  • Kopieren Sie die APK-Datei auf Ihr Telefon und benennen Sie sie in shlublu.jar-Datei um (eine APK ist jedoch eine Spezialisierung einer JAR-Datei).

Andere Schritte sind die gleichen wie von Shlublu beschrieben.

  • Vielen Dank an Shlublu für die Zusammenarbeit.
Pätris Halapuu
quelle
Gern geschehen, Pätris, und es war mir eine Freude, mit Ihnen zu diskutieren. (Ich habe diesen Kommentar ursprünglich geschrieben, als ich Ihre Antwort + 1 gegeben habe, aber ich habe offensichtlich vergessen, die Schaltfläche "Kommentar
hinzufügen
Ich weiß den Trick zu schätzen, aber die Reproduktion des obigen Beispiels von @Shlublu mit Ihrer Methode führt zu einer Apk von ~ 380 KB. Wenn ich nur ein Bibliotheksprojekt erstelle, erhalte ich eine 3-KB-JAR. Der Bibliotheksansatz erfordert jedoch einen zusätzlichen Befehl dx --dex --keep-classes --output=newjar.jar libproject.jar. Vielleicht gibt es eine Möglichkeit, diesen Dex-Schritt automatisch zu machen, wenn Eclipse die Bibliothek erstellt.
Harvey
Ich bin verwirrt, warum wir apk-Datei verwenden?! Ich bin neu in Xamarin Android. Ich möchte eine Bibliotheksprojekt-DLL (Class Library) erstellen und diese dann zur Laufzeit in meiner Haupt-App verwenden. Wie ist das möglich?
Mohammad Afrashteh
1

Ich bin nicht sicher, ob Sie dies erreichen können, indem Sie Java-Code dynamisch laden. Möglicherweise können Sie versuchen, eine Skript-Engine in Ihren Code wie Rhino einzubetten, die Java-Skripte ausführen kann, die dynamisch heruntergeladen und aktualisiert werden können.

Naresh
quelle
Vielen Dank. Ich werde diesen Ansatz untersuchen (wusste bis heute nichts über Skripte auf Android). Weitere Informationen (falls jemand sie benötigt) finden Sie hier: google-opensource.blogspot.com/2009/06/…
lluismontero
1
@Naresh Ja, Sie können auch Java-kompilierten Code herunterladen und ausführen. Siehe meine Antwort auf dieser Seite. Das hätte ich eigentlich nicht erwartet.
Shlublu
1
Danke @shlublu scheint interessant :). Das einzige Problem, an das ich denken kann, ist, dass es stark von Reflexionen abhängt oder dass Sie anfangen müssen, Wrapper zu schreiben. Ich sehe viele Verwaltungsprobleme, wenn wir den gesamten Code auf diese Weise schreiben müssen. Aus diesem Grund dachte ich, dass Skripte eine einfache und gute Möglichkeit sind, Geschäftslogik zu verwalten.
Naresh
Wie würden Sie auch anfangen, eigenständige Dex-JAR-Dateien zu generieren? Sie würden separate Android-Anwendungsprojekte erstellen? Ab sofort gibt es kein Konzept für statische Bibliotheken in Android Rite?
Naresh
1
Schauen Sie sich das Verzeichnis / system / framework Ihres Geräts an, es ist voller Gläser, die auf diese Weise hergestellt wurden. (kein Wurzelrecht erforderlich)
Shlublu
1

sicher ist es möglich. apk, das nicht installiert ist, kann von der Host-Android-Anwendung aufgerufen werden. Im Allgemeinen können Sie den Lebenskreis von Ressourcen und Aktivitäten auflösen und dann jar oder apk dynamisch laden. Einzelheiten finden Sie in meiner Open-Source-Forschung zu Github: https://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md

Außerdem sind DexClassLoader und Reflection erforderlich. Sehen Sie sich nun einen Schlüsselcode an:

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

Ihre Anforderungen sind in dem eingangs erwähnten Open-Source-Projekt nur teilweise von Bedeutung.

singwhatiwanna
quelle
Ich habe einen Fehler bei der Verwendung Ihres Projekts erhalten, es hat immer noch das gleiche Problem mit meinem vorherigen Projekt. ClassNotFound beim StartActivity ..
KDoX
Ich bin neu in der Android-Entwicklung. Ich weiß nicht, warum wir apk verwenden sollten, um dynamische Methoden zu laden?! Ich möchte meine vom Klassenbibliotheksprojekt erstellte DLL zur Laufzeit dynamisch laden.
Mohammad Afrashteh
1

Wenn Sie Ihre .DEX-Dateien im externen Speicher des Telefons aufbewahren, z. B. auf der SD-Karte (nicht empfohlen! Jede App mit denselben Berechtigungen kann Ihre Klasse leicht überschreiben und einen Code-Injection-Angriff ausführen), stellen Sie sicher, dass Sie die angegeben haben App-Berechtigung zum Lesen des externen Speichers. Die Ausnahme, die ausgelöst wird, wenn dies der Fall ist, ist "ClassNotFound", was ziemlich irreführend ist. Fügen Sie Folgendes in Ihr Manifest ein (konsultieren Sie Google für die aktuellste Version).

<manifest ...>

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

Technisch sollte funktionieren, aber was ist mit Google-Regeln? Von: play.google.com/intl/en-GB/about/developer-content-policy-pr‌ int

Eine über Google Play vertriebene App darf sich nicht mit einer anderen Methode als dem Aktualisierungsmechanismus von Google Play ändern, ersetzen oder aktualisieren. Ebenso darf eine App keinen ausführbaren Code (z. B. Dex-, JAR-, .so-Dateien) von einer anderen Quelle als Google Play herunterladen. Diese Einschränkung gilt nicht für Code, der in einer virtuellen Maschine ausgeführt wird und nur eingeschränkten Zugriff auf Android-APIs (z. B. JavaScript in einer WebView oder einem Browser) hat.

Marcin
quelle
1
Das ist absolut richtig. Das OP fragte jedoch, ob dies technisch machbar sei und möchte dies möglicherweise für seinen eigenen Zweck tun, ohne Google Play überhaupt zu verwenden. Es sieht auch so aus, als ob eine Google Play-App Jars einbetten könnte, die zur Bereitstellungszeit in den lokalen Speicher geschrieben und später von der App geladen würden, ohne gegen diese Regel zu verstoßen (unabhängig davon, ob dies eine gute Idee wäre). Sie sollten Entwickler jedoch zu Recht daran erinnern, dass eine von Google Play heruntergeladene App keinen Code von einer anderen Stelle herunterladen sollte. Dies sollte jedoch ein Kommentar sein.
Shlublu
1

Ich denke, die Antwort von @Shlublu ist richtig, aber ich möchte nur einige wichtige Punkte hervorheben.

  1. Wir können alle Klassen aus externen JAR- und APK-Dateien laden.
  2. In jeder Hinsicht können wir Activity aus einem externen JAR laden, aber wir können es aufgrund des Kontextkonzepts nicht starten.
  3. Um die Benutzeroberfläche aus einem externen JAR zu laden, können wir Fragment verwenden. Erstellen Sie die Instanz des Fragments und binden Sie es in die Aktivität ein. Stellen Sie jedoch sicher, dass das Fragment die Benutzeroberfläche dynamisch wie unten angegeben erstellt.

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