在做醫(yī)院項(xiàng)目的時(shí)候,遇到了醫(yī)院病人床頭屏app需要靜默安裝的需求。靜默安裝就是被安裝的app在升級或者安裝的時(shí)候不需要用戶手動點(diǎn)擊確認(rèn)安裝嵌巷,讓app自動在后臺安裝或者升級成功重窟。
要想實(shí)現(xiàn)app靜默安裝我們需要下面4個(gè)步驟:
1.首先我們需要在配置清單文件里面添加INSTALL_PACKAGE權(quán)限
<uses-permission android:name="android.permission.INSTALL_PACKAGES" />
2.把該應(yīng)用的uid
設(shè)置為系統(tǒng)級別的,在manifest標(biāo)簽下添加以下屬性
android:sharedUserId="android.uid.system"
3.僅僅設(shè)置uid隆敢,還是沒法實(shí)現(xiàn)靜默安裝发皿,因?yàn)橄到y(tǒng)并不認(rèn)為你這個(gè)app是系統(tǒng)級別的應(yīng)用,所以拂蝎,還應(yīng)該對該應(yīng)用的APK進(jìn)行系統(tǒng)簽名
(注意:不是那個(gè)靜默安裝的APK穴墅,是這個(gè)實(shí)現(xiàn)靜默安裝程序的APK)。簽名過程如下:
總共需要三個(gè)文件:
- SignApk.jar %系統(tǒng)源碼%/out/host/linux-x86/framework/signapk.jar
- platform.x509.pem %系統(tǒng)源碼%/build/target/product/security/platform.x509.pem
- platform.pk8 %系統(tǒng)源碼%/build/target/product/security/platform.pk8
由于我們的安卓設(shè)備是第三方定制的安卓系統(tǒng)温自,我們只能去找安卓設(shè)備供應(yīng)商要系統(tǒng)簽名需要的這三個(gè)文件玄货。
利用簽名工具signapk.jar修改應(yīng)用程序簽名:命令為:
java -jar signapk.jar platform.x509.pem platform.pk8 my.apk mysigned.apk
my.apk是我們的應(yīng)用程序的路徑,mysigned.apk是應(yīng)用系統(tǒng)簽名之后生成的新的apk的路徑
4.靜默安裝代碼
/**
* 靜默安裝
* 會依次調(diào)用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要低, 則需要進(jìn)一步校驗(yàn)簽名和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) {
//由于調(diào)用PackageInstaller安裝失敗的情況下, 重復(fù)安裝會導(dǎo)致內(nèi)存占用無限增長的問題.
//所以在安裝之前需要判斷當(dāng)前包名是否有過失敗記錄, 如果以前有過失敗記錄, 則不能再使用該方法進(jìn)行安裝
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;
}