프로그래밍 방식으로 APK 설치 / 제거 (PackageManager 및 의도)


142

내 응용 프로그램은 다른 응용 프로그램을 설치하며 설치된 응용 프로그램을 추적해야합니다. 물론 설치된 응용 프로그램 목록을 유지하면됩니다. 그러나 이것은 필요하지 않습니다! installedBy (a, b) 관계를 유지하는 것은 PackageManager의 책임입니다. 실제로 API에 따르면 다음과 같습니다.

public abstract String getInstallerPackageName (String packageName)- 패키지를 설치 한 응용 프로그램의 패키지 이름을 검색합니다. 이것은 패키지가 어느 시장에서 왔는지 식별합니다.

현재 접근

의도를 사용하여 APK 설치

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

의도를 사용하여 APK 제거 :

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

예를 들어 Android 마켓에서 패키지를 설치 / 제거하는 방식은 아닙니다. 더 풍부한 버전의 PackageManager를 사용합니다. Android Git 리포지토리에서 Android 소스 코드를 다운로드하면 알 수 있습니다. 아래는 의도 접근 방식에 해당하는 두 가지 숨겨진 방법입니다. 불행히도 외부 개발자는 사용할 수 없습니다. 그러나 아마도 그들은 미래에 있을까요?

더 나은 접근법

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 파일을 추출하여 패키지 계층 구조에 따라 폴더에 넣으려면 다음과 같은 깔끔한 스크립트를 확인할 수 있습니다 . Eclipse에서 Android 소스 코드보기 .


텍스트에 일부 URI가 없습니다. 허용 되 자마자 추가 할 것입니다 (새로운 사용자에게는 스팸 방지를위한 몇 가지 제한 사항이 있음).
Håvard Geithus

1
제거 기능을 비활성화하는 방법은 무엇입니까?

2
@ user938893 : "제거 기능을 어떻게 비활성화합니까?" -제거하기 어려운 악성 코드를 개발하고 있습니까?
Daniel

답변:


66

현재 타사 응용 프로그램에서는 사용할 수 없습니다. 리플렉션 또는 기타 트릭을 사용하여 installPackage ()에 액세스하더라도 시스템 응용 프로그램 만 사용할 수 있으므로 도움이되지 않습니다. (사용자가 권한을 승인 한 후에는 저수준 설치 메커니즘이기 때문에 일반 응용 프로그램이 액세스하기에 안전하지 않습니다.)

또한 installPackage () 함수 인수는 종종 플랫폼 릴리스간에 변경되었으므로 액세스하려고 시도하는 모든 다른 버전의 플랫폼에서 실패합니다.

편집하다:

또한이 installerPackage는 플랫폼 (2.2?)에 상당히 최근에 추가되었으며 원래 누가 앱을 설치했는지 추적하는 데 사용되지 않았습니다. 플랫폼에서 버그를보고 할 때 누가 시작 해야하는지 결정하는 데 사용됩니다 Android 피드백을 구현하기위한 앱 (이는 API 메소드 인수가 변경된 시간 중 하나이기도합니다.) 도입 된 지 오래 된 후에도 마켓은 여전히 ​​앱을 설치 한 앱을 추적하는 데 사용하지 않았습니다. ) 대신 Android 피드백 앱 (Market과 별 개인)을 "소유자"로 설정하여 피드백을 처리했습니다.


"반사 또는 기타 트릭을 사용하여 installPackage ()에 액세스하더라도 시스템 응용 프로그램 만 사용할 수 있으므로 도움이되지 않습니다." 기본 Android 자체가 아닌 특정 플랫폼에 패키지 설치 / 제거 / 관리 앱을 만들고 있다고 가정합니다. 설치 / 제거에 어떻게 액세스해야합니까?
dascandy

적절하게 구성된 의도가있는 startActivity (). (이것은 StackOverflow의 다른 곳에서 답변되었으므로 여기에 문제가 발생할 위험이있는 정확한 답변을 제공하려고 시도하지 않을 것입니다.)
hackbod

음, 표준 Android 설치 / 제거 대화 상자가 나타납니다. 이러한 세부 사항은 이미 처리되었습니다. "단지 ****이 패키지를 설치하십시오"및 "그냥 ****이 패키지를 제거하십시오"기능을 찾고 있습니다. 문자 그대로 질문이 없습니다.
dascandy

2
내가 말했듯이, 타사 응용 프로그램에서는 사용할 수 없습니다. 자체 시스템 이미지를 만드는 경우 플랫폼 구현이 있으며 여기에서 기능을 찾을 수 있지만 일반적인 타사 앱에서 사용할 수있는 API의 일부는 아닙니다.
hackbod

설치, 제거 및 백업 기능이있는 apk 파일 탐색기를 만들고 있으므로 Google이 Google Play에 내 응용 프로그램을 계속 게시 할 수 있습니까? 어떤 정책을 깰 것인가?
Rahul Mandaliya

86

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

제거합니다. 더 쉬운 것 같습니다 ...


이것이 코드를 실행하는 앱일 수 있습니까? onDestroy()방법 처럼 ?
Mahdi-Malv 2016 년

