以编程方式静默安装APK

时间:2018-07-30 08:51:02

标签: java android android-install-apk

对不起,我英语不好。我想弄清楚如何以编程方式无需root即可静默安装(或删除)APK文件。

首先,我添加了android:sharedUserId="android.uid.system"作为清单和权限

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission
        android:name="android.permission.INSTALL_PACKAGES"
        tools:ignore="ProtectedPermissions" />
    <uses-permission android:name="android.permission.DELETE_PACKAGES" tools:ignore="ProtectedPermissions"/>

安装和删除代码

    public void installApp(File file){
        try {
            final String command = "pm install " + file.getPath();
            Process proc = Runtime.getRuntime().exec(new String[] {command });
            proc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public void deleteApp(String appPackage){
        try {
            final String command = "pm uninstall " + appPackage;
            Process proc = Runtime.getRuntime().exec(new String[] {command });
            proc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

据我所知,我需要制造商密钥来签署我的应用程序。我没有找到适用于Android Studio模拟器的密钥,因此例如我从http://www.android-x86.org/releases/releasenote-4-4-r2下载了Android 4.4 r2的映像(并将其安装在Oracle VM中),并从https://sourceforge.net/p/android-x86/build/ci/android-x86-4.4-r2/tree/target/product/security/获得了密钥。理解platform.x509.pemplatform.pk8是我需要的键。

我用signapk.jar这样的java -jar signapk.jar platform.x509.pem platform.pk8 app.apk signapp.apk签名了我的应用。

但是它不起作用。某些尝试以错误结束:

卸载

07-30 04:42:46.050 1477-1477/com.jinga.jihome W/System.err: java.io.IOException: Error running exec(). Command: [pm uninstall ru.bogdanov.mom] Working Directory: null Environment: null
        at java.lang.ProcessManager.exec(ProcessManager.java:211)
07-30 04:42:46.060 1477-1477/com.jinga.jihome W/System.err:     at java.lang.Runtime.exec(Runtime.java:173)
        at java.lang.Runtime.exec(Runtime.java:128)
        at com.jinga.jihome.updater.packageInstaller.PackageInstallerHelper.deleteApp(PackageInstallerHelper.java:59)
        at com.jinga.jihome.MainActivity.onCreate(MainActivity.java:102)
        at android.app.Activity.performCreate(Activity.java:5231)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2159)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2245)
        at android.app.ActivityThread.access$800(ActivityThread.java:135)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1196)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5017)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
07-30 04:42:46.070 1477-1477/com.jinga.jihome W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
        at dalvik.system.NativeStart.main(Native Method)
    Caused by: java.io.IOException: Permission denied
        at java.lang.ProcessManager.exec(Native Method)
        at java.lang.ProcessManager.exec(ProcessManager.java:209)

安装

07-30 04:42:49.420 1477-1477/com.jinga.jihome W/System.err: java.io.IOException: Error running exec(). Command: [pm install /storage/sdcard/app-debug.apk] Working Directory: null Environment: null
        at java.lang.ProcessManager.exec(ProcessManager.java:211)
        at java.lang.Runtime.exec(Runtime.java:173)
07-30 04:42:49.430 1477-1477/com.jinga.jihome W/System.err:     at java.lang.Runtime.exec(Runtime.java:128)
        at com.jinga.jihome.updater.packageInstaller.PackageInstallerHelper.installApp(PackageInstallerHelper.java:49)
        at com.jinga.jihome.MainActivity$2.onSuccess(MainActivity.java:135)
        at com.jinga.jihome.MainActivity$2.onSuccess(MainActivity.java:126)
        at io.reactivex.internal.operators.single.SingleObserveOn$ObserveOnSingleObserver.run(SingleObserveOn.java:81)
        at io.reactivex.android.schedulers.HandlerScheduler$ScheduledRunnable.run(HandlerScheduler.java:109)
        at android.os.Handler.handleCallback(Handler.java:733)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5017)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
        at dalvik.system.NativeStart.main(Native Method)
    Caused by: java.io.IOException: No such file or directory
        at java.lang.ProcessManager.exec(Native Method)
        at java.lang.ProcessManager.exec(ProcessManager.java:209)

某些已签名的apk不想像App conflicts with existing package by the same name这样的错误安装,或者我的设备与此apk不兼容,但没有全部签名就可以。

我尝试使用不同的图像,密钥对和仿真器,但没有成功,我做错了什么?

2 个答案:

答案 0 :(得分:1)

在应用清单中定义的权限与Shell命令无关。他们保护Java API。

DELETE_PACKAGES权限保护PackageManager#deletePackage方法。它不是公共SDK的一部分,因此您必须使用反射来访问它(或针对unhidden android.jar编译应用)。

反射路径

您需要将此接口复制到您的项目中

package android.content.pm;

interface IPackageDeleteObserver {
    void packageDeleted(String packageName, int returnCode);
}

然后使用反射调用deletePackage方法。这是Kotlin中的示例:

private val deletePackageMethod = PackageManager::class.java.getDeclaredMethod(
    "deletePackage",
    String::class.java,
    IPackageDeleteObserver::class.java,
    Int::class.javaPrimitiveType
)

@RequiresPermission(Manifest.permission.DELETE_PACKAGES)
fun PackageManager.deletePackage(
    packageName: String,
    observer: IPackageDeleteObserver,
    flags: Int
) {
    deletePackageMethod.invoke(this, packageName, observer, flags)
}

您可以在PackageManager source code中找到标志并以前缀DELETE_的常量返回代码。

到目前为止,所有这些都已经过测试和验证。

注意:反射是一项额外的工作,因此我们只执行一次方法查找。该方法是隐藏的,但是公共的,因此您无需调用setAccessible(true)

警告::请在Android 9上对其进行测试,但我不能保证此功能可以禁止访问隐藏的API。我认为,由于您的应用是使用系统签名签名的,并且system用户身份运行,因此应该没问题。

取消隐藏路径

如果您针对修改后的android.jar进行编译,则可以直接引用上述类型。

我遇到的唯一问题是gradle任务mockableAndroidJar与修改后的android.jar不兼容。您必须通过在IDE的gradle命令行中添加-x mockableAndroidJar来将该任务排除在执行之外。

我实际上还没有尝试过,我无法告诉您IDE是否允许您继续使用它。

答案 1 :(得分:0)

我也有一个可以在客户端设备上安装和更新apk的应用。

它也使用平台签名密钥进行签名,这意味着我们有权在其播放器上安装它!

在Android 8.x上,我必须设置

  

android:sharedUserId =“ android.uid.shell”

中的

使其起作用。我正在使用命令:

File localApk = new File("apkName.apk"); String[] Commands = {"pm", "install", "-r", localApk.getAbsolutePath()}; Runtime.getRuntime().exec(Commands);

在未设置shareUserId的情况下,我总是遇到权限错误。