上一篇說了Android應用的升級,這篇來介紹下關于整個系統的升級。公司的車載系統使用了MTK的板子,深度定制的Android系統,平時開發過程中的修改可以直接重新燒錄固件,但設備量產投入市場之后的修改只能通過OTA的方式進行更新。
系統升級一般通過差分包的方式,因為整包更新需要的安裝包有幾百M,一般的小更新打個差分包的話也就幾M十幾M而已,能省不少流量。
設備檢測更新的流程基本和上一篇文章一樣,系統可以在設置界面上增加檢測升級的入口。
系統升級流程
- 向服務器請求系統更新信息;
- 獲取到返回信息后,判斷是否有更新版本,若有更新,進入步驟3;
- 下載服務器對應的更新文件;
- 下載完成后驗證更新文件;
- 系統重啟進入recovery模式進行升級,升級完成后設備重啟。
更新信息的請求和返回信息的處理過程與應用的更新升級一致。
更新文件的下載這次使用了系統自帶的類 DownloadManager。
DownloadManager 能自動管理系統的下載任務,在聯網發生變化、系統開關機、斷點續傳等情況自行進行處理。
創建下載任務,設置指定下載目錄
mDownloadManager = (DownloadManager)getSystemService(DOWNLOAD_SERVICE);
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
request.setDestinationInExternalPublicDir(OTAUtil.otaFolderName(), OTAUtil.otaFileName());
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
request.setVisibleInDownloadsUi(false);
mDownloadId = mDownloadManager.enqueue(request);
DownloadManager 下載進度可以顯示在通知欄中,也可以選擇只在特定聯網情況下(如wifi、移動信號)進行任務。
監聽下載狀態
class CompleteReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
long completeDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
if (completeDownloadId == mDownloadId) {
// download successful
String msg;
switch (OTAUtil.getOTADownloadStatus(completeDownloadId, mDownloadManager)) {
case DownloadManager.STATUS_FAILED:
msg = "Download failed!";
break;
case DownloadManager.STATUS_PAUSED:
msg = "Download paused!";
break;
case DownloadManager.STATUS_PENDING:
msg = "Download pending!";
break;
case DownloadManager.STATUS_RUNNING:
msg = "Download in progress!";
break;
case DownloadManager.STATUS_SUCCESSFUL:
break;
default:
msg = "Download is nowhere in sight";
break;
}
Log.i(TAG, "CompleteReceiver onReceive...." + msg);
}
}
}
用來更新界面的進度條
class DownloadChangeObserver extends ContentObserver {
public DownloadChangeObserver() {
super(mHandler);
}
@Override
public void onChange(boolean selfChange) {
int progress = OTAUtil.getOTADownloadPro(mDownloadId, mDownloadManager);
Message msg = mHandler.obtainMessage();
msg.what = UPDATE_DOWNLOAD_PROGRESS;
msg.arg1 = progress;
mHandler.sendMessage(msg);
}
}
其中getOTADownloadPro 方法通過id獲取指定下載任務的進度值
public static int getOTADownloadPro(long id, DownloadManager downloadManager) {
double progress = 0.0;
DownloadManager.Query q = new DownloadManager.Query();
q.setFilterById(id);
Cursor c = downloadManager.query(q);
if (c.moveToFirst()) {
int sizeIndex = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
int downloadedIndex = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
long size = c.getInt(sizeIndex);
long downloaded = c.getInt(downloadedIndex);
if (size != -1) {
progress = downloaded * 100.0 / size;
}
}
c.close();
return (int)progress;
}
這里有一個注意問題,正常情況下使用DownloadManager下載的文件只能保存在系統的外部存儲中(系統級的DownloadManager有個私有方法能將文件保存在內部存儲),而調用系統的接口更新升級時,recovery模式下系統文件路徑會重定向,此時無法找到原來保存在外部存儲的文件。所以為了保證能正常安裝,下載好的更新文件要保存到內部存儲中。
將更新文件從外部存儲拷貝到/data/recovery/ota.zip路徑下,這里使用了第三方庫 RootTools,能夠以命令行的方式,將文件從源目錄拷貝到指定目錄。
因為要對內部存儲進行讀寫,所以記得加上對應的權限
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/>
安裝更新的函數具體實現
public static boolean installOtaPackageAuto(final Context context, File file) {
String internalPath = "/data/recovery/ota.zip";
String cmd = "cat " + file.getAbsolutePath() + " > " + internalPath;
String res = Tools.shell(cmd, false);
Log.i(TAG, "shell: " + res);
File otaPackageFile = new File(internalPath);
try {
RecoverySystem.verifyPackage(otaPackageFile , null , null);
}
catch ( IOException e ) {
((HDUpdateActivity)context).runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(context, "RecoverySystem verifyPackage failed, file doesn't exist", Toast.LENGTH_LONG).show();
}
});
e.printStackTrace( );
return false;
}
catch ( GeneralSecurityException e ) {
((HDUpdateActivity)context).runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(context, "RecoverySystem verifyPackage failed, invalid package", Toast.LENGTH_LONG).show();
}
});
e.printStackTrace( );
return false;
}
try {
RecoverySystem.installPackage( context , otaPackageFile );
}
catch ( IOException e ) {
((HDUpdateActivity)context).runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(context, "RecoverySystem installPackage error, failed to install", Toast.LENGTH_LONG).show();
}
});
e.printStackTrace( );
return false;
}
return true;
}
傳入的參數 file 為DownloadManager已經下載到外部存儲的OTA更新文件,通過執行cat命令,將更新文件保存到內部存儲/data/recovery/ota.zip
String internalPath = "/data/recovery/ota.zip";
String cmd = "cat " + file.getAbsolutePath() + " > " + internalPath;
String res = Tools.shell(cmd, false);
調用了系統方法verifyPackage驗證更新包是否合法,若驗證失敗則安裝過程終止
RecoverySystem.verifyPackage(otaPackageFile , null , null);
接下來直接調用系統安裝更新的方法
RecoverySystem.installPackage( context , otaPackageFile );
若成功則設備重啟,并進入recovery模式更新升級,升級完成后設備重啟。
制作差分包
MTK系統的目錄
可以看到每次通過make命令編譯后生成的image文件既是 SP Flash Tool 燒錄需要用到的固件。
ota打包
make otapackage
打包完成后命令行打印的結果
這里有兩個文件需要注意的,一個在out/target/product/CM01B目錄下,一個在out/target/product/CM01B/obj/PACKAGING/target_files_intermediates目錄下(CM01B為產品編號,每個項目不一樣),雖然文件名一樣,但前者有400多M,后者800多M,和image固件大小差不多。
out/target/product/CM01B目錄下的文件為完全更新的包,可以用來給系統進行完全升級。
out/target/product/CM01B/obj/PACKAGING/target_files_intermediates目錄下的文件主要用來生成差分包(具體能不能直接用來升級沒嘗試過),通過兩個這種包可以生成差分包文件。
系統生成差分包的工具在這個位置,官方標準工具,里面包含了制作差分包的腳本
這里需要注意,執行差分包命令時必須在根目錄下執行,因為腳本里面寫定了相對路徑的引用文件。
制作差分包需要準備兩個不同版本的文件(out/target/product/CM01B/obj/PACKAGING/target_files_intermediates目錄下的),假設分別為a.zip和b.zip
將兩個文件拷貝到根目錄下,然后運行命令
./build/tools/releasetools/ota_from_target_files -i a.zip b.zip ota.zip
最終生成的差分包文件為ota.zip,即b相對于a修改了的內容,只能用在a上將a升級成b。ota.zip差分包的升級使用要求當前系統必須是a,否則無法進行升級。
系統升級
系統通過差分包升級,安裝時進入刷機界面,完成后自動重啟,升級完成
如果差分包有不正確或者沒保存到內部存儲中會報錯
以上
源碼和設備相關聯的,這次就不給了。完。:)