94

我正在尝试以静默方式将 apk 安装到系统中。我的应用位于 /system/app 并成功授予权限“android.permission.INSTALL_PACKAGES”

但是我在任何地方都找不到如何使用此权限。我试图将文件复制到 /data/app 并没有成功。我也尝试使用此代码

    Intent intent = new Intent(Intent.ACTION_VIEW);
    intent.setDataAndType(
            Uri.parse("file:///sdcard/app.apk"),
            "application/vnd.android.package-archive");
    startActivity(intent);

但是此代码会打开标准安装对话框。如何在没有 root 权限的情况下静默安装应用程序android.permission.INSTALL_PACKAGES

PS我正在编写一个应用程序,它将在第一次启动时将文件夹中的许多apks安装到系统中(替换安装向导)。我需要它来使固件更轻。

如果你认为我在写病毒:所有程序都安装到/data/app。权限 Install_packages 只能授予位于 /system/app 或使用系统密钥签名的系统级程序。所以病毒无法到达那里。

正如http://www.mail-archive.com/android-porting@googlegroups.com/msg06281.html所说,如果应用程序具有 install_packages 权限,则可以静默安装。此外,您不需要 Install_packages 权限来安装包而不是静默安装。加http://www.androidzoom.com/android_applications/tools/silent-installer_wgqi.html

4

17 回答 17

62

您的第一个赌注是查看 Android 的原生PackageInstaller。我建议以您喜欢的方式修改该应用程序,或者仅提取所需的功能。


具体来说,如果您查看PackageInstallerActivity及其方法onClickListener

 public void onClick(View v) {
    if(v == mOk) {
        // Start subactivity to actually install the application
        Intent newIntent = new Intent();
        ...
        newIntent.setClass(this, InstallAppProgress.class);
        ...
        startActivity(newIntent);
        finish();
    } else if(v == mCancel) {
        // Cancel and finish
        finish();
    }
}

然后您会注意到实际的安装程序位于InstallAppProgress类中。检查那个类,你会发现它initView是核心安装程序函数,它做的最后一件事是调用PackageManager'sinstallPackage函数:

public void initView() {
...
pm.installPackage(mPackageURI, observer, installFlags, installerPackageName);
}

下一步是检查PackageManager,它是抽象类。你会在installPackage(...)那里找到功能。坏消息是它标有@hide。这意味着它不是直接可用的(您将无法通过调用此方法进行编译)。

 /**
  * @hide
  * ....
  */
  public abstract void installPackage(Uri packageURI,
             IPackageInstallObserver observer, 
             int flags,String installerPackageName); 

但是您将能够通过反射访问此方法。

如果您对如何实现PackageManager'sinstallPackage功能感兴趣,请查看PackageManagerService

概括

您需要通过Context's获取包管理器对象getPackageManager()。然后你将installPackage通过反射调用函数。

于 2011-04-27T13:57:16.553 回答
15

我最近一直在未经用户同意的情况下实施安装 - 这是一个 API 级别 21+ 的信息亭应用程序,我可以完全控制环境。

基本要求是

  • API 级别 21+
  • root 访问权限以将更新程序安装为系统特权应用程序。

以下方法从 InputStream 读取并安装 APK:

public static boolean installPackage(Context context, InputStream in, String packageName)
            throws IOException {
        PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller();
        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
                PackageInstaller.SessionParams.MODE_FULL_INSTALL);
        params.setAppPackageName(packageName);
        // set params
        int sessionId = packageInstaller.createSession(params);
        PackageInstaller.Session session = packageInstaller.openSession(sessionId);
        OutputStream out = session.openWrite("COSU", 0, -1);
        byte[] buffer = new byte[65536];
        int c;
        while ((c = in.read(buffer)) != -1) {
            out.write(buffer, 0, c);
        }
        session.fsync(out);
        in.close();
        out.close();

        Intent intent = new Intent(context, MainActivity.class);
        intent.putExtra("info", "somedata");  // for extra data if needed..

        Random generator = new Random();

        PendingIntent i = PendingIntent.getActivity(context, generator.nextInt(), intent,PendingIntent.FLAG_UPDATE_CURRENT);
            session.commit(i.getIntentSender());


        return true;
    }

以下代码调用安装

 try {
     InputStream is = getResources().openRawResource(R.raw.someapk_source);
                    installPackage(MainActivity.this, is, "com.example.apk");
     } catch (IOException e) {
                    Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_SHORT).show();
     }

为了使整个事情正常工作,您迫切需要INSTALL_PACKAGES许可,否则上面的代码将默默地失败

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

要获得此权限,您必须将您的 APK 安装为需要 root 的系统应用程序(但是在您安装更新程序应用程序之后,它似乎在没有 root 的情况下工作)

要安装为系统应用程序,我创建了一个签名的 APK 并使用

adb push updater.apk /sdcard/updater.apk

