13

我想在 Android 中实现一个静默安装程序,来自 apk 文件和 unistaller 包。该主题已在 SO 和其他地方进行了广泛讨论,但由于某种原因,我无法申请任何我错过的内容。这个范围显然很难实现,因为如果成功,这将是 Android 的严重安全漏洞。但是,我需要为一个特殊项目实施它,而不是为消费市场。有两种方法:

  1. 通过调整 PackageManager 安装程序(实际上只是删除用户接受对话框),从源代码(例如 AOSP 或 Cyanogen mod)生成自定义 ROM。
  2. 通过以超级用户身份创建进程并执行“adb shell pm install”以编程方式执行此操作。我之前在 /system/xbin 中安装了“su”,并在运行时测试了 RootTools.rootIsAvailable()。

对于第一种情况,我深入研究了 Froyo 源代码,但使用 @hide 标记的方法陷入了死胡同。第二个我首先尝试了来自终端的命令

adb shell pm install /mnt/sdcard/HelloAndroid.apk

adb shell pm uninstall com.example.helloandroid

两者都工作正常。然后,我使用了以下代码,在有根的模拟器(2.2 - Froyo)上测试了开发:

@Override
    public void onClick(View v) {
        switch (v.getId())
           {
              case R.id.btnInstall:
                  try {  
                      install = Runtime.getRuntime().exec("su\n");   
                      DataOutputStream os = new DataOutputStream(install.getOutputStream());
                      os.writeBytes("pm install /mnt/sdcard/HelloAndroid.apk\n"); 
                      os.writeBytes("exit\n"); 
                      os.flush();
                      install.waitFor();

                              if (install.exitValue() == 0) {  
                                  Toast.makeText(MainActivity.this, "Success!", Toast.LENGTH_LONG).show();
                              }  
                              else {  
                                  Toast.makeText(MainActivity.this, "Failure. Exit code: "+String.valueOf(install.exitValue()), Toast.LENGTH_LONG).show();
                              }
                  }
                  catch (InterruptedException e) {  
                      logError(e);
                  }
                  catch (IOException e) {  
                  logError(e);
                  } 
                  break;

              case R.id.btnUninstall:
                  try {
                      install = Runtime.getRuntime().exec("su\n"); 
                      install=Runtime.getRuntime().exec("pm uninstall "+txtPackageName.getText().toString()+"\n");

                } catch (Exception e) {
                    logError(e);
                }
                  break;
           }

    }

为了避免拼写错误和其他修饰,我硬编码了安装命令的 apk 文件参数;在 'case R.id.btnInstall' 上,该命令未执行,退出为“失败”,退出值为 1,表示“找不到该类”;不知道这意味着什么……感谢您的帮助!

已编辑:我有干净的解决方案,只要我有时间和正确形式的代码,我就会从 AZ 发布答案!

4

6 回答 6

8

正如我在这里所承诺的那样,这是解决此问题的方法,除了必须将整个应用程序安装在 /system/app 目录中之外,无需对系统进行任何强制。我跟着,然后对这里的优秀文章做了一些修复:http: //paulonaka.wordpress.com/2011/07/02/how-to-install-a-application-in-background-on-android/。我已经下载了文章中引用的 zip 文件,(我尽量保持相同的类名):

  1. 创建了一个新项目和一个主要活动作为入口点

package com.example.silentinstuninst;

import java.io.File;
import java.lang.reflect.InvocationTargetException;

import com.example.instuninsthelper.ApplicationManager;
import com.example.instuninsthelper.OnDeletedPackage;
import com.example.instuninsthelper.OnInstalledPackage;

import android.os.Bundle;
import android.os.Environment;
import android.app.Activity;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class MainActivity extends Activity implements OnClickListener {

    Process install;
    Button btnInstall, btnUninstall;
    EditText txtApkFileName, txtPackageName; 

    public static final String TAG = "SilentInstall/Uninstall";

    private static ApplicationManager am;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initializeValues();

    }

    private void initializeValues() {

        btnInstall = (Button) findViewById(R.id.btnInstall);
        btnUninstall = (Button) findViewById(R.id.btnUninstall);
        txtApkFileName = (EditText) findViewById(R.id.txtApkFilePath);
        txtPackageName = (EditText) findViewById(R.id.txtPackageName);

        btnInstall.setOnClickListener(this);
        btnUninstall.setOnClickListener(this);

        try {
            am = new ApplicationManager(this);
            am.setOnInstalledPackage(new OnInstalledPackage() {

                public void packageInstalled(String packageName, int returnCode) {
                    if (returnCode == ApplicationManager.INSTALL_SUCCEEDED) {
                        Log.d(TAG, "Install succeeded");
                    } else {
                        Log.d(TAG, "Install failed: " + returnCode);
                    }
                }
            });

            am.setOnDeletedPackage(new OnDeletedPackage() {
                public void packageDeleted(boolean succeeded) {
                    Log.d(TAG, "Uninstall succeeded");  
                }
            });

        } catch (Exception e) {
            logError(e);
        }
    }

    private void logError(Exception e) {
        e.printStackTrace();
        Toast.makeText(this, R.string.error+e.getMessage(), Toast.LENGTH_LONG).show();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId())
           {
              case R.id.btnInstall:
                  // InstallUninstall.Install(txtApkFileName.getText().toString());
            try {
                am.installPackage(Environment.getExternalStorageDirectory() +
                        File.separator + txtApkFileName.getText().toString());
            } catch (IllegalArgumentException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            } catch (IllegalAccessException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            } catch (InvocationTargetException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
            } // install package
                  break;

              case R.id.btnUninstall:
                  // InstallUninstall.Uninstall(txtPackageName.getText().toString());
            try {
                am.uninstallPackage(txtPackageName.getText().toString());
            } catch (IllegalArgumentException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                logError(e);
            } catch (IllegalAccessException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                logError(e);
            } catch (InvocationTargetException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                logError(e);
            }
                  break;
           }    
    }
}
  1. 在 /src 中创建包com.example.instuninsthelper。我在那里添加了 ApplicationManager.java 和 OnInstalledPackage.java 文件
  2. 在 ApplicationManager 类中插入以下代码:

