2019-09-18
關鍵字:APK后台升級、APK自動升級
最近在磕一個APK,要做一個熱更新的功能。筆者以前從來沒有做過類似的功能,但沒吃過豬肉總是見過豬跑步的,想了一下,要實現一個純粹的升級功能似乎也不難。
1、升級原理
一個純粹的升級功能只需要三個模塊。
1、服務器;
2、升級包與升級信息;
3、具備升級功能的應用程序。
1、服務器
如果僅是想實現一個純粹的升級功能的話,那服務器這邊沒啥特殊的要求。搭建一個最普通的 HTTP 訪問型的站點即可。
2、升級包與升級信息
升級包就是新版本的 APK 程序安裝包。但由於升級包通常來講不具備信息描述能力,所以我們要再額外准備一個升級描述信息。升級策略就是將當前升級包的相關信息記錄在升級信息中,客戶端通過解析、判斷升級信息來決定升級與否。換句話說,服務器端對外只暴露出一個升級信息出來。真實的升級程序地址,是需要解析升級信息來來獲取的。
3、具備升級功能的應用程序
至於客戶端,職責就清晰且單一了。程序啟動后,在后台開一個線程定時訪問升級服務器,拿到升級信息以后再做解析判斷。當有更新版本時,下載然后提示升級。僅此而已。
2、實現
1、服務器搭建
服務器端很簡單,使用 Ngix 搭建即可。然后開辟一個目錄專門用於存放應用升級的文件。
2、升級包與升級信息准備
升級包就不說了,直接編譯生成 APK 文件即可。
至於升級信息,看實際需求咯。比如筆者這邊的升級需求就非常純粹。僅有一個版本號與升級文件名。
[root@localhost umlocator]# cat upgradeinfo.txt version=0 app_name=umlocator_alpha3_ver3.apk [root@localhost umlocator]#
客戶端在拿到這個升級信息時,首先檢查 version 字段記載的版本號。如果該版本號大於應用當前的版本號,則表明有新版本,需要下載新版本的程序。
app_name 字段記載的就是該新版本的安裝包的名稱,同時也是路徑,因為筆者設定的是升級包與升級信息於同一目錄下,所以客戶端在拿到新版本的程序名以后,將自行將網址替換一下即可。
顯然這份升級描述信息是極其簡陋的。連個 md5 檢驗都沒有,但筆者前面說了,只是想實現一個純粹的升級功能而已。業務上的合理性不重要。
3、客戶端升級處理邏輯
筆者這邊應用程序的升級總體邏輯很簡單:程序在登錄成功以后通過一個后台子線程,定時向服務器查詢升級信息。當檢查到有新版本時,立即開始下載,下載完成以后即彈出提示進行應用升級。
后台線程通過一個 Service 來創建。
<service android:name=".UpgradeService" android:enabled="true" android:exported="false" />
因為后台線程服務是不可能開放給外部應用調用的,所以將 exported 設置為 false。同時,創建升級檢測子線程不一定非得通過 Serivce 完成,大家可以按自己的實際業務需求來抉擇。
子線程的實現如下所示:
private Thread checkThread = new Thread(){ @Override public void run() { while(isContinueCheck) { Logger.v(TAG, "Upgrade:" + counter); if(isDownloading) { Logger.i(TAG, "Downloading new version apk:" + progress + "%"); }else{ // 180s. if(counter++ >= UPGRADE_CHECK_INTERVAL) { counter = 0;// reset. checkUpdate(); } } try { Thread.sleep(10000); // 10s. } catch(InterruptedException e) { e.printStackTrace(); } } Logger.i(TAG, "The upgrade check thread was stopped."); } };
這個檢測線程是通過一個標志位 isContinueCheck 來判斷是否持續檢測的。這個 while 語句每 10 秒執行一次,但是仍然有一個計數器 counter 來決定實際連接服務器檢測升級信息的時間間隔。筆者這里設置成 counter 達到 18 ,即每 180 秒連接一次服務器查詢升級信息。這個檢測頻率算是很高的了,檢測頻率高不是說會擔心消耗手機端的資源,而是要擔心服務器的資源消耗情況。當應用的用戶量足夠大時,這種檢測頻率,對服務提供方來講是很燒錢的。但還是那句話,筆者僅用於演示一個純粹的升級功能,所以這些都不重要。
那我們要如何來啟動這個子線程呢?
筆者是將升級功能封裝在 Serivce 中的。Service 有個特性:在應用運行期間,通過 start 的方式啟動 Service 僅會實例化一份 Service 類。
所以筆者將控制子線程循環運行的標志位 isContinueCheck 設為實例變量。然后通過外部的 Intent 來攜帶當前是要“啟動”還是“停止”線程的檢測。在 MainActivity 的 onResume() 方法中,發送啟動線程的命令。在 onPause() 方法中發送停止的命令。
@Override protected void onResume() { super.onResume(); //Start the upgrade check daemon. Intent intent = new Intent(this, UpgradeService.class); intent.putExtra("isContinueCheck", true); startService(intent); } @Override protected void onPause() { super.onPause(); //stop the upgrade check thread. Intent intent = new Intent(this, UpgradeService.class); intent.putExtra("isContinueCheck", false); startService(intent); }
同時,為了提升體驗,應該在程序啟動之初就檢查一次升級信息。因此要在啟動線程前將計數器 counter 的值 設為 17。
private void startThread() { Logger.d(TAG, "checkThread is alive:" + checkThread.isAlive()); if(checkThread.isAlive()) { if(isContinueCheck) { Logger.d(TAG, "upgrade check thread already running..."); }else{ Logger.d(TAG, "stopping the upgrade check thread..."); } }else{ /* * 升級策略:每 180 秒檢查一次升級包,線程每 10 秒執行一次循環。 * 剛打開程序時立即檢查是否有新升級包。 * 所以 counter 的默認值設為 17。 * */ counter = UPGRADE_CHECK_INTERVAL - 1; // counter = 17; checkThread.start(); Logger.d(TAG, "started the upgrade check thread."); } }
關於 HTTP 通信,筆者就直接使用 OkHttpUtils 開源庫來實現了。當成功得到服務器的返回信息以后即開始解析了,解析算法根據自己的升級信息來寫。筆者的實現如下圖所示:
有個地方要注意的就是查詢應用當前的版本號。Android SDK 有接口可以直接查
getPackageManager().getPackageInfo(getPackageName(), 0).versionCode;
然后就是下載文件了。下載仍然是使用 OkHttpUtils 開源庫來實現。這里強烈建議在 sdcard 中創建一個專用目錄用於保存下載文件。至於本地文件的管理邏輯還不那么簡單,就不再贅述了。OkHttpUtils 有一個回調類是專用於下載文件的(FileCallBack 類),照着下圖所示的代碼來寫即可:
如果想監聽下載進度,FileCallBack 類也有一個方法,重寫該方法即可:
在這個 inProgress() 方法中,progress 是百分比形式的當前下載進度。文件下載完成時它的值為 1.000000
在應用中可以通過 startActivity 的方式來實現打開某 APK 安裝引導程序的界面,它的實現代碼為:
Uri uri = Uri.fromFile(apk); Intent intent = new Intent(); intent.setClassName("com.android.packageinstaller", "com.android.packageinstaller.PackageInstallerActivity"); intent.setData(uri); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(intent);
上面加紅標粗的是 File 類的實例,它就是你下載好的 APK 文件。
一個純粹的 APK 升級功能的主要思路就是這樣。至於更詳情的邏輯處理,就要大家自行根據業務需求來實現了。