ACTION_INSTALL_PACKAGE는 어떻습니까? Play 스토어에서 최신 버전의 앱을 다운로드하여 설치할 수 있습니까?
MAS. John

3
Android P 삭제 앱은 "ACTION_DELETE"또는 "ACTION_UNINSTALL_PACKAGE" developer.android.com/reference/android/content/
Darklord5

Android P 권한을 언급 해 주셔서 감사합니다. 문제가 발생하여 이전에 무슨 일이 있었는지 잘 모르겠습니다.
Avi Parshan

43

API 레벨 14에는 ACTION_INSTALL_PACKAGEACTION_UNINSTALL_PACKAGE의 두 가지 새로운 조치가 도입되었습니다 . 이러한 작업을 통해 EXTRA_RETURN_RESULT 를 전달할 수 있습니다. 부울 하여 설치 결과 알림을받을 수 있습니다.

설치 제거 대화 상자를 호출하기위한 예제 코드 :

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

이 작업 대화 상자에서 사용자가 확인 또는 취소를 눌러이를 기반으로 결정을 내릴 수 있는지 확인하는 방법
Erum

2
@Erum 나는 당신이 요청한 것에 대한 예제를 추가했습니다
Alex Lipov

설치시 취소 단추가 onActivityResult 메소드로 결과를 얻지 못했습니다
diyoda_

2
API 25부터는 호출 ACTION_INSTALL_PACKAGE에 서명 수준 REQUEST_INSTALL_PACKAGES권한 이 필요합니다 . 마찬가지로 API 28 (Android P)부터 시작하면 ACTION_UNINSTALL_PACKAGE위험하지 않은 REQUEST_DELETE_PACKAGES권한 이 필요합니다 . 적어도 문서에 따르면.
Steve Blackwell

22

장치 소유자 (또는 프로필 소유자 인 경우 시도하지 않은) 권한이 있으면 장치 소유자 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;
}

이 작업을 소유자 장치로 수행하는 것이 가능해야한다는 것을 알고있었습니다. 답변 해주셔서 감사합니다!
Luke Cauthen

@sandeep 그것은 단지 APK의 내용을 출력 스트림으로 읽습니다
Ohad Cohen

@LukeCauthen 장치 소유자가 되려고 노력 했습니까? 효과가 있었습니까?
NetStarter

@NetStarter 예 있습니다. 앱을 장치 소유자로 만드는 것은 엉덩이에 고통입니다. 일단 그렇게하면 보통 루트가 필요한 많은 힘을 얻게됩니다.
Luke Cauthen

1
제거가 작동하려면 매니페스트에 android.permission.DELETE_PACKAGES를 추가해야합니다 (Api 레벨 22 이하에서 테스트 됨)
benchuk

4

이러한 방법에 액세스하는 유일한 방법은 리플렉션입니다. 이러한 메소드를 PackageManager호출 getApplicationContext().getPackageManager()하고 리플렉션 액세스를 사용하여 오브젝트에 대한 핸들을 얻을 수 있습니다 . 체크 아웃 튜토리얼을.


이것은 2.2 좋은 작품,하지만 난 2.3을 사용하여 운이 없었습니다
사람이 어딘가에

3
모든 API 버전에서 리플렉션이 안정적이지 않습니다
HandlerExploit

3

Froyo 소스 코드에 따르면 PackageInstallerActivity에서 설치 프로그램 패키지 이름에 대해 Intent.EXTRA_INSTALLER_PACKAGE_NAME 추가 키가 쿼리됩니다.


1
이보고에 의해 저지 내가이 일을해야한다고 생각
sergio91pt

2

루팅 된 기기에서 다음을 사용할 수 있습니다.

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() 여기에 정의되어 있습니다.


sdcard에 사전 다운로드 된 응용 프로그램을 설치하는 방법이 있습니까? 또는 Android 플랫폼의 쉘에서 사용할 수있는 명령을 확인하기 위해 페이지를 제안 할 수 있습니까?
yahya

1
@yahya developer.android.com/tools/help/shell.html 문구에 의해 발견, 오후 = 패키지 관리자 "오후 안드로이드"
18,446,744,073,709,551,615


고마워요! 이 링크는 다음과 같이 시작하기위한 정말 멋진 안내서입니다.
yahya

@ V.Kalyuzhnyu 그것은 2015 년에 일하던 시절이었다. IIRC는 삼성 갤럭시, 아마도 S5 일 것이다.
18446744073709551615

2

사용자 정의 함수에 패키지 이름을 매개 변수로 전달하는 경우 아래 코드를 사용하십시오.

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

0

Kotlin, API 14+를 사용하고 있고 앱에 대한 제거 대화 상자를 표시하려는 경우 :

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

packageName사용자에게 장치에서 다른 앱을 제거하라는 메시지를 표시하려는 경우 다른 패키지 이름으로 변경할 수 있습니다


0

전제 조건 :

이전에 올바르게 지적한대로 시스템에서 APK에 서명해야합니다. 이를 달성하는 한 가지 방법은 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);
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.