Android系统升级分为:全量升级和增量升级。全量升级一般是首次安装,需要打全量OTA升级包;而增量升级,可以打增量OTA升级包,也可以通过静默安装来实现升级。这里主要介绍增量升级的全流程:获取升级包—>验证升级包—>版本对比—>压缩包解压—>升级—>进度监听—>重启系统。
一、获取升级包
获取升级包一般有两种方式:从网络下载和SD卡读取。这里介绍的是从SD卡读取升级包,当监听到SD卡插入时,等待SD卡扫描完成,然后遍历SD卡目录,判断有没升级包。
//注册广播监听SD卡插拔
private void registerBroadcast(){
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
intentFilter.addAction(Intent.ACTION_MEDIA_REMOVED);
intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
intentFilter.addDataScheme("file");
cardReceiver = new SDCardReceiver();
registerReceiver(cardReceiver, intentFilter);
}
//SD卡插拔广播接收并处理
private class SDCardReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (intent == null || intent.getAction() == null)
return;
switch (intent.getAction()){
case Intent.ACTION_MEDIA_MOUNTED://SD卡插入
break;
case Intent.ACTION_MEDIA_REMOVED://SD卡移除
case Intent.ACTION_MEDIA_UNMOUNTED:
break;
case Intent.ACTION_MEDIA_SCANNER_FINISHED://SD卡扫描完成
//获取升级包路径
String updateFileName = FileUtil.getUpdateFile();
if (TextUtils.isEmpty(updateFileName))
return;
//比较版本号
int checkVersion = VersionUtil.parseVersion(updateFileName);
int result = VersionUtil.isNewVersion(checkVersion);
if (result == 0){
Log.i(TAG, "the same version, no need upgrade...");
return;
}
String updateMsg = result > 0 ? getString(R.string.update_msg_new)
: getString(R.string.update_msg_old);
showConfirmDialog(getString(R.string.update_tip), updateMsg);
break;
default:
break;
}
}
}
二、验证升级包
如果是OTA升级包,一般采用系统签名,然后系统升级前,会进行系统签名的校验,防止不合法或者不完整的升级包。如果是静默安装升级包,可以使用MD5校验。
三、版本对比
版本对比过程是,从升级包获取待升级版本,与当前系统版本进行对比。
/**
* 对比当前版本与待升级版本
* @param checkVersion 待升级版本
* @return 1:新版本;0:相同版本;-1:旧版本
*/
public static int isNewVersion(float checkVersion){
float currentVersion = SystemUtil.getAppVersion();
HxLogUtil.LogError(TAG, "currentAppVersion="+currentVersion);
if (checkVersion > currentVersion){
return 1;
}else if (checkVersion < currentVersion){
return -1;
}else {
return 0;
}
}
系统版本,我们可以使用系统Settings进行储存与读取:
public static void setAppVersion(float appVersion){
Settings.System.putFloat(UpdateApplication.getInstance().getContentResolver(),
"appVersion", appVersion);
}
public static float getAppVersion(){
return Settings.System.getFloat(UpdateApplication.getInstance().getContentResolver(),
"appVersion", 1.0f);
}
四、解压升级包
解压发生在版本对比后,如果判断为新版本,那么我们才从SD卡拷贝升级包到内存并解压。
/**
* 解压zip到指定的路径
* @param zipFile ZIP文件路径
* @param outPath 待解压缩路径
* @throws Exception Exception
*/
public static void unZipFile(String zipFile, String outPath) throws Exception {
if (TextUtils.isEmpty(zipFile) || TextUtils.isEmpty(outPath))
return;
ZipInputStream inZip = new ZipInputStream(new FileInputStream(zipFile));
ZipEntry zipEntry;
String szName;
while ((zipEntry = inZip.getNextEntry()) != null) {
szName = zipEntry.getName();
if (zipEntry.isDirectory()) {
//获取部件的文件夹名
szName = szName.substring(0, szName.length() - 1);
File folder = new File(outPath + File.separator + szName);
if(!folder.mkdirs())
break;
} else {
HxLogUtil.LogError("FileUtil","unzip=" + outPath + File.separator + szName);
File file = new File(outPath + File.separator + szName);
if (!file.exists()){
if(!file.createNewFile()) {
HxLogUtil.LogError("FileUtil","createNewFile error");
continue;
}
}
// 获取文件的输出流
FileOutputStream out = new FileOutputStream(file);
int len;
byte[] buffer = new byte[1024];
// 读取(字节)字节到缓冲区
while ((len = inZip.read(buffer)) != -1) {
// 从缓冲区(0)位置写入(字节)字节
out.write(buffer, 0, len);
out.flush();
}
out.close();
}
}
inZip.close();
}
五、升级
解压完升级包,得到apk文件后,我们就可以开始升级了。这里使用的是静默安装,反射调用系统的API:
//遍历所有APK文件进行升级
public void update(String updateFilePath){
if (TextUtils.isEmpty(updateFilePath))
return;
File updateFile = new File(updateFilePath);
if (updateFile.exists()){
File[] apkList = updateFile.listFiles();
if (apkList == null || apkList.length == 0){
HxLogUtil.LogError(TAG, "no any apk in the package...");
return;
}
apkTotal = apkList.length;
for (File apk : apkList){
installSilent(UpdateApplication.getInstance(), apk.getAbsolutePath());
}
}
}
1、静默升级的方法来自PackageManager的installPackage方法,这里需要传入context和filePath参数:
private void installSilent(Context context, String filePath) {
try {
PackageManager packageManager = context.getPackageManager();
Method method = packageManager.getClass().getDeclaredMethod("installPackage",
Uri.class, IPackageInstallObserver.class, int.class, String.class);
method.setAccessible(true);
File apkFile = new File(filePath);
Uri apkUri = Uri.fromFile(apkFile);
method.invoke(packageManager, apkUri, new IPackageInstallObserver.Stub() {
@Override
public void packageInstalled(String pkgName, int resultCode) throws RemoteException {
apkCount ++;
if (onUpdateListener != null){
onUpdateListener.onUpdate(apkCount*100/apkTotal);
}
}
}, 2, "com.example.update");
} catch (Exception e) {
e.printStackTrace();
}
}
2、除了反射调用,我们也可以使用pm install -r apkPath指令来静默安装:
//使用pm指令进行静默安装
private static boolean doUpdate(String apkPath){
if (TextUtils.isEmpty(apkPath))
return false;
String cmd = "pm install -r " + apkPath + " \n";
try {
Process process = Runtime.getRuntime().exec(cmd);
process.waitFor();
InputStream inputStream = process.getInputStream();
byte[] data = new byte[1024];
int size = inputStream.read(data);
if (size > 0){
String result = new String(data, 0, size);
}
inputStream.close();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
return false;
}
return true;
}
3、OTA升级是调用RecoverySystem的installPackage方法,需要Recovery权限:
public static void installPackage(Context context, File packageFile, boolean processed)
throws IOException {
synchronized (sRequestLock) {
LOG_FILE.delete();
// Must delete the file in case it was created by system server.
UNCRYPT_PACKAGE_FILE.delete();
String filename = packageFile.getCanonicalPath();
Log.w(TAG, "!!! REBOOTING TO INSTALL " + filename + " !!!");
// If the package name ends with "_s.zip", it's a security update.
boolean securityUpdate = filename.endsWith("_s.zip");
// If the package is on the /data partition, the package needs to
// be processed (i.e. uncrypt'd). The caller specifies if that has
// been done in 'processed' parameter.
if (filename.startsWith("/data/")) {
if (processed) {
if (!BLOCK_MAP_FILE.exists()) {
Log.e(TAG, "Package claimed to have been processed but failed to find "
+ "the block map file.");
throw new IOException("Failed to find block map file");
}
} else {
FileWriter uncryptFile = new FileWriter(UNCRYPT_PACKAGE_FILE);
try {
uncryptFile.write(filename + "\n");
} finally {
uncryptFile.close();
}
// UNCRYPT_PACKAGE_FILE needs to be readable and writable
// by system server.
if (!UNCRYPT_PACKAGE_FILE.setReadable(true, false)
|| !UNCRYPT_PACKAGE_FILE.setWritable(true, false)) {
Log.e(TAG, "Error setting permission for " + UNCRYPT_PACKAGE_FILE);
}
BLOCK_MAP_FILE.delete();
}
// If the package is on the /data partition, use the block map
// file as the package name instead.
filename = "@/cache/recovery/block.map";
}
final String filenameArg = "--update_package=" + filename + "\n";
final String localeArg = "--locale=" + Locale.getDefault().toLanguageTag() + "\n";
final String securityArg = "--security\n";
String command = filenameArg + localeArg;
if (securityUpdate) {
command += securityArg;
}
RecoverySystem rs = (RecoverySystem) context.getSystemService(
Context.RECOVERY_SERVICE);
if (!rs.setupBcb(command)) {
throw new IOException("Setup BCB failed");
}
// Having set up the BCB (bootloader control block), go ahead and reboot
// 如果已经启动BCB,执行系统重启
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
String reason = PowerManager.REBOOT_RECOVERY_UPDATE;
// On TV, reboot quiescently if the screen is off
//在TV中,如果屏幕处于熄灭状态,那么静默重启
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
if (wm.getDefaultDisplay().getState() != Display.STATE_ON) {
reason += ",quiescent";
}
}
pm.reboot(reason);
throw new IOException("Reboot failed (no permissions?)");
}
}
六、进度监听
我们可以使用当前已安装app数与总app数计算百分比,然后回调升级进度:
apkCount ++;
if (onUpdateListener != null){
onUpdateListener.onUpdate(apkCount*100/apkTotal);
}
监听升级进度,更新UI界面:
@Override
public void onUpdate(int progress) {
//使用handler通知主线程更新升级进度
mHandler.obtainMessage(MSG_PROGRESS, progress).sendToTarget();
if (progress == 100){
//保存更新的版本号
if (checkVersion > 0){
SystemUtil.setAppVersion(checkVersion);
}
//删除内存升级包
boolean deleteResult = FileUtil.deleteFolder(outputPath);
HxLogUtil.LogInfo(TAG, "delete result=" + deleteResult);
//通知升级完成
mHandler.sendEmptyMessage(MSG_UPDATE_FINISH);
}
}
七、系统重启
在升级完成后,我们一般调用PowerManager重启系统,保证升级后的app重新启动生效:
public static void reboot(Context context){
PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
if (powerManager != null) {
powerManager.reboot("");
}
}
至此,整个系统升级流程已完成。。。