【问题标题】:Install apps silently, with granted INSTALL_PACKAGES permission静默安装应用程序,并授予 INSTALL_PACKAGES 权限
【发布时间】:2011-08-13 19:46:43
【问题描述】:

我正在尝试以静默方式将 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);

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

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

如果你认为我在写病毒:所有程序都安装到/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

【问题讨论】:

  • @Fosco 假设他正在制造这样的设备作为原始设备制造商?
  • @Fosco 谁在乎?这是一个像 dascandy 描述的有效问题。当人们用“你不应该那样做”来回答这样的问题时,通常是没有效率的。有了这样的答案,我们获得了什么理解?
  • 说到效率低下.. 你参加这个派对 Zach 晚了 2 年多一点,而且这个问题看起来与原来的大不相同。
  • @Fosco 哈哈。此外,我可以确认这仍然适用于最新的 android 操作系统。
  • @LarsH 我刚刚安装了 F-Droid 应用程序:他们不会静默安装东西,他们只是提示 Android 的包管理器。

标签: android installation silent


【解决方案1】:

您的第一个选择是查看 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是核心安装函数,它做的最后一件事是调用PackageManagerinstallPackage函数:

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

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

如果你对PackageManagerinstallPackage函数是如何实现感兴趣的,可以看看PackageManagerService

总结

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

【讨论】:

  • 帮助!此技术导致: 原因:java.lang.SecurityException:用户 10101 和当前进程都没有 android.permission.INSTALL_PACKAGES。即使我的应用拥有该权限
  • 您的应用请求该权限,但 Android 不会将该权限授予第 3 方应用。有关权限相关的日志,请参阅LogCat
  • @inazaruk 我读到安装在 /system/app 中的应用程序将自动获得使用 install_pakages 的权限,但正如this post 中所述,它似乎不起作用。你能给我一些提示吗?
  • 如何通过反射调用 installPackage?实际上我做不到 Method method = aPackageManagerClass.getMethod("installPackage", new Class[]{Uri.class, IPackageInstallObserver.class, Integer.TYPE, String.class});鉴于 IPackageInstallObserver 也是隐藏的,有人可以帮我,在此处发布正确的代码进行反射。
  • 答案中的所有链接都已失效。
【解决方案2】:

我最近在未经用户同意的情况下实施安装 - 这是一个 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

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

【讨论】:

  • “由于某种原因,它不适用于简单的调试版本”你的意思是它在发布版或其他一些不太“简单”的调试版本中工作?
  • @JMLord 它适用于已签名的发布版本,但不适用于已签名和未签名的 debug 版本 - 我觉得奇怪的是它不适用于已签名的调试版本我,但我没有时间调查。不过系统 logcat 日志对于判断 APK 是否安装非常重要。
  • 确实,出于某种原因,作为发行版安装是获得 INSTALL_PACKAGES 权限的关键。该应用程序可以在 priv-app 的签名调试中运行,但权限请求被拒绝。非常感谢!
  • @BorisTreukhov 嘿,我可以和你核实一下 packageName 参数的用途吗?我将 InputStream 更改为 FileInputStream 但我收到此错误消息:无法解析 /data/app/vmdl676191029.tmp/COSU
  • 这些命令后如何安装系统私有应用?
【解决方案3】:

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

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

我制作了一个应用程序(名为 AppInstall),它应该安装另一个应用程序。
(正常安装,非root设备)
确实如此:
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 中安装应用程序,对吗?
我在非根设备上尝试了 adb remount 并得到:
remount failed: Operation not permitted.
这就是为什么我无法在非 root 设备上尝试 /system 的原因。

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

【讨论】:

  • 我似乎无法让它工作。有趣的是,该命令在 ADB shell 中运行良好,然后当我尝试以编程方式运行它时似乎不起作用。
  • 是的,我有同样的问题 - 在 shell 中工作,但从我的应用程序调用时不能,即使我通过 su 调用它)
  • 有没有其他方法可以在谷歌设备上工作,比如获得超级管理员权限?
  • 如果您想要超级管理员权限,您应该:1) 根您的设备,2) 在系统分区上安装应用程序,3) 或者应用程序应该使用与操作系统相同的密钥进行签名本身。
  • 这是因为 ADB 用户和您的应用程序的用户具有不同的权限
【解决方案4】:

你应该定义

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

在您的清单中,无论您是在系统分区 (/system/app) 还是您的应用程序已由制造商签名,您都将拥有 INSTALL_PACKAGES 权限。

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