然后将其移至system/priv-app- 这需要重新安装 FS(这就是需要 root 的原因)

adb shell
su
mount -o rw,remount /system
mv /sdcard/updater.apk /system/priv-app
chmod 644 /system/priv-app/updater.apk

由于某种原因,它不适用于简单的调试版本,但如果您的应用程序由于某种原因没有被拾取, logcat会显示有用的信息。priv-app

于 2016-11-05T16:39:44.897 回答
13

我检查了亚行如何安装应用程序。
- 它将 APK 复制到 /data/local/tmp
- 它运行 'shell:pm install /data/local/tmp/app.apk'

我试图通过这样做来复制这种行为:(在 pc 上,使用 USB 电缆)
adb push app.apk /sdcard/app.apk
adb shell
$ pm install /sdcard/app.apk
这行得通。该应用程序已安装。

我制作了一个应用程序(名为 AppInstall),它应该安装另一个应用程序。
(正常安装,非根设备)
它确实:
Runtime.getRuntime().exec("pm install /sdcard/app.apk").waitFor();
但这给出了错误:
java.lang.SecurityException: Neither user 10019 nor current process has android.permission.INSTALL_PACKAGES.
似乎错误是由pm引发的,而不是由AppInstall引发的。
因为 AppInstall 没有捕获 SecurityException 并且应用程序不会崩溃。

我在有根设备(相同的应用程序和 AppInstall)上尝试了同样的事情,它就像一个魅力。
(也正常安装,而不是 /system 或任何东西)
AppInstall 甚至没有询问 root 权限。
但那是因为外壳总是在那个设备上#而不是$在那个设备上。

顺便说一句,您需要 root 才能在 /system 中安装应用程序,对吗?
我在非 root 设备上尝试了 adb remount 并得到:
remount failed: Operation not permitted.
这就是为什么我无法在非 root 设备上尝试 /system 的原因。

结论:您应该使用有根设备
希望这会有所帮助:)

于 2012-05-20T13:51:40.070 回答
8

你应该定义

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

在你的清单中,那么如果你是在系统分区(/system/app)或者你的应用程序由制造商签名,你将拥有 INSTALL_PACKAGES 权限。

我的建议是创建一个兼容级别为 1.5 的小 android 项目,用于通过反射调用 installPackages 并导出一个带有安装包和调用真实方法的方法的 jar。然后,通过在项目中导入 jar,您就可以安装包了。

于 2012-02-13T11:21:35.453 回答
5

我尝试了有根的 Android 4.2.2,这个方法对我有用:

private void installApk(String filename) {
    File file = new File(filename); 
    if(file.exists()){
        try {   
            final String command = "pm install -r " + file.getAbsolutePath();
            Process proc = Runtime.getRuntime().exec(new String[] { "su", "-c", command });
            proc.waitFor();
        } catch (Exception e) {
            e.printStackTrace();
        }
     }
}
于 2014-02-20T09:44:08.850 回答
4

我不知道如何做到这一点,因为当时没有人回答,而且我没有找到有关此许可的文档。所以我找到了自己的解决方案。它比你的更糟糕,但无论如何这是一个解决方案。

我安装了busybox,将777权限设置为/data/app(我不关心安全性)。然后从应用程序执行“busybox install”。这可行,但存在很大的安全漏洞。如果您设置权限 777,则不需要 root。

于 2011-07-19T11:25:26.113 回答
4

android.content.pm.IPackageInstallObserver您可以通过反射使用隐藏的 API :

public class PackageManagement {
    public static final int INSTALL_REPLACE_EXISTING = 0x00000002;
    public static final int INSTALL_SUCCEEDED = 1;

    private static Method installPackageMethod;
    private static Method deletePackageMethod;

