ติดตั้ง / ถอนการติดตั้ง APKs ทางโปรแกรม (PackageManager vs Intents)


142

แอปพลิเคชันของฉันติดตั้งแอปพลิเคชันอื่นและจำเป็นต้องติดตามแอปพลิเคชันที่ติดตั้งไว้ แน่นอนว่าสามารถทำได้โดยเพียงแค่เก็บรายการแอปพลิเคชั่นที่ติดตั้งไว้ แต่สิ่งนี้ไม่จำเป็น! ควรเป็นความรับผิดชอบของ PackageManager ในการรักษาความสัมพันธ์ที่ติดตั้งโดย (a, b) ในความเป็นจริงตาม 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 Market พวกเขาใช้ PackageManager เวอร์ชันที่สมบูรณ์ยิ่งขึ้น สิ่งนี้สามารถเห็นได้โดยการดาวน์โหลดซอร์สโค้ด Android จากที่เก็บ Android Git ด้านล่างนี้เป็นวิธีการซ่อนสองวิธีที่สอดคล้องกับวิธีการแสดงเจตจำนง น่าเสียดายที่พวกเขาไม่พร้อมใช้งานสำหรับนักพัฒนาภายนอก แต่บางทีพวกเขาจะเป็นในอนาคต?

แนวทางที่ดีกว่า

การติดตั้ง APK โดยใช้ PackageManager

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

ถอนการติดตั้ง APK โดยใช้ PackageManager

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

ความแตกต่าง

  • เมื่อใช้ intents ตัวจัดการแพ็กเกจในพื้นที่จะไม่รับรู้ถึงแอปพลิเคชันที่ติดตั้งมา getInstallerPackageName (... ) จะส่งกลับค่า null โดยเฉพาะ

  • เมธอดที่ซ่อนอยู่ installPackage (... ) ใช้ชื่อแพ็กเกจตัวติดตั้งเป็นพารามิเตอร์และน่าจะสามารถตั้งค่านี้ได้

คำถาม

เป็นไปได้หรือไม่ที่จะระบุชื่อตัวติดตั้งแพ็คเกจโดยใช้ intents (อาจจะสามารถเพิ่มชื่อของแพ็คเกจการติดตั้งเป็นสิ่งเพิ่มเติมสำหรับเจตนาการติดตั้งได้หรือไม่)

เคล็ดลับ: หากคุณต้องการดาวน์โหลดซอร์สโค้ด Android คุณสามารถทำตามขั้นตอนที่อธิบายไว้ที่นี่: การดาวน์โหลดทรีซอร์ส เพื่อดึงไฟล์ * .java และใส่ไว้ในโฟลเดอร์ตามลำดับชั้นของแพคเกจที่คุณสามารถตรวจสอบสคริปต์เรียบร้อยนี้: ดู Android รหัสที่มาใน Eclipse


URIs บางส่วนหายไปในข้อความ ฉันจะเพิ่มพวกเขาทันทีที่ฉันอนุญาต (ผู้ใช้ใหม่มีข้อ จำกัด บางประการเพื่อป้องกันสแปม)
Håvard Geithus

1
วิธีปิดการใช้งานฟังก์ชั่นถอนการติดตั้ง?

2
@ user938893: "ปิดการใช้งานฟังก์ชั่นถอนการติดตั้งอย่างไร" - ทำงานกับมัลแวร์ที่ยากต่อการถอนการติดตั้งใช่ไหม
Daniel

คำตอบ:


66

ขณะนี้ไม่สามารถใช้ได้กับแอปพลิเคชันบุคคลที่สาม โปรดทราบว่าแม้การใช้การสะท้อนกลับหรือลูกเล่นอื่น ๆ ในการเข้าถึง installPackage () จะไม่ช่วยเพราะแอปพลิเคชันระบบเท่านั้นที่สามารถใช้งานได้ (นี่เป็นเพราะมันเป็นกลไกการติดตั้งระดับต่ำหลังจากได้รับการอนุมัติจากผู้ใช้ดังนั้นจึงไม่ปลอดภัยสำหรับแอปพลิเคชันทั่วไปที่จะเข้าถึงได้)

อาร์กิวเมนต์ของฟังก์ชั่น installPackage () มักจะมีการเปลี่ยนแปลงระหว่างการเปิดตัวแพลตฟอร์มดังนั้นสิ่งใดก็ตามที่คุณพยายามเข้าถึงมันจะล้มเหลวบนแพลตฟอร์มรุ่นอื่น ๆ

แก้ไข:

นอกจากนี้ยังมีค่าที่ชี้ให้เห็นว่า installerPackage นี้เพิ่งถูกเพิ่มเข้ามาในแพลตฟอร์ม (2.2?) และไม่ได้ใช้สำหรับการติดตามว่าใครติดตั้งแอพ - มันถูกใช้โดยแพลตฟอร์มเพื่อกำหนดว่าใครจะเปิดตัวเมื่อรายงานข้อบกพร่องด้วย แอปพลิเคชันสำหรับการใช้งานฟีดแบ็กของ Android (นี่เป็นหนึ่งในหลาย ๆ ครั้งที่การเปลี่ยนแปลงเมธอดข้อโต้แย้งของ API) เป็นเวลาอย่างน้อยหลังจากที่มีการเปิดตัว Market ยังไม่ได้ใช้เพื่อติดตามแอพที่ติดตั้ง (และอาจใช้ไม่ได้ ) แต่เพียงใช้สิ่งนี้เพื่อตั้งค่าแอพ Android Feedback (ซึ่งแยกจาก Market) เป็น "เจ้าของ" เพื่อดูแลความคิดเห็น