【讨论】:

    【解决方案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();
            }
         }
    }
    

    【讨论】:

    • 问题是关于在没有 root 的情况下安装应用程序,但具有系统“INSTALL_PACKAGES”权限。我在你的回答中看到“su”,所以它需要 root。
    • 非常感谢您的回答!我有相同的代码,但我使用的是“adb install -r”而不是“pm install -r”,它对我不起作用。你在这里为我节省了很多时间!
    • @Vladmir 我需要你的帮助。你能给我一些时间吗?我会非常感谢你
    【解决方案6】:

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

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

    【讨论】:

    • 你用过这样的代码吗? Process install; install = Runtime.getRuntime().exec("/system/bin/busybox install " + myAPK.getAbsolutePath()); int iSuccess = install.waitFor();
    【解决方案7】:

    你可以通过反射使用隐藏的APIandroid.content.pm.IPackageInstallObserver

    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

    【讨论】:

    • 你在 Lollipop 和 Marshamallow 上测试过这段代码吗?对我来说似乎是一种很棒的方法,我想使用它
    • 我已经从 ICS 测试到 Lollipop
    • 要查看的行是“您的应用必须是系统的”。我相当确定这将适用于棒棒糖或其他任何东西 - 授予您的应用程序是“系统”,也就是说,您的应用程序具有系统权限(基本上是 root)。不过,在这种情况下,您也可以使用命令行安装程序。
    • 我如何调用 installPackage 方法?意思是,例如,我如何获得 IPackageInstallObserver?
    • installFlags的值是多少?
    【解决方案8】:

    您可以简单地使用 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();
            }
         }
      }
    

    【讨论】:

    • 这里的StringUtils是什么意思?是否面临无法解决的错误?
    • 对不起,我应该解释一下。这是我的字符串自定义类,此方法可确保路径正确。你可以忽略这个
    • 什么是 StringUtil 类。你能发布 StringUtil.insertEscape() 方法吗?因为如果我评论那一行我得到异常错误运行exec()。命令:[su, -c, adb install -r /storage/sdcard0/Android/data/com.install.file 提前致谢
    • java.io.IOException:运行 exec() 时出错。命令:[su, -c, adb install -r /storage/emulated/0/download/SwipeAnimation.apk] 工作目录:null 环境:null -- 你能解释一下错误吗?以及清单中是否有任何权限
    • @ZeeShaNAbbAs 生根问题解决后.. 谢谢
    【解决方案9】:

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

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

    How to Update Android App Silently Without User Interaction

    Android Device Owner - Minimal App

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

    Fully managed device

    【讨论】:

      【解决方案10】:

      先决条件:

      如前所述,您的 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 上测试并且运行良好。

      【讨论】:

      • 我需要你的帮助。你能给我一些时间吗?我会非常感谢你
      • 我需要你的帮助。我需要知道我们如何创建自己的 AOSP
      【解决方案11】:

      我使用 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 上进行了测试。也许它适用于早期版本。

      【讨论】:

      • 它适用于 4.1 和 4.0 以及任何高于 2.1 的 android 版本(包括 2.1)。我现在正在使用 4.0 SDK。您需要将其添加到 /system/app 文件夹,否则您的应用程序将不会具有所需的系统级(不是 root)权限。我也可以使用这种方法静默删除应用程序,请查看我的其他与删除相关的问题。
      • 是的,它可以工作,但只有 root 的权限。并且无法在非 root 设备上将应用安装到系统/应用中。
      • 不是root,而是系统级权限。如果您是固件制造商,则可以将其添加到 /system/app。就我而言 - 我是(我要求设备制造商将我的 apk 添加到固件中)。您将无法以任何其他方式静默安装应用程序 - 否则将是一个巨大的安全漏洞。
      • 关于安全漏洞——我完全同意你的看法。如果这种方法适用于任何应用程序,这对我来说将是一个惊喜。如何在不生根的情况下将应用程序安装到 /system/app 中?我不是制造商。
      • 没有root或刷新你不能。
      【解决方案12】:

      在安装之前试试这个LD_LIBRARY_PATH=/vendor/lib:/system/lib。效果很好。

      【讨论】:

        【解决方案13】:

        第 3 方应用程序无法静默安装 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();
            }
         }
        }
        

        希望这个回答对你有帮助。

        【讨论】:

          【解决方案14】:

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

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

          【讨论】:

            【解决方案15】:

            你可以在终端或shell中使用它

            adb shell install -g MyApp.apk
            

            develope google中查看更多信息

            【讨论】:

              【解决方案16】:

              正如@inazaruk 在答案中提到的,installPackage 方法处于隐藏状态,您需要通过反射调用它。但是,IPackageInstallObserver 回调也是隐藏的,它作为参数传递给installPackage,因此您需要使用动态代理才能实现此接口。您可以在下面找到一个代码 sn-p ysing 反射和代理:

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

              【讨论】:

                【解决方案17】:

                !/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 终端上运行它。

                【讨论】:

                  猜你喜欢
                  • 2017-01-31
                  • 2012-04-29
                  • 1970-01-01
                  • 2023-04-08
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-09-09
                  相关资源
                  最近更新 更多