Android-静默安装与卸载实现

简述

本文介绍如何使用安卓的隐藏API,实现应用的安装与删除功能。

PackageManager的框架

PackageManager框架介绍

整体结构如下所示,PackageManager为接口文件,ApplicationPackageManager为PackageManager的实现类,而实际完成应用管理的是PackageManagerService。

服务端为:

在这里插入图片描述

客户端为:

在这里插入图片描述

在该框架结构中,涉及到了客户端与服务器的进程间通信,在安卓源码中是使用AIDL解决进程间通信的问题。

文件所在位置

文件名位置
PackageManager.javaframeworks\base\core\java\android\content\pm
ApplicationPackageManager.javaframeworks\base\core\java\android\app
IPackageManager.aidlframeworks\base\core\java\android\content\pm
PackageManagerService.javaframeworks\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。

在这里插入图片描述

APK安装与卸载的返回值

PackageManager中一共包含了38种安装返回值,以及4种卸载返回值。

安装返回值

属性名码值说明
INSTALL_SUCCEEDED1安装成功
INSTALL_FAILED_ALREADY_EXISTS-1应用已经存在
INSTALL_FAILED_INVALID_APK-2安装包无效
INSTALL_FAILED_INVALID_URI-3URI无效
INSTALL_FAILED_INSUFFICIENT_STORAGE-4存储空间不足
INSTALL_FAILED_DUPLICATE_PACKAGE-5存在同名文件
INSTALL_FAILED_NO_SHARED_USER-6shared用户不存在
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-12SDK版本过低
INSTALL_FAILED_CONFLICTING_PROVIDER-13包含在该系统中已经存在的content provider
INSTALL_FAILED_NEWER_SDK-14SDK版本过新
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-20Media无效
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_SUCCEEDED1卸载成功
DELETE_FAILED_INTERNAL_ERROR-1卸载失败,原因不明
DELETE_FAILED_DEVICE_POLICY_MANAGER-2卸载失败,DevicePolicy manager的实现
DELETE_FAILED_USER_RESTRICTED-3卸载失败,用户受限

静默安装与卸载的实现

安装流程

在这里插入图片描述

在这里插入图片描述

Root情况下

通过执行底层Linux命令

在机器已经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过。

非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

通过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

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的操作,功能非常的强大。然而问题还是需要给每一个功能写特定的实现。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值