web-dev-qa-db-ja.com

プログラムによるAPKのインストール/アンインストール(PackageManager vs Intents)

私のアプリケーションは他のアプリケーションをインストールしますが、インストールしたアプリケーションを追跡する必要があります。もちろん、これはインストールされたアプリケーションのリストを単に保持することで達成できます。しかし、これは必要ではないはずです! installedBy(a、b)関係を維持するのは、PackageManagerの責任です。実際、APIによると:

パブリック抽象文字列 getInstallerPackageName(文字列packageName)- パッケージをインストールしたアプリケーションのパッケージ名を取得します。これは、パッケージがどの市場から来たかを識別します。

現在のアプローチ

Intentを使用してAPKをインストールする

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(apkUri, "application/vnd.Android.package-archive");
startActivity(intent);

Intentを使用してAPKをアンインストールします。

Intent intent = new Intent(Intent.ACTION_DELETE, Uri.fromParts("package",
getPackageManager().getPackageArchiveInfo(apkUri.getPath(), 0).packageName,null));
startActivity(intent);

これは明らかに方法ではありません。 Android Marketは、パッケージをインストール/アンインストールします。 PackageManagerのより豊富なバージョンを使用します。これは、Android GitリポジトリからAndroidソースコードをダウンロードすることで確認できます。以下は、Intentアプローチに対応する2つの隠しメソッドです。残念ながら、外部の開発者は利用できません。しかし、おそらく彼らは将来になるのでしょうか?

より良いアプローチ

PackageManagerを使用してAPKをインストールする

/**
 * @hide
 * 
 * Install a package. Since this may take a little while, the result will
 * be posted back to the given observer.  An installation will fail if the calling context
 * lacks the {@link Android.Manifest.permission#INSTALL_PACKAGES} permission, if the
 * package named in the package file's manifest is already installed, or if there's no space
 * available on the device.
 *
 * @param packageURI The location of the package file to install.  This can be a 'file:' or a
 * 'content:' URI.
 * @param observer An observer callback to get notified when the package installation is
 * complete. {@link IPackageInstallObserver#packageInstalled(String, int)} will be
 * called when that happens.  observer may be null to indicate that no callback is desired.
 * @param flags - possible values: {@link #INSTALL_FORWARD_LOCK},
 * {@link #INSTALL_REPLACE_EXISTING}, {@link #INSTALL_ALLOW_TEST}.
 * @param installerPackageName Optional package name of the application that is performing the
 * installation. This identifies which market the package came from.
 */
public abstract void installPackage(
        Uri packageURI, IPackageInstallObserver observer, int flags,
        String installerPackageName);

PackageManagerを使用したAPKのアンインストール

/**
 * Attempts to delete a package.  Since this may take a little while, the result will
 * be posted back to the given observer.  A deletion will fail if the calling context
 * lacks the {@link Android.Manifest.permission#DELETE_PACKAGES} permission, if the
 * named package cannot be found, or if the named package is a "system package".
 * (TODO: include pointer to documentation on "system packages")
 *
 * @param packageName The name of the package to delete
 * @param observer An observer callback to get notified when the package deletion is
 * complete. {@link Android.content.pm.IPackageDeleteObserver#packageDeleted(boolean)} will be
 * called when that happens.  observer may be null to indicate that no callback is desired.
 * @param flags - possible values: {@link #DONT_DELETE_DATA}
 *
 * @hide
 */
public abstract void deletePackage(
        String packageName, IPackageDeleteObserver observer, int flags);

違い

  • インテントを使用する場合、ローカルパッケージマネージャーは、インストール元のアプリケーションを認識しません。具体的には、getInstallerPackageName(...)はnullを返します。

  • 隠しメソッドinstallPackage(...)は、インストーラーパッケージ名をパラメーターとして使用し、ほとんどの場合、この値を設定できます。

質問

インテントを使用してパッケージインストーラー名を指定することは可能ですか?(インストーラーパッケージの名前は、インストールの目的に追加することができますか?)

ヒント:Androidソースコードをダウンロードする場合は、ソースツリーのダウンロードで説明されている手順に従ってください。 * .Javaファイルを抽出し、パッケージ階層に従ってフォルダーに入れるには、このすっきりしたスクリプトを確認してください: View Android Source Code in Eclipse

136
Håvard Geithus