private OnDeletedPackage onDeletedPackage;
class PackageDeleteObserver extends IPackageDeleteObserver.Stub { 

        public void packageDeleted(boolean succeeded) throws RemoteException {
            if (onDeletedPackage != null) {
                onDeletedPackage.packageDeleted(succeeded);
            }
        }

    }
  1. 创建,在相同的com.example.instuninsthelper下使用以下代码打包文件 OnDeletedPackage.java:

package com.example.instuninsthelper;
public interface OnDeletedPackage {
    public void packageDeleted(boolean succeeded);
}
  1. 在 android.content.pm 包中(不应更改命名空间)我修改了 IPackageDeleteObserver.java,结果如下:

package android.content.pm;

public interface IPackageDeleteObserver extends android.os.IInterface {

    public abstract static class Stub extends android.os.Binder implements android.content.pm.IPackageDeleteObserver {
        public Stub() {
            throw new RuntimeException("Stub!");
        }

        public static android.content.pm.IPackageDeleteObserver asInterface(android.os.IBinder obj) {
            throw new RuntimeException("Stub!");
        }

        public android.os.IBinder asBinder() {
            throw new RuntimeException("Stub!");
        }

        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)
                throws android.os.RemoteException {
            throw new RuntimeException("Stub!");
        }
    }

    public abstract void packageDeleted(boolean succeeded)
            throws android.os.RemoteException;
}
  1. 在 Eclipse 中构建应用程序并将其部署到模拟器
  2. 在模拟器中:主页按钮>设置>应用程序> ...卸载应用程序(因为它没有安装在/system/app中,我们只需要生成apk文件)
  3. 执行以下操作来启动模拟器(以便我们可以在 /system/app 中写入;我使用的其他解决方案是生成一个自定义 ROM,此应用程序包含在 /system/app 中):
  4. 从控制台,进入项目的 /bin 目录,然后输入: * adb push .apk /system/app
  5. 最后,总是从控制台输入: * adb shell am start -n com.example.silentinstuninst/com.example.silentinstuninst.MainActivity
  6. 请享用!
于 2013-02-19T10:19:38.357 回答
2

不知道,只是一个想法:

我认为您正在编写标准,而不是执行命令或通过其输入向进程提供额外数据。我认为应该是:

Runtime.getRuntime().exec("pm install /mnt/sdcard/HelloAndroid.apk\n"); 

希望这可以帮助。

于 2013-02-14T09:56:13.327 回答
1

/system/app目录中安装本质上与需要 root 相同。

假设您有 root,请查看RootTools。然后你可以这样做:

if (RootTools.isAccessGiven()) {
    CommandCapture command = new CommandCapture(0, "pm install " + PATH_TO_APK);
    RootTools.getShell(true).add(command).waitForFinish();
}

请注意,这waitForFinish()是一个阻塞调用!

于 2013-07-25T05:38:51.687 回答
0

好吧,您也可以直接使用 PackageManager 执行此操作(需要 root 访问权限):

  • 使用具有公开接口的 platform-sdk 创建一个应用程序(创建或下载它,并配置 eclipse)
  • 在应用程序中直接调用允许静默安装/删除的隐藏 API 函数。
  • 通过将 APK 复制到 /system/app(需要 root),将 APK 作为系统应用安装在您的设备上

看到这个: http: //forum.xda-developers.com/showthread.php?t=1711653

于 2013-02-14T09:34:51.373 回答
0

Runtime.getRuntime().exec("pm install /mnt/sdcard/HelloAndroid.apk\n");

这对我有用,尽管还需要做两个额外的细节:

  1. 在 AndroidManifest.xml 中添加 android:sharedUserId="android.uid.system" 。

  2. 使用系统密钥对 apk 进行签名。

    但是这种方式似乎无法判断是否安装成功,所以我稍后会尝试@Ginger的方法。

于 2013-11-07T23:34:11.090 回答
0

对于所有仍然有问题的人:您将需要一个有根设备并使用

Process result = Runtime.getRuntime().exec("pm install -r -d MyApp.apk /system/app")

如果您收到结果代码 9(错误代码 9),您需要从设备中删除您的 apk 并将其推回(推送而不是安装!)。

转到设备外壳并推送 apk

launcher=MyApp.apk
$adb shell su -c "mount -o remount,rw -t rfs /dev/stl5 /system"
$adb push $launcher /sdcard/$launcher
$adb shell su -c "chmod 644 /system/app/$launcher"

现在您可以使用 pm install 而不会出现错误。希望它会帮助某人。

于 2015-01-19T11:49:50.533 回答