cài đặt / gỡ cài đặt APK theo chương trình (Gói Trình quản lý so với Ý định)


141

Ứng dụng của tôi cài đặt các ứng dụng khác và nó cần theo dõi những ứng dụng nào đã cài đặt. Tất nhiên, điều này có thể đạt được bằng cách chỉ cần giữ một danh sách các ứng dụng đã cài đặt. Nhưng điều này không cần thiết! Trách nhiệm của Gói quản lý là duy trì mối quan hệ đã cài đặtBy (a, b). Trên thực tế, theo API đó là:

chuỗi trừu tượng công khai getInstallerPackageName (String packName) - Lấy tên gói của ứng dụng đã cài đặt một gói. Điều này xác định thị trường mà gói đến từ đâu.

Cách tiếp cận hiện tại

Cài đặt APK bằng Intent

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

Gỡ cài đặt APK bằng Intent:

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

Đây rõ ràng không phải là cách ví dụ như cài đặt / gỡ cài đặt Android Market. Họ sử dụng một phiên bản phong phú hơn của Gói Trình quản lý. Điều này có thể được nhìn thấy bằng cách tải xuống mã nguồn Android từ kho lưu trữ Android Git. Dưới đây là hai phương thức ẩn tương ứng với phương pháp Ý định. Thật không may, chúng không có sẵn cho các nhà phát triển bên ngoài. Nhưng có lẽ họ sẽ ở trong tương lai?

Cách tiếp cận tốt hơn

Cài đặt APK bằng Trình quản lý gói