これは現在、サードパーティのアプリケーションでは利用できません。リフレクションまたはその他のトリックを使用してinstallPackage()にアクセスしても、システムアプリケーションのみが使用できるため、役に立ちません。 (これは、アクセス許可がユーザーによって承認された後の低レベルのインストールメカニズムであるため、通常のアプリケーションがアクセスすることは安全ではないためです。)

また、プラットフォームのリリース間でinstallPackage()関数の引数が頻繁に変更されるため、アクセスしようとすると、プラットフォームのさまざまなバージョンで失敗します。

編集:

また、このinstallerPackageはごく最近プラットフォームに追加された(2.2?)だけであり、もともとアプリのインストール者を追跡するために実際には使用されていなかったことを指摘する価値があります。 Androidフィードバックを実装するためのアプリ。 (これは、APIメソッドの引数が変更されたときの1つでもありました。)導入されてから少なくとも長い間、Marketはそれを使用してインストールしたアプリを追跡しませんでした)、代わりにこれを使用して、フィードバックを処理する「所有者」としてAndroidフィードバックアプリ(Marketとは別のもの)を設定しました。

66
hackbod

Android P +では、AndroidManifest.xmlでこの権限が必要です

<uses-permission Android:name="Android.permission.REQUEST_DELETE_PACKAGES" />

次に:

Intent intent = new Intent(Intent.ACTION_DELETE);
intent.setData(Uri.parse("package:com.example.mypackage"));
startActivity(intent);

アンインストールします。簡単そうです...

73
JohnyTex

APIレベル14では、2つの新しいアクション ACTION_INSTALL_PACKAGEACTION_UNINSTALL_PACKAGE が導入されました。これらのアクションを使用すると、 EXTRA_RETURN_RESULT boolean extraを渡して、インストール結果通知(アン)を取得できます。

アンインストールダイアログを呼び出すためのサンプルコード:

String app_pkg_name = "com.example.app";
int UNINSTALL_REQUEST_CODE = 1;

Intent intent = new Intent(Intent.ACTION_UNINSTALL_PACKAGE);  
intent.setData(Uri.parse("package:" + app_pkg_name));  
intent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
startActivityForResult(intent, UNINSTALL_REQUEST_CODE);

Activity#onActivityResult メソッドで通知を受け取ります:

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == UNINSTALL_REQUEST_CODE) {
        if (resultCode == RESULT_OK) {
            Log.d("TAG", "onActivityResult: user accepted the (un)install");
        } else if (resultCode == RESULT_CANCELED) {
            Log.d("TAG", "onActivityResult: user canceled the (un)install");
        } else if (resultCode == RESULT_FIRST_USER) {
            Log.d("TAG", "onActivityResult: failed to (un)install");
        }
    }
}
38
Pir Fahim Shah

デバイス所有者(またはプロファイル所有者、私は試していません)のアクセス許可がある場合、デバイス所有者APIを使用してパッケージをサイレントインストール/アンインストールできます。

アンインストール用:

public boolean uninstallPackage(Context context, String packageName) {
    ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
    PackageManager packageManger = context.getPackageManager();
    if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.Lollipop) {
        PackageInstaller packageInstaller = packageManger.getPackageInstaller();
        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        params.setAppPackageName(packageName);
        int sessionId = 0;
        try {
            sessionId = packageInstaller.createSession(params);
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
        packageInstaller.uninstall(packageName, PendingIntent.getBroadcast(context, sessionId,
                new Intent("Android.intent.action.MAIN"), 0).getIntentSender());
        return true;
    }
    System.err.println("old sdk");
    return false;
}

そして、パッケージをインストールするには:

