75

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

次に例を示します。

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

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

4

7 に答える 7

97

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

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

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カードにとして保存したDEXファイルにパッケージ化しました/sdcard/shlublu.jar

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

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()ます。

そしてそれは機能します... MyClassLogCatで定義されたトレースが表示されます。

ここに画像の説明を入力してください

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

これは、アプリケーションがダウンロードして実行するための外部DEXファイルを作成できることを意味します。ここでは難しい方法(醜いObjectキャスト、醜い呼び出し...)が行われましたが、何かをよりクリーンにするためMethod.invoke()にsで遊ぶことが可能でなければなりません。Interface

わお。私は最初に驚いた。私は期待していたSecurityException

詳細調査に役立ついくつかの事実:

  • 私のDEXshlublu.jarは署名されましたが、私のアプリは署名されていません
  • 私のアプリはEclipse/USB接続から実行されました。つまり、これはDEBUGモードでコンパイルされた署名されていないAPKです。
于 2011-07-28T14:25:20.903 に答える
16

Shlubluのanwserは本当にいいです。それは初心者を助けるでしょうが、いくつかの小さなこと:

  • ライブラリファイル「MyClass」の場合は、MyClassファイルをsrcフォルダー内の唯一のファイルとして持つ別のAndroidアプリケーションプロジェクトを作成します(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アプリケーションとして実行し、プロジェクトのbinフォルダーから.apkファイルを見つけます。

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

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

  • 協力してくれたShlubluに大いに感謝します。
于 2014-09-09T15:36:42.710 に答える
4

技術的には機能するはずですが、Googleのルールはどうですか?差出人: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など)へのアクセスが制限されているコードには適用されません。

于 2017-12-07T10:40:44.263 に答える
1

Javaコードを動的にロードすることでこれを実現できるかどうかはわかりません。動的にダウンロードして更新できるjavaスクリプトを実行できるrhinoのようなコードをスクリプトエンジンに埋め込んでみることができるかもしれません。

于 2011-07-28T11:03:34.703 に答える
1

確かに、それは可能です。インストールされていないapkは、ホストandroidアプリケーションによって呼び出すことができます。一般に、リソースとアクティビティのライフサークルを解決してから、jarまたはapkを動的にロードできます。詳細については、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;
}

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

于 2014-12-06T05:43:07.723 に答える
1

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

<manifest ...>

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
                 android:maxSdkVersion="18" />
    ...
</manifest>
于 2014-12-13T02:13:32.677 に答える
1

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

  1. 外部のjarファイルとapkファイルから任意のクラスをロードできます。
  2. いずれにせよ、外部jarからActivityをロードすることはできますが、コンテキストの概念のために開始することはできません。
  3. 外部jarからUIをロードするには、fragmentを使用できます。フラグメントのインスタンスを作成し、アクティビティに埋め込みます。ただし、以下に示すように、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;
     }
    }
    
于 2018-09-18T11:46:32.463 に答える