web-dev-qa-db-ja.com

Androidアプリケーションから実行時にライブラリを動的にロードすることは可能ですか?

Android実行時にダウンロードしてJavaライブラリを使用するアプリケーションを作成する方法はありますか?

以下に例を示します。

アプリケーションが入力値に応じていくつかの計算を行う必要があると想像してください。アプリケーションはこれらの入力値を要求し、必要なClassesまたはMethodsが利用可能かどうかを確認します。

そうでない場合、サーバーに接続し、必要なライブラリをダウンロードし、実行時にロードして、リフレクション技術を使用して必要なメソッドを呼び出します。実装は、ライブラリをダウンロードするユーザーなどのさまざまな基準に応じて変更される可能性があります。

66
lluismontero

申し訳ありませんが、私は遅れており、質問はすでに受け入れられていますが、yes、外部ライブラリをダウンロードして実行できます。ここに私がやった方法があります:

これが実現可能かどうか疑問に思っていたので、次のクラスを作成しました。

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

そして、デバイスのSDカードに_/sdcard/shlublu.jar_として保存したDEXファイルにパッケージ化しました。

次に、EclipseプロジェクトからMyClassを削除してクリーンアップした後、以下の「愚かなプログラム」を作成しました。

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

基本的にクラスMyClassをそのようにロードします:

  • DexClassLoader を作成します

  • クラスを使用して、MyClassを_"/sdcard/shlublu.jar"_から抽出します

  • このクラスをアプリケーションの_"dex"_プライベートディレクトリ(電話の内部ストレージ)に保存します。

次に、MyClassのインスタンスを作成し、作成されたインスタンスでdoSomething()を呼び出します。

そしてそれは動作します... LogCatのMyClassで定義されたトレースが表示されます:

enter image description here

エミュレーター2.1と物理的なHTC携帯電話(Android 2.2で、ルート化されていない)の両方で試してみました。

これは、アプリケーションがダウンロードして実行する外部DEXファイルを作成できることを意味します。ここでは難しい方法(madeいObjectキャスト、Method.invoke() ugい呼び出し...)が行われましたが、Interfacesで遊んで何かをきれいにする必要があります。

ワオ。私は最初に驚いた。 SecurityExceptionを期待していました。

より多くの調査に役立ついくつかの事実:

  • DEX shlublu.jarに署名しましたが、アプリではありません
  • 私のアプリはEclipse/USB接続から実行されました。したがって、これはDEBUGモードでコンパイルされた署名のないAPKです
88
Shlublu

Shlubluのアンサーは本当にいいです。いくつかの小さなことは初心者にも役立ちます:

  • ライブラリファイル「MyClass」の場合、別のAndroid srcフォルダー内のファイルとしてのみMyClassファイルを持つアプリケーションプロジェクト(project.properties、manifest、resなどのその他のものも必要です)そこにいる)
  • ライブラリプロジェクトマニフェストで、次のことを確認してください:<application Android:icon="@drawable/icon" Android:label="@string/app_name"> <activity Android:name=".NotExecutable" Android:label="@string/app_name"> </activity> </application>( ".NotExecutable"は予約語ではありません。ここに何かを入れなければなりませんでした)

  • .dexファイルを作成するには、ライブラリプロジェクトをAndroid application(コンパイル用))として実行し、プロジェクトのbinフォルダーから.apkファイルを探します。

  • .apkファイルを電話にコピーし、shlublu.jarファイルに名前を変更します(APKは実際にはjarの特殊化です)

その他の手順は、Shlubluの説明と同じです。

  • 協力してくれたShlubluに感謝します。
14
Pätris Halapuu

.DEXファイルを電話の外部メモリ(SDカードなど)に保存している場合(推奨されません!同じアクセス許可を持つアプリは、クラスを簡単に上書きしてコードインジェクション攻撃を実行できます)外部メモリを読み取るためのアプリの許可。この場合にスローされる例外は、誤解を招く「ClassNotFound」です。マニフェストに次のようなものを入れてください(最新バージョンについては、Googleにご相談ください)。

<manifest ...>

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

Javaコードを動的にロードすることでこれを達成できるかどうかはわかりません。おそらく、Java動的にダウンロードおよび更新できるスクリプト。

1
Naresh

確かに、それは可能です。インストールされていないapkはホストによって呼び出すことができますAndroid application.generally、resolve resource and activity's lifecircle、then、can can load jar or apk can.detail、please my open source research on github : https://github.com/singwhatiwanna/dynamic-load-apk/blob/master/README-en.md

また、DexClassLoaderとリフレクションが必要です。今、いくつかの重要なコードを見てください:

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

あなたの要求は、冒頭で述べたオープンソースプロジェクトの機能の一部にすぎません。

1
singwhatiwanna

技術的には機能するはずですが、Googleのルールはどうですか? From:play.google.com/intl/en-GB/about/developer-content-policy-pr‌ int

Google Playを介して配信されるアプリは、Google Playの更新メカニズム以外の方法を使用して、それ自体を変更、置換、または更新することはできません。同様に、アプリはGoogle Play以外のソースから実行可能コード(dex、JAR、.soファイルなど)をダウンロードできません。この制限は、仮想マシンで実行され、Android API(WebViewまたはブラウザのJavaScriptなど)へのアクセスが制限されているコードには適用されません。

0
Marcin

@Shlubluの答えは正しいと思いますが、いくつかの重要なポイントを強調したいだけです。

  1. 外部のjarおよびapkファイルから任意のクラスをロードできます。
  2. いずれにしても、外部jarからActivityをロードできますが、コンテキストの概念のために開始できません。
  3. 外部jarからUIをロードするには、fragmentを使用できます。フラグメントのインスタンスを作成し、アクティビティに埋め込みます。ただし、フラグメントが以下に示すようにUIを動的に作成することを確認してください。

    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