public boolean installPackage(Context context,
                                     String packageName, String packagePath) {
    ComponentName name = new ComponentName(MyAppName, MyDeviceAdminReceiver.class.getCanonicalName());
    PackageManager packageManger = context.getPackageManager();
    if (Android.os.Build.VERSION.SDK_INT >= Android.os.Build.VERSION_CODES.Lollipop) {
        PackageInstaller packageInstaller = packageManger.getPackageInstaller();
        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        params.setAppPackageName(packageName);
        try {
            int sessionId = packageInstaller.createSession(params);
            PackageInstaller.Session session = packageInstaller.openSession(sessionId);
            OutputStream out = session.openWrite(packageName + ".apk", 0, -1);
            readTo(packagePath, out); //read the apk content and write it to out
            session.fsync(out);
            out.close();
            System.out.println("installing...");
            session.commit(PendingIntent.getBroadcast(context, sessionId,
                    new Intent("Android.intent.action.MAIN"), 0).getIntentSender());
            System.out.println("install request sent");
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }
    System.err.println("old sdk");
    return false;
}
22
Ohad Cohen

これらのメソッドにアクセスする唯一の方法は、リフレクションを使用することです。 getApplicationContext().getPackageManager()を呼び出し、リフレクションを使用してこれらのメソッドにアクセスすることにより、PackageManagerオブジェクトのハンドルを取得できます。チェックアウト this チュートリアル。

4
HandlerExploit

Froyoのソースコードによると、Intent.EXTRA_INSTALLER_PACKAGE_NAMEの追加キーは、PackageInstallerActivityのインストーラーパッケージ名について照会されます。

3
njzk2

ルート化されたデバイスでは、次を使用できます。

String pkg = context.getPackageName();
String shellCmd = "rm -r /data/app/" + pkg + "*.apk\n"
                + "rm -r /data/data/" + pkg + "\n"
                // TODO remove data on the sd card
                + "sync\n"
                + "reboot\n";
Util.Sudo(shellCmd);

Util.Sudo()はここで定義されます。

パッケージ名をパラメーターとしてユーザー定義関数のいずれかに渡す場合は、次のコードを使用します。

    Intent intent=new Intent(Intent.ACTION_DELETE);
    intent.setData(Uri.parse("package:"+packageName));
    startActivity(intent);
1
Rashwin S M

前提条件:

APKは、以前に正しく指摘されているようにシステムによって署名される必要があります。それを実現する1つの方法は、AOSPイメージを自分でビルドし、ビルドにソースコードを追加することです。

コード:

システムアプリとしてインストールすると、パッケージマネージャーメソッドを使用して、次のようにAPKをインストールおよびアンインストールできます。

インストール:

public boolean install(final String apkPath, final Context context) {
    Log.d(TAG, "Installing apk at " + apkPath);
    try {
        final Uri apkUri = Uri.fromFile(new File(apkPath));
        final String installerPackageName = "MyInstaller";
        context.getPackageManager().installPackage(apkUri, installObserver, PackageManager.INSTALL_REPLACE_EXISTING, installerPackageName);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

アンインストール:

public boolean uninstall(final String packageName, final Context context) {
    Log.d(TAG, "Uninstalling package " + packageName);
    try {
        context.getPackageManager().deletePackage(packageName, deleteObserver, PackageManager.DELETE_ALL_USERS);
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    }
}

APKのインストール/アンインストール後にコールバックを使用するには、これを使用できます。

/**
 * Callback after a package was installed be it success or failure.
 */
private class InstallObserver implements IPackageInstallObserver {

    @Override
    public void packageInstalled(String packageName, int returnCode) throws RemoteException {

        if (packageName != null) {
            Log.d(TAG, "Successfully installed package " + packageName);
            callback.onAppInstalled(true, packageName);
        } else {
            Log.e(TAG, "Failed to install package.");
            callback.onAppInstalled(false, null);
        }
    }

    @Override
    public IBinder asBinder() {
        return null;
    }
}

/**
 * Callback after a package was deleted be it success or failure.
 */
private class DeleteObserver implements IPackageDeleteObserver {

    @Override
    public void packageDeleted(String packageName, int returnCode) throws RemoteException {
        if (packageName != null) {
            Log.d(TAG, "Successfully uninstalled package " + packageName);
            callback.onAppUninstalled(true, packageName);
        } else {
            Log.e(TAG, "Failed to uninstall package.");
            callback.onAppUninstalled(false, null);
        }
    }

    @Override
    public IBinder asBinder() {
        return null;
    }
}

/**
 * Callback to give the flow back to the calling class.
 */
public interface InstallerCallback {
    void onAppInstalled(final boolean success, final String packageName);
    void onAppUninstalled(final boolean success, final String packageName);
}
0
phoebus

Kotlin、API 14+を使用していて、アプリのアンインストールダイアログを表示したい場合:

startActivity(Intent(Intent.ACTION_UNINSTALL_PACKAGE).apply {
    data = Uri.parse("package:$packageName")
})

デバイス上の別のアプリをアンインストールするようユーザーに求める場合は、packageNameを他のパッケージ名に変更できます。

0
Louis CAD