    static {
        try {
            installPackageMethod = PackageManager.class.getMethod("installPackage", Uri.class, IPackageInstallObserver.class, Integer.TYPE, String.class);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    public static void installPackage(PackageManager pm, Uri mPackageUri, IPackageInstallObserver observer, int installFlags, String installerPackageName) {
        try {
            installPackageMethod.invoke(pm, mPackageUri, observer, installFlags, installerPackageName);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

导入android.content.pm.IPackageInstallObserver到您的项目中。您的应用程序必须是系统的。您必须在清单文件中激活权限android.permission.INSTALL_PACKAGES

于 2014-11-27T13:21:35.027 回答
3

您可以简单地使用 adb install 命令静默安装/更新 APK。示例代码如下

public static void InstallAPK(String filename){
    File file = new File(filename); 
    if(file.exists()){
        try {   
            String command;
            filename = StringUtil.insertEscape(filename);
            command = "adb install -r " + filename;
            Process proc = Runtime.getRuntime().exec(new String[] { "su", "-c", command });
            proc.waitFor();
        } catch (Exception e) {
        e.printStackTrace();
        }
     }
  }
于 2013-08-29T10:37:14.473 回答
2

我检查了所有答案,结论似乎是您必须首先拥有设备的 root 访问权限才能使其工作。

但后来我发现这些文章非常有用。因为我正在制造“公司所有”的设备。

如何在没有用户交互的情况下静默更新 Android 应用程序

Android 设备所有者 - 最小应用程序

这是谷歌关于“托管设备”的文档

完全托管的设备

于 2019-03-15T06:40:11.363 回答
1

先决条件:

如前所述,您的 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);
}

===> 在 Android 8.1 上测试并且运行良好。

于 2018-07-26T17:06:51.367 回答
0

我使用 PackageManager.installPackage 方法为静默安装制作了一个测试应用程序。

我通过反射得到 installPackage 方法,并在我的 src 文件夹中制作了 android.content.pm.IPackageInstallObserver 接口(因为它隐藏在 android.content.pm 包中)。

当我运行 installPackage 时,我得到了带有字符串指示的 SecurityException,我的应用程序没有 android.permission.INSTALL_PACKAGES,但它在 AndroidManifest.xml 中定义。

所以,我认为,不可能使用这种方法。

PS。我在 Android SDK 2.3 和 4.0 上进行了测试。也许它适用于早期版本。

于 2013-02-13T18:48:58.527 回答
0

LD_LIBRARY_PATH=/vendor/lib:/system/lib在 pm install 之前试试这个。它运作良好。

于 2014-02-04T05:10:59.417 回答
0

3rd 方应用程序无法静默安装 Android 应用程序。但是,第 3 方应用程序可以要求 Android 操作系统安装应用程序。

所以你应该定义这个:

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file:///sdcard/app.apk", "application/vnd.android.package-archive");
startActivity(intent);

您也可以尝试将其安装为系统应用程序以授予权限并忽略此定义。(需要根)

您可以在您的 3rd 方应用程序上运行以下命令,以在有根设备上安装应用程序。

代码是:

private void installApk(String filename) {
File file = new File(filename); 
if(file.exists()){
    try {   
        final String command = "pm install -r " + file.getAbsolutePath();
        Process proc = Runtime.getRuntime().exec(new String[] { "su", "-c", command });
        proc.waitFor();
    } catch (Exception e) {
        e.printStackTrace();
    }
 }
}

我希望这个答案对你有帮助。

于 2018-02-16T17:55:40.730 回答
0

可以在 Android 6 及更高版本上进行静默安装。使用 Boris Treukhov 在答案中提供的功能,忽略帖子中的所有其他内容,也不需要 root。

以设备管理员身份安装您的应用程序,您可以拥有完整的信息亭模式,并在后台静默安装更新。

于 2019-01-27T17:13:30.693 回答
0

您可以在终端或外壳中使用它

adb shell install -g MyApp.apk

在开发谷歌中查看更多信息

于 2020-09-20T07:41:26.267 回答
0

正如@inazaruk在答案中提到的,installPackage隐藏的方法,你需要通过反射来调用它。但是,IPackageInstallObserver回调也是隐藏的,它installPackage作为参数传递给,所以你需要使用动态代理来实现这个接口。您可以在下面找到同时使用反射和代理的代码片段:

private void silentAppInstall(Uri apkUri){
        PackageManager pm = getContext().getPackageManager();
        try {
            Class<?> cPackageInstallObserver = Class.forName("android.content.pm.IPackageInstallObserver");
            Object installObserver = Proxy.newProxyInstance(cPackageInstallObserver.getClassLoader(),
                    new Class[]{cPackageInstallObserver}, new InstallObserverHandler());
            Class<?>[] types = new Class[] {Uri.class, cPackageInstallObserver, int.class, String.class};
            Method method = pm.getClass().getMethod("installPackage", types);
            method.invoke(pm, apkUri, installObserver, 2, null);
        } catch (NoSuchMethodException | ClassNotFoundException | IllegalAccessException | InvocationTargetException e) {
            e.printStackTrace();
        }
}

static class InstallObserverHandler implements InvocationHandler {

        @Override
        public Object invoke(Object o, Method method, Object[] args) throws Throwable {
            if (method.getName().equals("packageInstalled")){
                // Place you code here
            }
            return null;
        }
}
于 2021-07-07T13:13:01.677 回答
-6

!/bin/bash

f=/home/cox/myapp.apk   #or $1 if input from terminal.

#backup env var
backup=$LD_LIBRARY_PATH
LD_LIBRARY_PATH=/vendor/lib:/system/lib
myTemp=/sdcard/temp.apk
adb push $f $myTemp
adb shell pm install -r $myTemp
#restore env var
LD_LIBRARY_PATH=$backup

这对我有用。我在 ubuntu 12.04 的 shell 终端上运行它。

于 2014-06-06T05:53:12.570 回答