通過DownloadManager來下載並靜默安裝APK
當產品有BUG需要被修復的時候,我們可以使用DownloadManager 系統提供的下載類來實現 下載新版本的APK,並通過靜默安裝的方式,將APK神不知鬼不覺的安裝到手機中,靜默安裝分為兩種方式,第一種為root過的手機,第二種為非root過的手機,非root過的手機往往不會使用靜默安裝的方式來更新你的App,而root過的手機實現靜默安裝是我們今天討論的前提。之前在網上搜到的很多靜默安裝都無法正常實現下載後的後臺安裝,不知道是自己理解有誤還是就是有人沒有經過驗證而把錯誤的版本傳遍開來,畢竟root的手機實現流氓的靜默安裝,研究的意義還沒有到親測的地步... 如下程式碼是自己親測過的,但安卓版本在安卓7.0之後,貌似7.0版本略有不同?下面進入主題:
首先講一下我下面程式碼的流程:1.在你應用開始進入的第一個介面的OnCreate方法中,首先從服務端獲取伺服器中的當前版本號,當然直接在後臺寫一個介面顯示新版本的版本號或是在伺服器中寫一個含有版本號的文件也可以啦..2.然後通過getVersionName方法獲取當前應用的版本號與網路獲取的版本號進行對比,3.當版本低於服務端的版本就開啟下載安裝的服務。
下面的程式碼為獲取手機端當前版本的版本號的方法,很簡單是固定的操作:
/** * 獲取當前版本的版本號 */ private int getVersionName() throws Exception { // 獲取packageManager的例項PackageManager packageManager = getPackageManager(); // getPackageName()是你當前類的包名,0代表的是獲取版本資訊 PackageInfo packInfo = packageManager.getPackageInfo(getPackageName(), 0); Log.d("MMM", "packInfo.versionCode:" + packInfo.versionCode); return packInfo.versionCode; }
下面是自己寫的簡單的使用OkHttp網路異步向網路請求資料:
(如果對OkHttp網路請求方面有疑惑可以參考這裡的教學網址,講解的很詳細並且是免費的,裡面對同步非同步請求,檔案下載上傳,表單圖片的上傳下載,和模擬伺服器的搭建都有基本的講解,當初自己在學習OkHttp網路這裡很疑惑的是對後臺服務端不瞭解,並不知道自己移動端的請求伺服器到底收沒收到,或是不知道服務端如何去寫能測試從服務端的下載,畢竟平時的工作兩者都是區分開的,這對測試結果和學習帶來不便,這套視訊也能很好的實現移動端和伺服器端的程式碼測試,為初學者帶來方便:http://www.imooc.com/learn/764)
// 非同步下載檔案 mOkHttpClient = new OkHttpClient(); Request.Builder builder = new Request.Builder(); Request request = builder.get().url(url).build(); Call call = mOkHttpClient.newCall(request); call.enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { Log.d("LLL", "請求失敗"); } @Override public void onResponse(Call call, okhttp3.Response response) throws IOException { string = response.body().string(); } });
當然如果覺得麻煩寫個更新按鈕點選來實現下載安裝也行... ...
接下來是最主要的服務類來實現下載安裝功能的程式碼(我把註釋儘可能的寫在了程式碼上):
public class UpdateService extends Service { private Uri uriForDownloadedFile; public UpdateService() { } File file; /** * 安卓系統下載類 **/ DownloadManager manager; /** * 接收下載完的廣播 **/ DownloadCompleteReceiver receiver; /** * 初始化下載器 **/ private void initDownManager() { manager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE); receiver = new DownloadCompleteReceiver(); // 設定下載地址 String urlPath = "http://192.168.1.111:8080/app-release.apk"; Uri parse = Uri.parse(urlPath); DownloadManager.Request down = new DownloadManager.Request(parse); // 設定允許使用的網路型別,這裡是行動網路和wifi都可以 down.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI); // 下載時通知欄顯示進度 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { down.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); } // 顯示下載頁面 down.setVisibleInDownloadsUi(true); // 設定下載後文件存放位置 Environment.DIRECTORY_DOWNLOADS String apkName = parse.getLastPathSegment(); down.setDestinationInExternalFilesDir(this, Environment.DIRECTORY_DOWNLOADS, apkName); // 將下載請求放入佇列 manager.enqueue(down); // 註冊下載廣播 registerReceiver(receiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } @Override public int onStartCommand(Intent intent, int flags, int startId) { // 呼叫下載 initDownManager(); return super.onStartCommand(intent, flags, startId); } // 需要顯示進度條時再次進行設定 @Nullable @Override public IBinder onBind(Intent intent) { return null; } @Override public void onDestroy() { // 登出下載廣播 if (receiver != null) { unregisterReceiver(receiver); } super.onDestroy(); } // 接受下載完後的intent class DownloadCompleteReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { // 判斷是否下載完成的廣播 if (intent.getAction().equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { // 獲取下載的檔案id long downId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1); Log.d("DownloadCompleteReceive", "id = " + downId); // 自動安裝apk if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { uriForDownloadedFile = manager.getUriForDownloadedFile(downId); Log.d("CCC", "uri = " + uriForDownloadedFile); installApkNew(); } // 停止服務並關閉廣播 UpdateService.this.stopSelf(); } } // 安裝apk protected void installApkNew() { try { String str = "/Android/data/sq.test_updata/files/Download/app-release.apk"; String fileName = Environment.getExternalStorageDirectory() + str; Log.d("MMM", fileName); Process process = Runtime.getRuntime().exec("su"); DataOutputStream dos = new DataOutputStream(process.getOutputStream());dos.write(("pm install -r " + fileName).getBytes()); dos.writeBytes("\n"); dos.flush();}catch (Exception e){ } } } }
以上需要注意的是靜默安裝需要調取root許可權,Runtime.getRuntime().exec("su"); 再通過執行命令列的形式,先查詢到你APK下載到的預設路徑,在使用pm install -r + “路徑”的形式來安裝APK;(這裡網上有很多的版本的寫法,開始覺得一樣,後來才發現裡面存在不同的差異,有的寫法實現不了自動的安裝功能,僅僅就能跳轉到使用者確認或取消安裝的介面為止,後來查詢資料和嘗試寫法的區別在於,dos.write(xxx).getBytes()之後在dos.writeBytes("\n") 最後在flush,而很多錯誤的版本是 dos.writeBytes(commmand)了,這是有本質區別的),思路流程就是在服務中利用下載完傳送廣播,在利用廣播接收器實現安裝功能,如果想要新增進度條等可以利用服務的bind方法中書寫我就不累贅的寫太多跟本文章標題不一致的內容了。
下載的APK可以安裝到SDK中,當然也可以安裝在手機的預設目錄下,如果不知道可以使用RE管理器去查詢(RE管理器需要手機Root許可權),所以說到這就別忘記加許可權啦~
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION"/>
再就是在Activity的呼叫這裡寫一個開啟這個下載服務的方法:
public void update() { new Thread(new Runnable() { @Override public void run() { // 啟動服務 Intent service = new Intent(MainActivity.this, UpdateService.class); startService(service); } }).start(); }
合理的呼叫這個方法就好啦~在就是提示一下,我好久沒有寫服務了手抖把startService寫成了startActivity了...就會報Activity沒有註冊的錯誤提示,記得注意下哦。然後就是我們文章開始階段說的,先通過網路獲取服務端的版本號,在於自己當前版本號作比較,判斷是否載入update方法實現自動下載安裝。如果想要測試一下程式碼能否實現推薦一個比較常用的軟體 上網搜尋apache-tomcat模擬服務端,因為DownloadManager只能訪問http//的協議地址,測試的時候記得更改版本號...打包的簽名需要一致,介面隨意更改下背景顏色來做老版本和新版本的區分。
結語:好久沒寫部落格了,感覺最近收穫有限,不能分享什麼特別牛逼的技術,還是把基礎練好偶爾分享下一些有意思的東西吧~~ 我不知道自己離架構師的路還有多久,但是很慶幸一直在走未曾停過。