"โปรดทราบว่าแม้การใช้การสะท้อนกลับหรือลูกเล่นอื่น ๆ ในการเข้าถึง installPackage () จะไม่ช่วยเพราะแอปพลิเคชันระบบเท่านั้นที่สามารถใช้งานได้" สมมติว่าฉันกำลังสร้างแพ็กเกจติดตั้ง / ลบ / จัดการแอพสำหรับแพลตฟอร์มที่กำหนดนอกเหนือจาก Android ดั้งเดิม ฉันจะเข้าถึงการติดตั้ง / ลบได้อย่างไร
dascandy

startActivity () พร้อมด้วยเจตนาที่เกิดขึ้นอย่างเหมาะสม (ผมแน่ใจว่านี้ได้รับการตอบรับที่อื่น ๆ ใน StackOverflow ดังนั้นฉันจะไม่พยายามที่จะให้คำตอบที่แน่นอนที่นี่ที่มีความเสี่ยงในการได้รับบางสิ่งบางอย่างที่ไม่ถูกต้อง.)
hackbod

mmmkay ที่แสดงกล่องโต้ตอบติดตั้ง / ลบ Android มาตรฐาน รายละเอียดเหล่านั้นได้รับการจัดการแล้ว - ฉันกำลังมองหาฟังก์ชั่น "เพียง **** ติดตั้งแพคเกจนี้" และ "เพียงแค่ **** ลบแพคเกจนี้" ฟังก์ชั่นอย่างแท้จริงไม่มีคำถามที่ถาม
dascandy

2
อย่างที่ฉันได้กล่าวไปแล้วว่าแอปพลิเคชันบุคคลที่สามเหล่านี้ไม่สามารถใช้ได้ หากคุณสร้างอิมเมจระบบของคุณเองคุณจะต้องมีการติดตั้งแพลตฟอร์มและคุณสามารถค้นหาฟังก์ชั่นได้ที่นั่น แต่มันไม่ได้เป็นส่วนหนึ่งของ API ที่มีให้สำหรับแอปของบุคคลที่สามทั่วไป
hackbod

ฉันกำลังสร้างไฟล์ apk explorer พร้อมการติดตั้งลบและสำรองฟังก์ชันการทำงานดังนั้น Google อนุญาตให้ฉันยังคงเผยแพร่แอปพลิเคชันของฉันบน Google Play ได้หรือไม่ และนโยบายใดที่เราจะทำลาย?
ราหุลมันดาลิยา

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

ACTION_INSTALL_PACKAGE เกี่ยวกับอะไร เราสามารถดาวน์โหลดและติดตั้งเวอร์ชั่นล่าสุดแอพของเราจาก play store ได้ไหม?
MAS John

3
ตั้งแต่ Android P ลบปพลิเคชันต้องได้รับอนุญาตอย่างชัดแจ้ง "android.permission.REQUEST_DELETE_PACKAGES" ไม่ว่าถ้าคุณใช้ "ACTION_DELETE" หรือ "ACTION_UNINSTALL_PACKAGE" developer.android.com/reference/android/content/...
Darklord5

ขอบคุณที่กล่าวถึงสิทธิ์ Android P ฉันติดขัดและไม่แน่ใจว่าเกิดอะไรขึ้นก่อนหน้านี้
Avi Parshan

43

ระดับ API 14 แนะนำสองการกระทำใหม่: ACTION_INSTALL_PACKAGEและACTION_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;
}

ฉันรู้ว่ามันเป็นไปได้ที่จะทำเช่นนี้เป็นอุปกรณ์เจ้าของ ขอบคุณสำหรับคำตอบ!
ลุค Cauthen

@Sandeep มันเป็นเพียงแค่อ่านเนื้อหาของ APK ลงในสตรีมเอาท์พุท
Ohad Cohen

@LukeCauthen คุณลองเป็นเจ้าของอุปกรณ์แล้วหรือยัง มันทำงานหรือไม่
NetStarter

@NetStarter ใช่ฉันมี มันเป็นเพียงความเจ็บปวดในการได้รับแอปเป็นเจ้าของอุปกรณ์ เมื่อคุณทำเช่นนั้นคุณจะได้รับพลังมากมายที่ปกติต้องใช้รูท
ลุค Cauthen

1
โปรดทราบว่าคุณต้องเพิ่ม android.permission.DELETE_PACKAGES ของคุณเพื่อให้ถอนการติดตั้งเพื่อใช้งาน (ทดสอบบน Api ระดับ 22 หรือต่ำกว่า)
benchuk

4

วิธีเดียวในการเข้าถึงวิธีการเหล่านั้นคือการไตร่ตรอง คุณสามารถจัดการกับPackageManagerวัตถุโดยการโทรgetApplicationContext().getPackageManager()และใช้การเข้าถึงการสะท้อนวิธีการเหล่านี้ ชำระเงินนี้กวดวิชา


มันใช้งานได้ดีกับ 2.2 แต่ฉันไม่เคยโชคดีมาใช้กับ 2.3
บางคนที่ไหนสักแห่ง

3
การสะท้อนนั้นไม่คงที่ตลอดทั้ง api ทุกรุ่น
HandlerExploit

3

ตามซอร์สโค้ด Froyo คีย์พิเศษ Intent.EXTRA_INSTALLER_PACKAGE_NAME จะถูกสอบถามสำหรับชื่อแพ็คเกจตัวติดตั้งใน PackageInstallerActivity


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พบโดยวลี "pm android", pm = ตัวจัดการแพคเกจ
18446744073709551615


ขอบคุณมาก! ลิงก์เหล่านี้เป็นคำแนะนำที่ยอดเยี่ยมจริงๆที่จะเริ่มต้นด้วย :)
yahya

@ V.Kalyuzhnyu มันเคยทำงานกลับมาในปี 2558 IIRC เป็น Samsung Galaxy บางที 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.