/**
 * @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);

Gỡ cài đặt APK bằng Trình quản lý gói

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

Sự khác biệt

  • Khi sử dụng ý định, trình quản lý gói cục bộ không được biết cài đặt bắt nguồn từ ứng dụng nào. Cụ thể, getInstallerPackageName (...) trả về null.

  • Phương thức ẩn installPackage (...) lấy tên gói trình cài đặt làm tham số và rất có thể có khả năng đặt giá trị này.

Câu hỏi

Có thể chỉ định tên trình cài đặt gói bằng cách sử dụng ý định? (Có lẽ tên của gói trình cài đặt có thể được thêm vào như một phần bổ sung cho mục đích cài đặt?)

Mẹo: Nếu bạn muốn tải xuống mã nguồn Android, bạn có thể làm theo các bước được mô tả tại đây: Tải xuống Cây nguồn. Để trích xuất các tệp * .java và đặt chúng vào các thư mục theo phân cấp gói, bạn có thể xem tập lệnh gọn gàng này: Xem Mã nguồn Android trong Eclipse .


Một số URI bị thiếu trong văn bản. Tôi sẽ thêm chúng ngay khi tôi được phép (người dùng mới có một số hạn chế để ngăn chặn thư rác).
Håvard Geithus

1
Làm thế nào để vô hiệu hóa chức năng gỡ cài đặt?

2
@ user938893: "làm thế nào để tắt chức năng gỡ cài đặt?" - Làm việc trên một số phần mềm độc hại khó gỡ cài đặt, phải không?
Daniel

Câu trả lời:


66

Điều này hiện không có sẵn cho các ứng dụng của bên thứ ba. Lưu ý rằng ngay cả việc sử dụng sự phản chiếu hoặc các thủ thuật khác để truy cập installPackage () sẽ không giúp ích gì, vì chỉ các ứng dụng hệ thống mới có thể sử dụng nó. (Điều này là do đây là cơ chế cài đặt cấp thấp, sau khi các quyền đã được người dùng chấp thuận, do đó không an toàn cho các ứng dụng thông thường có quyền truy cập.)

Ngoài ra, các đối số hàm installPackage () thường thay đổi giữa các bản phát hành nền tảng, do đó, bất cứ điều gì bạn thử truy cập đều sẽ thất bại trên các phiên bản khác nhau của nền tảng.

BIÊN TẬP:

Ngoài ra, điều đáng chú ý là trình cài đặt này Gói chỉ mới được thêm vào gần đây vào nền tảng (2.2?) Và ban đầu không thực sự được sử dụng để theo dõi ai đã cài đặt ứng dụng - nền tảng được sử dụng để xác định ai sẽ khởi chạy khi báo cáo lỗi với ứng dụng để thực hiện Phản hồi Android. (Đây cũng là một trong những lần các đối số phương thức API thay đổi.) Trong ít nhất một thời gian sau khi được giới thiệu, Market vẫn không sử dụng nó để theo dõi các ứng dụng đã cài đặt (và rất có thể nó vẫn không sử dụng nó ), nhưng thay vào đó chỉ sử dụng điều này để đặt ứng dụng Phản hồi Android (tách biệt với Thị trường) làm "chủ sở hữu" để chăm sóc phản hồi.


"Lưu ý rằng ngay cả việc sử dụng sự phản chiếu hoặc các thủ thuật khác để truy cập installPackage () sẽ không giúp ích gì, vì chỉ các ứng dụng hệ thống mới có thể sử dụng nó." Giả sử tôi đang tạo một gói cài đặt / gỡ bỏ / quản lý ứng dụng cho một nền tảng nhất định, ngoại trừ chính Android gốc. Làm thế nào tôi nên truy cập cài đặt / gỡ bỏ?
dascandy

startActivity () với Ý định được hình thành phù hợp. (Tôi chắc chắn này đã được trả lời ở đâu đó trên StackOverflow, vì vậy tôi sẽ không cố gắng để cung cấp cho câu trả lời chính xác ở đây có nguy cơ nhận được một cái gì đó sai.)
hackbod

mmmkay, điều đó mang đến các hộp thoại cài đặt / gỡ bỏ Android tiêu chuẩn. Các chi tiết đó đã được xử lý - Tôi đang tìm kiếm các chức năng "chỉ cần **** cài đặt gói này" và "chỉ **** loại bỏ gói này", theo nghĩa đen không có câu hỏi nào được hỏi.
dascandy

2
Như tôi đã nói, những thứ này không có sẵn cho các ứng dụng của bên thứ ba. Nếu bạn đang tạo hình ảnh hệ thống của riêng mình, bạn có triển khai nền tảng và bạn có thể tìm thấy các chức năng ở đó, nhưng chúng không phải là một phần của API có sẵn cho các ứng dụng bên thứ ba thông thường.
hackbod

Tôi đang tạo apk apk explorer với chức năng cài đặt, gỡ bỏ và sao lưu, vậy google có cho phép tôi tiếp tục xuất bản ứng dụng của mình trên google play không? và chính sách nào chúng ta sẽ phá vỡ?
Rahul Mandaliya

84

Android P + yêu cầu quyền này trong AndroidManifest.xml

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

Sau đó:

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

để gỡ cài đặt. Có vẻ dễ dàng hơn ...


Đây có thể là ứng dụng chạy mã? như thế nào trong onDestroy()phương pháp?
Mahdi-Malv

làm thế nào về ACTION_INSTALL_PACKAGE? chúng tôi có thể tải xuống và cài đặt phiên bản mới nhất ứng dụng của chúng tôi từ cửa hàng play không?
M .. Giăng

3
Kể từ khi Android P xóa các ứng dụng đòi hỏi manifest phép "android.permission.REQUEST_DELETE_PACKAGES" không có vấn đề nếu bạn sử dụng "ACTION_DELETE" hay "ACTION_UNINSTALL_PACKAGE" developer.android.com/reference/android/content/...
Darklord5

Cảm ơn bạn đã đề cập đến quyền Android P, tôi đã bị mắc kẹt và không chắc chắn những gì đang xảy ra trước đó.
Avi Parshan

43

Mức API 14 giới thiệu hai hành động mới: ACTION_INSTALL_PACKAGEACTION_UNINSTALL_PACKAGE . Những hành động đó cho phép bạn vượt qua EXTRA_RETURN_RESULT boolean thêm để nhận thông báo kết quả cài đặt (un).

Mã ví dụ để gọi hộp thoại gỡ cài đặt:

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

Và nhận thông báo trong phương thức Activity # onActivityResult của bạn :

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

Làm thế nào tôi có thể xác nhận từ hộp thoại hành động này rằng một trong hai người dùng đã nhấn ok hoặc hủy để tôi có thể đưa ra quyết định dựa trên điều này
Erum

2
@Erum Tôi đã thêm một ví dụ cho những gì bạn đã hỏi
Alex Lipov

Khi cài đặt, nút hủy không nhận được kết quả quay lại phương thức
onActivityResult

2
Bắt đầu với API 25, việc gọi ACTION_INSTALL_PACKAGEsẽ yêu cầu cấp REQUEST_INSTALL_PACKAGESphép chữ ký . Tương tự, bắt đầu với API 28 (Android P), gọi điện ACTION_UNINSTALL_PACKAGEsẽ yêu cầu quyền không nguy hiểm REQUEST_DELETE_PACKAGES. Ít nhất là theo các tài liệu.
Steve Blackwell

22

Nếu bạn có quyền của Chủ sở hữu thiết bị (hoặc chủ sở hữu cấu hình, tôi chưa thử), bạn có thể âm thầm cài đặt / gỡ cài đặt các gói bằng API của chủ sở hữu thiết bị.

để gỡ cài đặt:

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

và để cài đặt gói:

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

Tôi biết rằng nó phải có thể làm điều này là thiết bị chủ sở hữu. Cảm ơn câu trả lời!
Luke Cauthen

@sandeep nó chỉ đọc nội dung của APK vào luồng đầu ra
Ohad Cohen

@LukeCauthen bạn đã thử làm chủ sở hữu thiết bị chưa? nó đã làm việc?
NetStarter

@NetStarter Có tôi có. Nó chỉ là một nỗi đau khi nhận được một ứng dụng là chủ sở hữu thiết bị. Một khi bạn làm điều đó, bạn sẽ có được rất nhiều sức mạnh thường sẽ yêu cầu root.
Luke Cauthen

1
Vui lòng lưu ý rằng bạn phải thêm ERIC.DELETE_PACKAGES vào bảng kê khai của bạn để gỡ cài đặt hoạt động (được thử nghiệm ở cấp độ Api 22 trở xuống)
Benchuk

4

Cách duy nhất để truy cập các phương thức đó là thông qua sự phản ánh. Bạn có thể xử lý một PackageManagerđối tượng bằng cách gọi getApplicationContext().getPackageManager()và sử dụng phản xạ truy cập các phương thức này. Kiểm tra hướng dẫn này .


Điều này hoạt động rất tốt với 2.2, nhưng tôi đã không gặp may mắn khi sử dụng nó với 2.3
Ai đó ở đâu đó

3
Sự phản chiếu không ổn định trong tất cả các phiên bản api
HandlerExploit

3

Theo mã nguồn Froyo, khóa bổ sung Intent.EXTRA_INSTALLER_PACKAGE_NAME được truy vấn cho tên gói trình cài đặt trong GóiInstallerActivity.


1
Bằng cách nhìn vào cam kết này, tôi nghĩ rằng nó nên hoạt động
sergio91pt

2

Trên thiết bị đã root, bạn có thể sử dụng:

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() được định nghĩa ở đây.


Có cách nào để cài đặt một ứng dụng được tải xuống trước trong sdcard không? Hoặc bạn có thể gợi ý cho tôi một số trang để kiểm tra những lệnh nào chúng ta có thể sử dụng trên shell tại Nền tảng Android không?
yahya

1
@yahya developer.android.com/tools/help/shell.html được tìm thấy bởi cụm từ "pm android", pm = trình quản lý gói
18446744073709551615


Cảm ơn rất nhiều! Các liên kết này là những hướng dẫn thực sự tuyệt vời để bắt đầu với :)
yahya

@ V.Kalyuzhnyu Nó từng hoạt động trở lại vào năm 2015. IIRC nó là một chiếc Samsung Galaxy, có thể là S5.
18446744073709551615

2

Nếu bạn chuyển tên gói làm tham số cho bất kỳ chức năng nào do người dùng xác định thì hãy sử dụng mã dưới đây:

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

0

Nếu bạn đang sử dụng Kotlin, API 14+ và chỉ muốn hiển thị hộp thoại gỡ cài đặt cho ứng dụng của bạn:

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

Bạn có thể thay đổi packageNamethành bất kỳ tên gói nào khác nếu bạn muốn nhắc người dùng gỡ cài đặt ứng dụng khác trên thiết bị


0

Điều kiện tiên quyết:

APK của bạn cần được ký bởi hệ thống như được chỉ ra chính xác trước đó. Một cách để đạt được điều đó là tự xây dựng hình ảnh AOSP và thêm mã nguồn vào bản dựng.

Mã số:

Sau khi cài đặt dưới dạng ứng dụng hệ thống, bạn có thể sử dụng các phương thức quản lý gói để cài đặt và gỡ cài đặt APK như sau:

Tải về:

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

Gỡ cài đặt:

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

Để có một cuộc gọi lại sau khi APK của bạn được cài đặt / gỡ cài đặt, bạn có thể sử dụng điều này:

/**
 * 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);
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.