見證過部落格園的多次升級,你也希望你的軟體通過更新發布新特性通知使用者吧,是的。
這篇文章是android開發人員的必備知識,是我特別為大家整理和總結的,不求完美,但是有用。
1.設計思路,使用VersionCode定義為版本升級參數。
android為我們定義版本提供了2個屬性:
<manifest package="com.cnblogs.tianxia.subway" android:versionCode="1" <!--Integer類型,系統不顯示給使用者--> android:versionName="1.0"<!--String類型,系統顯示使用者-->></manifest>
Google建議我們使用versionCode自增來表明版本升級,無論是大的改動還是小的改動,而versionName是顯示使用者看的軟體版本,作為顯示使用。所以我們選擇了VersionCode作為我們定義版本升級的參數。
2.工程目錄
為了對真實項目或者企業運用有實戰指導作用,我類比一個獨立的項目,工程目錄設定的合理嚴謹一些,而不是僅僅一個demo。
假設我們以上海地鐵為項目,命名為"Subway",工程結構如下,
3.版本初始化和版本號碼的對比。
首先定義在通用檔案Global.java中定義變數localVersion和serverVersion分別存放本地版本號碼和伺服器版本號碼。
public class Global { //版本資訊 public static int localVersion = 0; public static int serverVersion = 0; public static String downloadDir = "app/download/";}
因為本文只是重點說明升級更新,為了防止其他太多無關代碼冗餘其中,我直接在SubwayApplication中定義方法initGlobal()方法。
/** * 初始化全域變數 * 實際工作中這個方法中serverVersion從伺服器端擷取,最好在啟動畫面的activity中執行 */ public void initGlobal(){ try{ Global.localVersion = getPackageManager().getPackageInfo(getPackageName(),0).versionCode; //設定本地版本號碼 Global.serverVersion = 1;//假定伺服器版本為2,本地版本預設是1 }catch (Exception ex){ ex.printStackTrace(); } }
如果檢測到新版本發布,提示使用者是否更新,我在SubwayActivity中定義了checkVersion()方法:
/** * 檢查更新版本 */ public void checkVersion(){ if(Global.localVersion < Global.serverVersion){ //發現新版本,提示使用者更新 AlertDialog.Builder alert = new AlertDialog.Builder(this); alert.setTitle("軟體升級") .setMessage("發現新版本,建議立即更新使用.") .setPositiveButton("更新", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { //開啟更新服務UpdateService //這裡為了把update更好模組化,可以傳一些updateService依賴的值 //如布局ID,資源ID,動態擷取的標題,這裡以app_name為例 Intent updateIntent =new Intent(SubwayActivity.this, UpdateService.class); updateIntent.putExtra("titleId",R.string.app_name); startService(updateIntent); } }) .setNegativeButton("取消",new DialogInterface.OnClickListener(){ public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }); alert.create().show(); }else{ //清理工作,略去 //cheanUpdateFile(),文章後面我會附上代碼 } }
如:
好,我們現在把這些東西串一下:
第一步在SubwayApplication的onCreate()方法中執行initGlobal()初始化版本變數。
public void onCreate() { super.onCreate(); initGlobal(); }
第二步在SubwayActivity的onCreate()方法中檢測版本更新checkVersion()。
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); checkVersion(); }
現在入口已經開啟,在checkVersion方法的第18行代碼中看出,當使用者點擊更新,我們開啟更新服務,從伺服器上下載最新版本。
4.使用Service在後台從伺服器端下載,完成後提示使用者下載完成,並關閉服務。
定義一個服務UpdateService.java,首先定義與下載和通知相關的變數:
//標題 private int titleId = 0; //檔案儲存體 private File updateDir = null; private File updateFile = null; //通知欄 private NotificationManager updateNotificationManager = null; private Notification updateNotification = null; //通知欄跳轉Intent private Intent updateIntent = null; private PendingIntent updatePendingIntent = null;
在onStartCommand()方法中準備相關的下載工作:
@Override public int onStartCommand(Intent intent, int flags, int startId) { //擷取傳值 titleId = intent.getIntExtra("titleId",0); //建立檔案 if(android.os.Environment.MEDIA_MOUNTED.equals(android.os.Environment.getExternalStorageState())){ updateDir = new File(Environment.getExternalStorageDirectory(),Global.downloadDir); updateFile = new File(updateDir.getPath(),getResources().getString(titleId)+".apk"); } this.updateNotificationManager = (NotificationManager)getSystemService(NOTIFICATION_SERVICE); this.updateNotification = new Notification(); //設定下載過程中,點擊通知欄,回到主介面 updateIntent = new Intent(this, SubwayActivity.class); updatePendingIntent = PendingIntent.getActivity(this,0,updateIntent,0); //設定通知欄顯示內容 updateNotification.icon = R.drawable.arrow_down_float; updateNotification.tickerText = "開始下載"; updateNotification.setLatestEventInfo(this,"上海地鐵","0%",updatePendingIntent); //發出通知 updateNotificationManager.notify(0,updateNotification); //開啟一個新的線程下載,如果使用Service同步下載,會導致ANR問題,Service本身也會阻塞 new Thread(new updateRunnable()).start();//這個是下載的重點,是下載的過程 return super.onStartCommand(intent, flags, startId); }
上面都是準備工作,
從代碼中可以看出來,updateRunnable類才是真正下載的類,出於使用者體驗的考慮,這個類是我們單獨一個線程後台去執行的。
下載的過程有兩個工作:1.從伺服器上下載資料;2.通知使用者下載的進度。
線程通知,我們先定義一個空的updateHandler。
private Handler updateHandler = new Handler(){ @Override public void handleMessage(Message msg) { } };
再來建立updateRunnable類的真正實現:
class updateRunnable implements Runnable { Message message = updateHandler.obtainMessage(); public void run() { message.what = DOWNLOAD_COMPLETE; try{ //增加許可權; if(!updateDir.exists()){ updateDir.mkdirs(); } if(!updateFile.exists()){ updateFile.createNewFile(); } //下載函數,以QQ為例子 //增加許可權; long downloadSize = downloadUpdateFile("http://softfile.3g.qq.com:8080/msoft/179/1105/10753/MobileQQ1.0(Android)_Build0198.apk",updateFile); if(downloadSize>0){ //下載成功 updateHandler.sendMessage(message); } }catch(Exception ex){ ex.printStackTrace(); message.what = DOWNLOAD_FAIL; //下載失敗 updateHandler.sendMessage(message); } } }
下載函數的實現有很多,我這裡把代碼貼出來,而且我們要在下載的時候通知使用者下載進度:
public long downloadUpdateFile(String downloadUrl, File saveFile) throws Exception { //這樣的下載代碼很多,我就不做過多的說明 int downloadCount = 0; int currentSize = 0; long totalSize = 0; int updateTotalSize = 0; HttpURLConnection httpConnection = null; InputStream is = null; FileOutputStream fos = null; try { URL url = new URL(downloadUrl); httpConnection = (HttpURLConnection)url.openConnection(); httpConnection.setRequestProperty("User-Agent", "PacificHttpClient"); if(currentSize > 0) { httpConnection.setRequestProperty("RANGE", "bytes=" + currentSize + "-"); } httpConnection.setConnectTimeout(10000); httpConnection.setReadTimeout(20000); updateTotalSize = httpConnection.getContentLength(); if (httpConnection.getResponseCode() == 404) { throw new Exception("fail!"); } is = httpConnection.getInputStream(); fos = new FileOutputStream(saveFile, false); byte buffer[] = new byte[4096]; int readsize = 0; while((readsize = is.read(buffer)) > 0){ fos.write(buffer, 0, readsize); totalSize += readsize; //為了防止頻繁的通知導致應用吃緊,百分比增加10才通知一次 if((downloadCount == 0)||(int) (totalSize*100/updateTotalSize)-10>downloadCount){ downloadCount += 10; updateNotification.setLatestEventInfo(UpdateService.this, "正在下載", (int)totalSize*100/updateTotalSize+"%", updatePendingIntent); updateNotificationManager.notify(0, updateNotification); } } } finally { if(httpConnection != null) { httpConnection.disconnect(); } if(is != null) { is.close(); } if(fos != null) { fos.close(); } } return totalSize; }
顯示下載進度,
下載完成後,我們提示使用者下載完成,並且可以點擊安裝,那麼我們來補全前面的Handler吧。
先在UpdateService.java定義2個常量來表示下載狀態:
//下載狀態 private final static int DOWNLOAD_COMPLETE = 0; private final static int DOWNLOAD_FAIL = 1;
根據下載狀態處理主線程:
private Handler updateHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch(msg.what){ case DOWNLOAD_COMPLETE: //點擊安裝PendingIntent Uri uri = Uri.fromFile(updateFile); Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.setDataAndType(uri, "application/vnd.android.package-archive"); updatePendingIntent = PendingIntent.getActivity(UpdateService.this, 0, installIntent, 0); updateNotification.defaults = Notification.DEFAULT_SOUND;//鈴聲提醒 updateNotification.setLatestEventInfo(UpdateService.this, "上海地鐵", "下載完成,點擊安裝。", updatePendingIntent); updateNotificationManager.notify(0, updateNotification); //停止服務 stopSelf(); break; case DOWNLOAD_FAIL: //下載失敗 updateNotification.setLatestEventInfo(UpdateService.this, "上海地鐵", "下載完成,點擊安裝。", updatePendingIntent); updateNotificationManager.notify(0, updateNotification); break; default: stopSelf(); } } };
下載完成,
至此,檔案下載並且在通知欄通知進度。
發現本人廢話很多,其實幾句話的事情,來來回回寫了這麼多,囉嗦了,後面博文我會朝著精簡方面努力。
PS:前面說要附上cheanUpdateFile()的代碼
File updateFile = new File(Global.downloadDir,getResources().getString(R.string.app_name)+".apk");if(updateFile.exists()){ //當不需要的時候,清除之前的下載檔案,避免浪費使用者空間 updateFile.delete(); }
謝謝大家!!!!