本文介绍如何使用安卓的隐藏API,实现应用的安装与删除功能。
整体结构如下所示,PackageManager为接口文件,ApplicationPackageManager为PackageManager的实现类,而实际完成应用管理的是PackageManagerService。
服务端为:
客户端为:
在该框架结构中,涉及到了客户端与服务器的进程间通信,在安卓源码中是使用AIDL解决进程间通信的问题。
文件名 | 位置 |
---|---|
PackageManager.java | frameworks\base\core\java\android\content\pm |
ApplicationPackageManager.java | frameworks\base\core\java\android\app |
IPackageManager.aidl | frameworks\base\core\java\android\content\pm |
PackageManagerService.java | frameworks\base\services\java\com\android\server\pm |
PackageManager对象是通过Context的getPackageManager方法获得的,查看ContextImpl.java(位于frameworks\base\core\java\android\app下)的getPackageManager方法,可以发现返回的为ApplicationPackageManager对象,同时还带入了IPackageManager对象作为参数。
在ApplicationPackageManager中,我们可以看到其实真正的操作都是通过mPM对象完成的。
IPackageManager的对象是通过ActivityThread(安卓主线程,位于frameworks\base\core\java\android\app下)的getPackageManager方法获得的,查看该方法。其中,通过ServiceManager.getService(“package”)获得PackageManagerService,通过调用IPackageManager.Stub.asInterface(b)得到IPackageManager的代理类Proxy。
PackageManager中一共包含了38种安装返回值,以及4种卸载返回值。
属性名 | 码值 | 说明 |
---|---|---|
INSTALL_SUCCEEDED | 1 | 安装成功 |
INSTALL_FAILED_ALREADY_EXISTS | -1 | 应用已经存在 |
INSTALL_FAILED_INVALID_APK | -2 | 安装包无效 |
INSTALL_FAILED_INVALID_URI | -3 | URI无效 |
INSTALL_FAILED_INSUFFICIENT_STORAGE | -4 | 存储空间不足 |
INSTALL_FAILED_DUPLICATE_PACKAGE | -5 | 存在同名文件 |
INSTALL_FAILED_NO_SHARED_USER | -6 | shared用户不存在 |
INSTALL_FAILED_UPDATE_INCOMPATIBLE | -7 | 与之前已经安装的同名应用签名不同 |
INSTALL_FAILED_SHARED_USER_INCOMPATIBLE | -8 | 与之前已经安装的同名应用shared用户不同 |
INSTALL_FAILED_MISSING_SHARED_LIBRARY | -9 | 使用了无效的共享库 |
INSTALL_FAILED_REPLACE_COULDNT_DELETE | -10 | 无法删除之前的安装 |
INSTALL_FAILED_DEXOPT | -11 | 优化和验证dex文件时失败,原因可能是存储空间不足或验证失败。 |
INSTALL_FAILED_OLDER_SDK | -12 | SDK版本过低 |
INSTALL_FAILED_CONFLICTING_PROVIDER | -13 | 包含在该系统中已经存在的content provider |
INSTALL_FAILED_NEWER_SDK | -14 | SDK版本过新 |
INSTALL_FAILED_TEST_ONLY | -15 | 只用于测试 |
INSTALL_FAILED_CPU_ABI_INCOMPATIBLE | -16 | 应用包含了不与CPU ABI兼容的native代码 |
INSTALL_FAILED_MISSING_FEATURE | -17 | 应用中使用了不可用功能 |
INSTALL_FAILED_CONTAINER_ERROR | -18 | 无法接入外部媒体受保护内容 |
INSTALL_FAILED_INVALID_INSTALL_LOCATION | -19 | 无法在指定的位置上安装该应用 |
INSTALL_FAILED_MEDIA_UNAVAILABLE | -20 | Media无效 |
INSTALL_FAILED_VERIFICATION_TIMEOUT | -21 | 安装超时 |
INSTALL_FAILED_VERIFICATION_FAILURE | -22 | 验证失败 |
INSTALL_FAILED_PACKAGE_CHANGED | -23 | 安装包名与应用中规定的不同 |
INSTALL_FAILED_UID_CHANGED | -24 | 与之前已经安装的同名应用UID不同 |
INSTALL_FAILED_VERSION_DOWNGRADE | -25 | 应用版本低于之前已经安装的同名应用 |
INSTALL_PARSE_FAILED_NOT_APK | -100 | 非.apk结尾 |
INSTALL_PARSE_FAILED_BAD_MANIFEST | -101 | 缺少AndroidManifest.xml文件 |
INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION | -102 | 解析器发生非预期错误 |
INSTALL_PARSE_FAILED_NO_CERTIFICATES | -103 | 解析器为发现相关证书 |
INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES | -104 | 解析器发现不一致证书 |
INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING | -105 | 解析器在解码某个文件是发生CertificateEncodingException |
INSTALL_PARSE_FAILED_BAD_PACKAGE_NAME | -106 | 解析器解释到无效错误包名 |
INSTALL_PARSE_FAILED_BAD_SHARED_USER_ID | -107 | 解析器遇到错误的shared用户ID。 |
INSTALL_PARSE_FAILED_MANIFEST_MALFORMED | -108 | 解析器遇到结构上的问题 |
INSTALL_PARSE_FAILED_MANIFEST_EMPTY | -109 | 解析器未发现任何actionable(instrumentation or application)标签 |
INSTALL_FAILED_INTERNAL_ERROR | -110 | 因为系统问题无法安装 |
INSTALL_FAILED_USER_RESTRICTED | -111 | 无法安装因为用户被限制安装 |
属性名 | 码值 | 说明 |
---|---|---|
DELETE_SUCCEEDED | 1 | 卸载成功 |
DELETE_FAILED_INTERNAL_ERROR | -1 | 卸载失败,原因不明 |
DELETE_FAILED_DEVICE_POLICY_MANAGER | -2 | 卸载失败,DevicePolicy manager的实现 |
DELETE_FAILED_USER_RESTRICTED | -3 | 卸载失败,用户受限 |
在机器已经root过的情况下,我们可以利用root用户的权限,使用底层Linux命令进行应用的安装与卸载操作。
public String do_exec(String cmd) {
try {
Process exeEcho = Runtime.getRuntime().exec("su");
DataOutputStream os = new DataOutputStream(exeEcho.getOutputStream());
os.writeBytes(cmd);
os.flush();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return cmd;
}
注:这种方法实现应用的安装与卸载应用必须确保机器已经被root过。
通过删除掉PackageManager中被@hide注释的方法并编译新的jar包进行使用。
通过反射ApplicationPackageManager对象的安装和卸载方式实现。注意:不能反射PackageManager.java的方法,否则会出现空指针错误,因为PackageManager.java只是接口文件。
代码如下:
public static void installSilentWithReflection(Context context, String filePath, String packageName) {
try {
PackageManager packageManager = context.getPackageManager();
Method method = packageManager.getClass().getDeclaredMethod("installPackage",
new Class[] {Uri.class, IPackageInstallObserver.class, int.class, String.class} );
method.setAccessible(true);
File apkFile = new File(filePath);
Uri apkUri = Uri.fromFile(apkFile);
method.invoke(packageManager, new Object[] {apkUri, new IPackageInstallObserver.Stub() {
@Override
public void packageInstalled(String pkgName, int resultCode) throws RemoteException {
Log.d(TAG, "packageInstalled = " + pkgName + "; resultCode = " + resultCode) ;
}
}, Integer.valueOf(2), packageName});
//PackageManager.INSTALL_REPLACE_EXISTING = 2;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void deleteSilentWithReflection(Context context, String packageName) {
try {
PackageManager packageManager = context.getPackageManager();
Method method = packageManager.getClass().getDeclaredMethod("deletePackage",
new Class[] {String.class, IPackageDeleteObserver.class, int.class} );
method.setAccessible(true);
int DELETE_ALL_USERS = 0x00000002;
method.invoke(packageManager, new Object[] {packageName, new IPackageDeleteObserver.Stub() {
@Override
public void packageDeleted(String packageName, int returnCode) throws RemoteException {
Log.d(TAG, "packageDelete = " + packageName) ;
}
}, DELETE_ALL_USERS });
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
注:通过反射实现应用的安装与卸载的方式实现应用安装与卸载比较简单,但是如果需要调用多个方法则需要反射多次,这种条件下使用反射会变得异常繁琐。
通过AIDL的方式,实现进程间的通信,得到IPackageManager的代理类Proxy进行操作。
首先,要保证项目中包含以下AIDL文件,这些文件都可以在frameworks\base\core\java\android\content\pm中直接复制,其中IPackageDeleteObserver.aidl、IPackageInstallObserver.aidl和IPackageManager.aidl是实现安装与卸载不可或缺的,其余文件可以除去:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ivNLYWnG-1574062319922)(https://kcms.konkawise.com/upload/201909251152008190.png)]
代码如下:
/**
* 表示安装过程中是否锁定
*/
public static final int INSTALL_FORWARD_LOCK = 0x00000001;
/**
* 表示是否替换安装包
*/
public static final int INSTALL_REPLACE_EXISTING = 0x00000002;
/**
* 是否是测试安装包
*/
public static final int INSTALL_ALLOW_TEST = 0x00000004;
/**
* 指定本次安装必须在SDCard中
*/
public static final int INSTALL_EXTERNAL = 0x00000008;
/**
* 指定本次安装必须在内部存储空间中
*/
public static final int INSTALL_INTERNAL = 0x00000010;
/**
* 指定本次安装由ADB起始
*/
public static final int INSTALL_FROM_ADB = 0x00000020;
/**
* 安装对于所有用户立即可见
*/
public static final int INSTALL_ALL_USERS = 0x00000040;
/**
* 允许安装比当前版本低的安装包
*/
public static final int INSTALL_ALLOW_DOWNGRADE = 0x00000080;
/**
* 不删除用户数据
*/
public static final int DELETE_KEEP_DATA = 0x00000001;
/**
* 不保存用户数据
*/
public static final int DELETE_ALL_USERS = 0x00000002;
/**
* 可卸载系统应用
*/
public static final int DELETE_SYSTEM_APP = 0x00000004;
private static MyPackageManager instance;
private static IPackageManager mIPackageManager = null;
private MyPackageManager() {
Class<?> forName = null;
try {
forName = Class.forName("android.os.ServiceManager");
Method method = forName.getMethod("getService", String.class);
IBinder iBinder = (IBinder) method.invoke(null, "package");
mIPackageManager = IPackageManager.Stub.asInterface(iBinder);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
public static MyPackageManager getInstance() {
if (instance == null) {
instance = new MyPackageManager();
}
return instance;
}
public void installPackage(Uri uri, IPackageInstallObserver iPackageInstallObserver, int flag, String installerPackageName) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, RemoteException {
mIPackageManager.installPackage(uri, iPackageInstallObserver, flag, installerPackageName);
}
public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, RemoteException {
mIPackageManager.deletePackageAsUser(packageName, observer, getUserId(), flags);
}
public ApplicationInfo getApplicationInfo(String packageName) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, RemoteException {
return mIPackageManager.getApplicationInfo(packageName, 0, getUserId());
}
private int getUserId() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class UserHandle = Class.forName("android.os.UserHandle");
Method method = UserHandle.getDeclaredMethod("myUserId", null);
return method.invoke(null, null) == null ? -1 : (int) method.invoke(null, null);
}
代码中,因为需要用到android.os.ServiceManager的getService方法来取得PackageManagerService,所以还需要通过反射来调用getService。
forName = Class.forName("android.os.ServiceManager");
Method method = forName.getMethod("getService", String.class);
IBinder iBinder = (IBinder) method.invoke(null, "package");
mIPackageManager = IPackageManager.Stub.asInterface(iBinder);
注:通过AIDL的方式实现应用的安装与卸载比较复杂,但是成功取得IPackageManager的代理类后可以直接调用多个方法。
ProcessBuilder类是J2SE 1.5在java.lang中新添加的一个新类,此类用于创建操作系统进程,它提供一种启动和管理进程的方法。通过ProcessBuilder,可以利用Linux pm实现应用的安装与卸载。
代码如下:
public static int deleteSilent(String packageName) {
if (packageName == null || packageName.length() == 0 || packageName.trim().equals("")) {
return 1;
}
String[] args = {"pm", "uninstall", packageName};
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
int result;
try {
process = processBuilder.start();
successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s;
while ((s = successResult.readLine()) != null) {
successMsg.append(s);
}
while ((s = errorResult.readLine()) != null) {
errorMsg.append(s);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
// TODO should add memory is not enough here
if (successMsg.toString().contains("Success") || successMsg.toString().contains("success")) {
result = 0;
} else {
result = 2;
}
Log.d("test-test", "successMsg:" + successMsg + ", ErrorMsg:" + errorMsg);
return result;
}
public static int installSilent(String filePath) {
File file = new File(filePath);
if (filePath == null || filePath.length() == 0 || file == null || file.length() <= 0 || !file.exists() || !file.isFile()) {
return 1;
}
String[] args = {"pm", "install", "-r", filePath};
ProcessBuilder processBuilder = new ProcessBuilder(args);
Process process = null;
BufferedReader successResult = null;
BufferedReader errorResult = null;
StringBuilder successMsg = new StringBuilder();
StringBuilder errorMsg = new StringBuilder();
int result;
try {
process = processBuilder.start();
successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
String s;
while ((s = successResult.readLine()) != null) {
successMsg.append(s);
}
while ((s = errorResult.readLine()) != null) {
errorMsg.append(s);
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (successResult != null) {
successResult.close();
}
if (errorResult != null) {
errorResult.close();
}
} catch (IOException e) {
e.printStackTrace();
}
if (process != null) {
process.destroy();
}
}
// TODO should add memory is not enough here
if (successMsg.toString().contains("Success") || successMsg.toString().contains("success")) {
result = 0;
} else {
result = 2;
}
Log.d("test-test", "successMsg:" + successMsg + ", ErrorMsg:" + errorMsg);
return result;
}
注:通过ProcessBuilder不仅仅可以使用pm操作,还可以使用am的操作,功能非常的强大。然而问题还是需要给每一个功能写特定的实现。