在做医院项目的时候,遇到了医院病人床头屏app需要静默安装的需求。静默安装就是被安装的app在升级或者安装的时候不需要用户手动点击确认安装,让app自动在后台安装或者升级成功。
要想实现app静默安装我们需要下面4个步骤:
1.首先我们需要在配置清单文件里面添加INSTALL_PACKAGE权限
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
2.把该应用的uid
设置为系统级别的,在manifest标签下添加以下属性
android:sharedUserId="android.uid.system"
3.仅仅设置uid,还是没法实现静默安装,因为系统并不认为你这个app是系统级别的应用,所以,还应该对该应用的APK进行系统签名
(注意:不是那个静默安装的APK,是这个实现静默安装程序的APK)。签名过程如下:
总共需要三个文件:
- SignApk.jar %系统源码%/out/host/linux-x86/framework/signapk.jar
- platform.x509.pem %系统源码%/build/target/product/security/platform.x509.pem
- platform.pk8 %系统源码%/build/target/product/security/platform.pk8
由于我们的安卓设备是第三方定制的安卓系统,我们只能去找安卓设备供应商要系统签名需要的这三个文件。
利用签名工具signapk.jar修改应用程序签名:命令为:
java -jar signapk.jar platform.x509.pem platform.pk8 my.apk mysigned.apk
my.apk是我们的应用程序的路径,mysigned.apk是应用系统签名之后生成的新的apk的路径
4.静默安装代码
/**
* 静默安装
* 会依次调用Stream-->反射-->Shell
*
* @param apkFile APK文件
* @return 成功或失败
*/
@SuppressLint("PackageManagerGetSignatures")
@RequiresPermission(Manifest.permission.INSTALL_PACKAGES)
public static synchronized boolean install(Context context, String apkFile) throws InterruptedException {
File file;
if (TextUtils.isEmpty(apkFile) || !(file = new File(apkFile)).exists())
return false;
context = context.getApplicationContext();
//加上apk合法性判断
AppUtils.AppInfo apkInfo = AppUtils.getApkInfo(file);
if (apkInfo == null || TextUtils.isEmpty(apkInfo.getPackageName())) {
LogUtils.iTag(TAG, "apk info is null, the file maybe damaged: " + file.getAbsolutePath());
return false;
}
//加上本地apk版本判断
AppUtils.AppInfo appInfo = AppUtils.getAppInfo(apkInfo.getPackageName());
if (appInfo != null) {
//已安装的版本比apk版本要高, 则不需要安装
if (appInfo.getVersionCode() >= apkInfo.getVersionCode()) {
LogUtils.iTag(TAG, "The latest version has been installed locally: " + file.getAbsolutePath(),
"app info: packageName: " + appInfo.getPackageName() + "; app name: " + appInfo.getName(),
"apk version code: " + apkInfo.getVersionCode(),
"app version code: " + appInfo.getVersionCode());
return true;
}
//已安装的版本比apk要低, 则需要进一步校验签名和ShellUID
PackageManager pm = context.getPackageManager();
try {
PackageInfo appPackageInfo, apkPackageInfo;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
appPackageInfo = pm.getPackageInfo(appInfo.getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
apkPackageInfo = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNING_CERTIFICATES);
} else {
appPackageInfo = pm.getPackageInfo(appInfo.getPackageName(), PackageManager.GET_SIGNATURES);
apkPackageInfo = pm.getPackageArchiveInfo(file.getAbsolutePath(), PackageManager.GET_SIGNATURES);
}
if (appPackageInfo != null && apkPackageInfo != null &&
!compareSharedUserId(appPackageInfo.sharedUserId, apkPackageInfo.sharedUserId)) {
LogUtils.wTag(TAG, "Apk sharedUserId is not match",
"app shellUid: " + appPackageInfo.sharedUserId,
"apk shellUid: " + apkPackageInfo.sharedUserId);
return false;
}
} catch (Throwable ignored) {
}
}
// try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//由于调用PackageInstaller安装失败的情况下, 重复安装会导致内存占用无限增长的问题.
//所以在安装之前需要判断当前包名是否有过失败记录, 如果以前有过失败记录, 则不能再使用该方法进行安装
if (sPreferences == null) {
sPreferences = context.getSharedPreferences(SP_NAME_PACKAGE_INSTALL_RESULT, Context.MODE_PRIVATE);
}
String packageName = apkInfo.getPackageName();
boolean canInstall = sPreferences.getBoolean(packageName, true);
if (canInstall) {
boolean success = installByPackageInstaller(context, file, apkInfo);
sPreferences.edit().putBoolean(packageName, success).apply();
if (success) {
LogUtils.iTag(TAG, "Install Success[PackageInstaller]: " + file.getAbsolutePath());
return true;
}
}
}
if (installByReflect(context, file)) {
if (sPreferences != null)
sPreferences.edit().putBoolean(apkInfo.getPackageName(), true).apply();
LogUtils.iTag(TAG, "Install Success[Reflect]", file.getPath());
return true;
}
if (installByShell(file, DeviceUtils.isDeviceRooted())) {
if (sPreferences != null)
sPreferences.edit().putBoolean(apkInfo.getPackageName(), true).apply();
LogUtils.iTag(TAG, "Install Success[Shell]", file.getPath());
return true;
}
// } catch (InterruptedException e) {
// throw e;
// } catch (Throwable e) {
// e.printStackTrace();
// LogUtils.wTag(TAG, e);
// }
LogUtils.iTag(TAG, "Install Failure: " + file.getAbsolutePath());
return false;
}