Android-静默安装与卸载实现

简述

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

PackageManager的框架

PackageManager框架介绍

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

Android-静默安装与卸载实现_第1张图片

服务端为:

Android-静默安装与卸载实现_第2张图片

客户端为:

Android-静默安装与卸载实现_第3张图片

在该框架结构中,涉及到了客户端与服务器的进程间通信,在安卓源码中是使用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对象作为参数。

Android-静默安装与卸载实现_第4张图片

在ApplicationPackageManager中,我们可以看到其实真正的操作都是通过mPM对象完成的。
在这里插入图片描述
Android-静默安装与卸载实现_第5张图片

IPackageManager的对象是通过ActivityThread(安卓主线程,位于frameworks\base\core\java\android\app下)的getPackageManager方法获得的,查看该方法。其中,通过ServiceManager.getService(“package”)获得PackageManagerService,通过调用IPackageManager.Stub.asInterface(b)得到IPackageManager的代理类Proxy。

Android-静默安装与卸载实现_第6张图片

APK安装与卸载的返回值

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 卸载失败,用户受限

静默安装与卸载的实现

安装流程

Android-静默安装与卸载实现_第7张图片

Android-静默安装与卸载实现_第8张图片

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

你可能感兴趣的:(Android)