静默升级通常指的是在用户不知情的情况下,应用程序能够自动下载并安装更新。这种升级过程涉及到几个步骤:
- 获取版本信息:应用在启动时检查当前版本号,这通常是通过访问服务器以获取最新的版本信息。
- 开始下载更新:一旦检测到新版本可用,应用会开始从服务器上下载更新包。
- 保存更新包:更新包被下载后,通常会保存在设备的指定文件夹中。
- 安装更新:在更新包下载完成后,应用需要使用特定的命令来安装更新。这些命令可能包括
pm install或其他相关的Shell命令。
需要注意的是:
1、静默升级需要添加系统权限,并且apk得是系统应用;
2、 静默升级并不是所有情况下都可行或安全的。它通常需要设备具有较高的权限,如ROOT权限,以及可能需要开启辅助功能。此外,某些设备或制造商可能会禁用这些功能,因此非官方的静默升级方法可能会有安全风险。
在实际操作中,可以使用一些开源库或工具来帮助实现静默升级的功能。例如,Aria是一个可以用于静默升级的开源框架,它可以处理应用的版本更新流程。另一个例子是AutoInstaller项目,它提供了更高级的方法来自动化应用的静默安装和升级。
要实现静默升级,首先要准备的
一般需要该应用是系统级别的应用,经过了平台下发的对应签名apk,即:
1.内置到ROM,即APK包的安装位置是/system/app下。(制成一个系统刷机包)
2.使用APK的目标安装系统同样的签名。(系统签名)
静默升级步骤
- 首先导入安卓工具库依赖:
implementation 'com.blankj:utilcode:1.26.0'
- 将SilentInstallUtils工具类复制到项目中:
/**
* 静默安装工具类
*/
public class SilentInstallUtils {
private static final String SP_NAME_PACKAGE_INSTALL_RESULT = "package_install_result";
private static SharedPreferences sPreferences;
public static synchronized void silenceInstall(@NonNull Context context, @NonNull String path) {
Log.d("","InstallByPackageInstaller, silenceInstall starting...");
try {
boolean installSuccess = install(context, path);
if (installSuccess) {
Intent intent = context.getPackageManager().getLaunchIntentForPackage(context.getPackageName());
if (intent != null) {
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP);
}
ContextCompat.startActivity(context, intent, null);
Log.d("","InstallByPackageInstaller, silenceInstall success");
} else {
Log.d("","InstallByPackageInstaller, silenceInstall fail");
}
} catch (InterruptedException e) {
Log.d("","InstallByPackageInstaller, silenceInstall fail : " + e);
}
}
@Synchronized
public static boolean install(@NonNull Context context, @NonNull String apkFile) throws InterruptedException {
File file = new File(apkFile);
if (apkFile.isEmpty() || !file.exists()) {
return false;
}
Context mContext = context.getApplicationContext();
//apk合法性判断
AppUtils.AppInfo apkInfo = AppUtils.getApkInfo(file);
if (apkInfo == null || apkInfo.getPackageName().isEmpty()) {
Log.d("","InstallByPackageInstaller,apk info is null, the file maybe damaged: " + file.getAbsolutePath());
return false;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//由于调用PackageInstaller安装失败的情况下, 重复安装会导致内存占用无限增长的问题.
//所以在安装之前需要判断当前包名是否有过失败记录, 如果以前有过失败记录, 则不能再使用该方法进行安装
if (sPreferences == null) {
sPreferences = mContext.getSharedPreferences(SP_NAME_PACKAGE_INSTALL_RESULT, Context.MODE_PRIVATE);
}
String packageName = apkInfo.getPackageName();
boolean success = installByPackageInstaller(mContext, file, apkInfo);
sPreferences.edit().putBoolean(packageName, success).apply();
if (success) {
Log.d("","InstallByPackageInstaller,Install Success[PackageInstaller]:: " + file.getAbsolutePath());
return true;
}
}
return false;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static boolean installByPackageInstaller(@NonNull Context context, @NonNull File file, @NonNull AppUtils.AppInfo apkInfo) {
Log.d("","InstallByPackageInstaller,filePath: " + file.getPath());
OutputStream outStream = null;
InputStream inStream = null;
PackageInstaller.Session session = null;
int sessionId = -1;
boolean success = false;
PackageManager pm = context.getPackageManager();
PackageInstaller installer = pm.getPackageInstaller();
try {
//初始化安装参数
PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
params.setSize(file.length());
params.setAppIcon(ImageUtils.drawable2Bitmap(apkInfo.getIcon()));
params.setAppLabel(apkInfo.getName());
params.setAppPackageName(apkInfo.getPackageName());
sessionId = installer.createSession(params);
//sessionId返回一个正数非零的值,若小于0,则会话开启失败
if (sessionId > 0) {
InstallReceiver callBack = new InstallReceiver(context, true, file.getAbsolutePath());
session = installer.openSession(sessionId);
outStream = session.openWrite(file.getName(), 0, file.length());
inStream = new FileInputStream(file);
int len;
byte[] buffer = new byte[8192];
while ((len = inStream.read(buffer)) != -1) {
outStream.write(buffer, 0, len);
}
session.fsync(outStream);
inStream.close();
outStream.close();
session.commit(callBack.getIntentSender());
success = callBack.isSuccess();
}
} catch (Throwable e) {
Log.d("","InstallByPackageInstaller, fail:" + e.toString());
} finally {
try {
//若会话开启,但未成功,需将其销毁
if (sessionId > 0 && !success) {
if (session != null) {
session.abandon();
}
installer.abandonSession(sessionId);
}
} catch (Throwable e) {
Log.d("","InstallByPackageInstaller, fail:" + e.toString());
}
CloseUtils.closeIOQuietly(inStream, outStream, session);
}
return success;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static boolean uninstallByPackageInstaller(@NonNull Context context, @NonNull String packageName) {
Log.d("","InstallByPackageInstaller, uninstall packageName:" + packageName);
try {
InstallReceiver callBack = new InstallReceiver(context, false, packageName);
PackageInstaller installer = Utils.getApp().getPackageManager().getPackageInstaller();
installer.uninstall(packageName, callBack.getIntentSender());
return callBack.isSuccess();
} catch (Throwable e) {
Log.d("","InstallByPackageInstaller, uninstall fail:" + e.toString());
}
return false;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private static class InstallReceiver extends BroadcastReceiver {
private String ACTION = InstallReceiver.class.getName() + SystemClock.elapsedRealtimeNanos();
private Context mContext;
private String mOperate;
private String mParam;
/**
* 异步转同步
*/
private CountDownLatch mCountDownLatch = new CountDownLatch(1);
private boolean mSuccess;
public InstallReceiver(Context context, boolean isInstall, String param) {
this.mContext = context.getApplicationContext();
this.mOperate = isInstall ? "Install" : "Uninstall";
this.mParam = param;
context.getApplicationContext().registerReceiver(this, new IntentFilter(ACTION));
}
public boolean isSuccess() {
try {
//安装最长等待2分钟
mCountDownLatch.await(2, TimeUnit.MINUTES);
return mSuccess;
} catch (InterruptedException e) {
Log.d("","InstallByPackageInstaller, fail:" + e.toString());
} finally {
mContext.unregisterReceiver(this);
}
return false;
}
public IntentSender getIntentSender() {
return PendingIntent
.getBroadcast(mContext, ACTION.hashCode(), new Intent(ACTION).setPackage(mContext.getPackageName()),
PendingIntent.FLAG_UPDATE_CURRENT)
.getIntentSender();
}
@Override
public void onReceive(Context context, Intent intent) {
try {
int status = -200;
if (intent == null) {
mSuccess = false;
} else {
status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
mSuccess = status == PackageInstaller.STATUS_SUCCESS;
}
Log.d("","InstallByPackageInstaller, receive: " + mOperate + " Result: " + mSuccess + " [" + status + "]");
} finally {
mCountDownLatch.countDown();
- 在ViewModel中调用工具类中的方法:
class MainActivityViewModel : ViewModel(){
fun silentInstall(context : Context){
SilentInstallUtils.silenceInstall(context, "/home/config/app-release.apk")
}
}
- 点击按钮的时候调用ViewModel中silentInstall方法实现静默升级:
class MainActivity : AppCompatActivity() {
private lateinit var mainViewModel : MainActivityViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mainViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)
// 静默升级按钮
val updateButton = findViewById<Button>(R.id.upgrade_button)
updateButton.setOnClickListener {
mainViewModel.silentInstall(this)
Toast.makeText(this, "UpGrade Apk Successful!", Toast.LENGTH_LONG, ).show()
}
}
}
注意:
- 需要在androidManifest中增加系统权限;
- 导入依赖后需要先运行下,否则有可能会报错
报错内容:Didn't find class "com.blankj.utilcode.util.Utils$FileProvider4UtilCode" on path……
解决办法:更换依赖
implementation 'com.blankj:utilcode:1.29.0'
// if u use AndroidX, use the following
implementation 'com.blankj:utilcodex:1.29.0'
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