app是我們在開發時候經常碰到的事情,一般解決一些bug,添加了需求,實現了新的功能,以讓用戶體驗新版本的功能,這些都是項目中用到的,今天就來總結一下
第一種:引用jjdxmashl的jjdxm_update
GitHub地址:jjdxmashl/jjdxm_update
這是大神jjdxmashl的開源項目,下載地址見上方。有版本更新、手動更新、靜默更新、自動更新4種情況。應用內更新,實現類是友盟自動更新sdk的模式,用戶使用前只需要配置自己的服務器更新檢查接口即可(必須接口),也可以擴展加入一個接口作爲在線參數配置來實現,可以實現下面的4種2更新方式和是否強制更新組合使用,支持get、post方式請求網絡,默認是get請求。
4種更新檢查類型
- 手動更新:手動檢測更新(所有網絡類型環境檢測並提示主要用於點擊檢測使用)
- 自動更新:自動檢測更新(所有網絡類型環境檢測並提示)
- 僅WiFi自動檢測更新(只有WiFi網絡類型環境檢測並提示)
- 靜默更新:僅WiFi自動檢測下載(只有WiFi網絡類型環境檢測、下載完才提示)
2種強制更新方式
- 在更新檢查返回後,直接設置update.setForce(true)
- 配合在線參數使用,通過在線參數返回的數據設置UpdateHelper.getInstance().setForced(true)
上述4中更新檢查 結合 2種強制更新,適用於:上一個app版本有重大漏洞,修改在線 參數統一控制所有的app用戶,不更新就不可以使用app。
主要原理:服務器上修改參數值,app端獲取後進行判斷,如果是強制更新,則在打開應用時就提示有新版本的app,更新完成後纔可以使用該app;更新爲完成,則提示框不消失,點擊back鍵則退出應用。
第二種: 判斷VersionCode,xUtils實現下載
第一步 服務器端:- 服務端提供一個藉口,或者網址,我這裏就用的服務器是tomcat,這裏提供一個網址如下
//也就是一個json數據接口 public static final String UPDATE_URL = "http://192.168.1.103:8080/update.json";
-
第二步 客戶端需要實現:
首先我們要去解析服務端給的json,那麼我們就要來創建一個model類了(代碼過多,這裏只有字段,getter和setter方法自己創建):
我們知道,我們在進入app的時候,這個時候如果檢測到服務器端有了新的版本,就回彈出提示框,提示我們更新。這個我們在MainActivity裏面處理邏輯(onCreate()方法裏面)://app名字 private String appname; //服務器版本 private String serverVersion; //服務器標誌 private String serverFlag; //強制升級 private String lastForce; //app最新版本地址 private String updateurl; //升級信息 private String upgradeinfo; //在這裏使用了一個輔助類,基本和model字段差不多: public class UpdateInformation { public static String appname = MyApplication.getInstance() .getResources().getString(R.string.app_name); public static int localVersion = 1;// 本地版本 public static String versionName = ""; // 本地版本名 public static int serverVersion = 1;// 服務器版本 public static int serverFlag = 0;// 服務器標誌 public static int lastForce = 0;// 之前強制升級版本 public static String updateurl = "";// 升級包獲取地址 public static String upgradeinfo = "";// 升級信息 public static String downloadDir = "wuyinlei";// 下載目錄 }
OkhttpManager.getAsync(Config.UPDATE_URL, new OkhttpManager.DataCallBack() { @Override public void requestFailure(Request request, Exception e) { } @Override public void requestSuccess(String result) { try { Log.d("wuyiunlei",result); JSONObject object = new JSONObject(result); UpdateInfoModel model = new UpdateInfoModel(); model.setAppname(object.getString("appname")); model.setLastForce(object.getString("lastForce")); model.setServerFlag(object.getString("serverFlag")); model.setServerVersion(object.getString("serverVersion")); model.setUpdateurl(object.getString("updateurl")); model.setUpgradeinfo(object.getString("upgradeinfo")); tmpMap.put(DeliverConsts.KEY_APP_UPDATE, model); } catch (JSONException e) { e.printStackTrace(); } //發送廣播 sendBroadcast(new Intent(UpdateReceiver.UPDATE_ACTION)); } });
當然了,我們也要註冊和結束廣播:
/** * 廣播註冊 */ private void registerBroadcast() { mUpdateReceiver = new UpdateReceiver(false); mIntentFilter = new IntentFilter(UpdateReceiver.UPDATE_ACTION); this.registerReceiver(mUpdateReceiver, mIntentFilter); } /** * 廣播卸載 */ private void unRegisterBroadcast() { try { this.unregisterReceiver(mUpdateReceiver); } catch (Exception e) { e.printStackTrace(); } }
好了,接下來我們看下我們自定義的廣播接收者UpdateReceiver .java:
這個時候,可能看到服務怎麼這麼多代碼啊,我頭都大了,不要着急,我們一步一步說明一下,這裏邏輯很簡單,就是在通知欄中,用到了通知,這個時候我們有三種情況,造成了我們好多代碼的重複,(你也可以不必考慮那麼多情況),還有,裏面有了幾個工具類,沒有提取出來,分別是獲取sdcard大小是否可用(創建文件夾),獲取當前下載進度,獲取應用大小,下載文件,這裏也可以使用第三方框架來下載。/** * 版本更新升級 廣播接受者 * */ public class UpdateReceiver extends BroadcastReceiver { private AlertDialog.Builder mDialog; public static final String UPDATE_ACTION = "wuyinlei_aixinwen"; private SharedPreferencesHelper mSharedPreferencesHelper; private boolean isShowDialog; public UpdateReceiver() { } public UpdateReceiver(boolean isShowDialog) { super(); this.isShowDialog = isShowDialog; } @Override public void onReceive(Context context, Intent intent) { mSharedPreferencesHelper = mSharedPreferencesHelper .getInstance(MyApplication.getInstance()); //當然了,這裏也可以直接new處hashmap HashMap<String, Object> tempMap = MyApplication.getInstance() .getTempMap(); UpdateInfoModel model = (UpdateInfoModel) tempMap //就是一個標誌 .get(DeliverConsts.KEY_APP_UPDATE); try { /** * 獲取到當前的本地版本 */ UpdateInformation.localVersion = MyApplication .getInstance() //包管理獨享 .getPackageManager() //包信息 .getPackageInfo( MyApplication.getInstance() .getPackageName(), 0).versionCode; /** * 獲取到當前的版本名字 */ UpdateInformation.versionName = MyApplication .getInstance() .getPackageManager() .getPackageInfo( MyApplication.getInstance() .getPackageName(), 0).versionName; } catch (Exception e) { e.printStackTrace(); } //app名字 UpdateInformation.appname = MyApplication.getInstance() .getResources().getString(R.string.app_name); //服務器版本 UpdateInformation.serverVersion = Integer.parseInt(model .getServerVersion()); //服務器標誌 UpdateInformation.serverFlag = Integer.parseInt(model.getServerFlag()); //強制升級 UpdateInformation.lastForce = Integer.parseInt(model.getLastForce()); //升級地址 UpdateInformation.updateurl = model.getUpdateurl(); //升級信息 UpdateInformation.upgradeinfo = model.getUpgradeinfo(); //檢查版本 checkVersion(context); } /** * 檢查版本更新 * * @param context */ public void checkVersion(Context context) { if (UpdateInformation.localVersion < UpdateInformation.serverVersion) { // 需要進行更新 mSharedPreferencesHelper.putIntValue( //有新版本 SharedPreferencesTag.IS_HAVE_NEW_VERSION, 1); //更新 update(context); } else { mSharedPreferencesHelper.putIntValue( SharedPreferencesTag.IS_HAVE_NEW_VERSION, 0); if (isShowDialog) { //沒有最新版本,不用升級 noNewVersion(context); } clearUpateFile(context); } } /** * 進行升級 * * @param context */ private void update(Context context) { if (UpdateInformation.serverFlag == 1) { // 官方推薦升級 if (UpdateInformation.localVersion < UpdateInformation.lastForce) { //強制升級 forceUpdate(context); } else { //正常升級 normalUpdate(context); } } else if (UpdateInformation.serverFlag == 2) { // 官方強制升級 forceUpdate(context); } } /** * 沒有新版本 * @param context */ private void noNewVersion(final Context context) { mDialog = new AlertDialog.Builder(context); mDialog.setTitle("版本更新"); mDialog.setMessage("當前爲最新版本"); mDialog.setNegativeButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create().show(); } /** * 強制升級 ,如果不點擊確定升級,直接退出應用 * * @param context */ private void forceUpdate(final Context context) { mDialog = new AlertDialog.Builder(context); mDialog.setTitle("版本更新"); mDialog.setMessage(UpdateInformation.upgradeinfo); mDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent mIntent = new Intent(context, UpdateService.class); mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mIntent.putExtra("appname", UpdateInformation.appname); mIntent.putExtra("appurl", UpdateInformation.updateurl); //啓動服務 context.startService(mIntent); } }).setNegativeButton("退出", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { // 直接退出應用 //ManagerActivity.getInstance().finishActivity(); System.exit(0); } }).setCancelable(false).create().show(); } /** * 正常升級,用戶可以選擇是否取消升級 * * @param context */ private void normalUpdate(final Context context) { mDialog = new AlertDialog.Builder(context); mDialog.setTitle("版本更新"); mDialog.setMessage(UpdateInformation.upgradeinfo); mDialog.setPositiveButton("確定", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { Intent mIntent = new Intent(context, UpdateService.class); mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //傳遞數據 mIntent.putExtra("appname", UpdateInformation.appname); mIntent.putExtra("appurl", UpdateInformation.updateurl); context.startService(mIntent); } }).setNegativeButton("取消", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }).create().show(); } /** * 清理升級文件 * * @param context */ private void clearUpateFile(final Context context) { File updateDir; File updateFile; if (Environment.MEDIA_MOUNTED.equals(Environment .getExternalStorageState())) { updateDir = new File(Environment.getExternalStorageDirectory(), UpdateInformation.downloadDir); } else { updateDir = context.getFilesDir(); } updateFile = new File(updateDir.getPath(), context.getResources() .getString(R.string.app_name) + ".apk"); if (updateFile.exists()) { Log.d("update", "升級包存在,刪除升級包"); updateFile.delete(); } else { Log.d("update", "升級包不存在,不用刪除升級包"); } } }
接下最後我們來看下服務吧UpdateService .java:
/** * 不要忘記註冊,在mainfest文件中 */ public class UpdateService extends Service { // BT字節參考量 private static final float SIZE_BT = 1024L; // KB字節參考量 private static final float SIZE_KB = SIZE_BT * 1024.0f; // MB字節參考量 private static final float SIZE_MB = SIZE_KB * 1024.0f; private final static int DOWNLOAD_COMPLETE = 1;// 完成 private final static int DOWNLOAD_NOMEMORY = -1;// 內存異常 private final static int DOWNLOAD_FAIL = -2;// 失敗 private String appName = null;// 應用名字 private String appUrl = null;// 應用升級地址 private File updateDir = null;// 文件目錄 private File updateFile = null;// 升級文件 // 通知欄 private NotificationManager updateNotificationManager = null; private Notification updateNotification = null; private Intent updateIntent = null;// 下載完成 private PendingIntent updatePendingIntent = null;// 在下載的時候 @Override public IBinder onBind(Intent arg0) { return null; } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); appName = intent.getStringExtra("appname"); appUrl = intent.getStringExtra("appurl"); updateNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); updateNotification = new Notification(); //通知圖標 updateNotification.icon = R.mipmap.head; //通知信息描述 updateNotification.tickerText = "正在下載 " + appName; updateNotification.when = System.currentTimeMillis(); updateIntent = new Intent(this, MyApplication.class); updatePendingIntent = PendingIntent.getActivity(this, 0, updateIntent, 0); updateNotification.contentIntent = updatePendingIntent; updateNotification.contentIntent.cancel(); updateNotification.contentView = new RemoteViews(getPackageName(), //這個佈局很簡單,就是一個圖片和兩個textview,分別是正在下載和下載進度 R.layout.download_notification); updateNotification.contentView.setTextViewText( R.id.download_notice_name_tv, appName + " 正在下載"); updateNotification.contentView.setTextViewText( R.id.download_notice_speed_tv, "0MB (0%)"); updateNotificationManager.notify(0, updateNotification); new UpdateThread().execute(); } /** * 在這裏使用了asynctask異步任務來下載 */ class UpdateThread extends AsyncTask<Void, Void, Integer> { @Override protected Integer doInBackground(Void... params) { return downloadUpdateFile(appUrl); } @Override protected void onPostExecute(Integer result) { super.onPostExecute(result); if (result == DOWNLOAD_COMPLETE) { Log.d("update", "下載成功"); String cmd = "chmod 777 " + updateFile.getPath(); try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { e.printStackTrace(); } Uri uri = Uri.fromFile(updateFile); //安裝程序 Intent installIntent = new Intent(Intent.ACTION_VIEW); installIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); installIntent.setDataAndType(uri, "application/vnd.android.package-archive"); updatePendingIntent = PendingIntent.getActivity( UpdateService.this, 0, installIntent, 0); updateNotification.contentIntent = updatePendingIntent; updateNotification.contentView.setTextViewText( R.id.download_notice_speed_tv, getString(R.string.update_notice_finish)); updateNotification.tickerText = appName + "下載完成"; updateNotification.when = System.currentTimeMillis(); updateNotification.defaults = Notification.DEFAULT_SOUND; updateNotification.flags |= Notification.FLAG_AUTO_CANCEL; updateNotificationManager.notify(0, updateNotification); //啓動安裝程序 UpdateService.this.startActivity(installIntent); stopSelf(); } else if (result == DOWNLOAD_NOMEMORY) { //如果內存有問題 updateNotification.tickerText = appName + "下載失敗"; updateNotification.when = System.currentTimeMillis(); updateNotification.contentView.setTextViewText( R.id.download_notice_speed_tv, getString(R.string.update_notice_nomemory)); updateNotification.flags |= Notification.FLAG_AUTO_CANCEL; updateNotification.defaults = Notification.DEFAULT_SOUND; updateNotificationManager.notify(0, updateNotification); stopSelf(); } else if (result == DOWNLOAD_FAIL) { //下載失敗 updateNotification.tickerText = appName + "下載失敗"; updateNotification.when = System.currentTimeMillis(); updateNotification.contentView.setTextViewText( R.id.download_notice_speed_tv, getString(R.string.update_notice_error)); updateNotification.flags |= Notification.FLAG_AUTO_CANCEL; updateNotification.defaults = Notification.DEFAULT_SOUND; updateNotificationManager.notify(0, updateNotification); stopSelf(); } } } /** * 下載更新程序文件 * @param downloadUrl 下載地址 * @return */ private int downloadUpdateFile(String downloadUrl) { int count = 0; long totalSize = 0; //總大小 long downloadSize = 0; //下載的大小 URI uri = null; //這個已經捨棄了,要用的話,就要加上org.apache.http.legacy.jar這個jar包 HttpGet httpGet = null; try { uri = new URI(downloadUrl); httpGet = new HttpGet(uri); } catch (URISyntaxException e) { String encodedUrl = downloadUrl.replace(' ', '+'); httpGet = new HttpGet(encodedUrl); e.printStackTrace(); } HttpClient httpClient = new DefaultHttpClient(); HttpResponse httpResponse = null; FileOutputStream fos = null; InputStream is = null; try { httpResponse = httpClient.execute(httpGet); if (httpResponse != null) { int stateCode = httpResponse.getStatusLine().getStatusCode(); if (stateCode == HttpStatus.SC_OK) { HttpEntity entity = httpResponse.getEntity(); if (entity != null) { totalSize = entity.getContentLength(); //如果內存可用 if (MemoryAvailable(totalSize)) { is = entity.getContent(); if (is != null) { fos = new FileOutputStream(updateFile, false); byte buffer[] = new byte[4096]; int readsize = 0; while ((readsize = is.read(buffer)) > 0) { fos.write(buffer, 0, readsize); downloadSize += readsize; if ((count == 0) || (int) (downloadSize * 100 / totalSize) >= count) { count += 5; updateNotification.contentView .setTextViewText( R.id.download_notice_speed_tv, getMsgSpeed(downloadSize,totalSize)); updateNotificationManager.notify(0, updateNotification); } } fos.flush(); if (totalSize >= downloadSize) { return DOWNLOAD_COMPLETE; } else { return DOWNLOAD_FAIL; } } } else { if (httpGet != null) { httpGet.abort(); } return DOWNLOAD_NOMEMORY; } } } } } catch (Exception e) { e.printStackTrace(); } finally { try { if (fos != null) { fos.close(); } if (is != null) { is.close(); } } catch (IOException e) { e.printStackTrace(); } if (httpClient != null) { httpClient.getConnectionManager().shutdown(); } } return DOWNLOAD_FAIL; } /** * 可用內存大小 * @param fileSize * @return */ private boolean MemoryAvailable(long fileSize) { fileSize += (1024 << 10); if (MemoryStatus.externalMemoryAvailable()) { if ((MemoryStatus.getAvailableExternalMemorySize() <= fileSize)) { if ((MemoryStatus.getAvailableInternalMemorySize() > fileSize)) { createFile(false); return true; } else { return false; } } else { createFile(true); return true; } } else { if (MemoryStatus.getAvailableInternalMemorySize() <= fileSize) { return false; } else { createFile(false); return true; } } } /** * 獲取下載進度 * @param downSize * @param allSize * @return */ public static String getMsgSpeed(long downSize, long allSize) { StringBuffer sBuf = new StringBuffer(); sBuf.append(getSize(downSize)); sBuf.append("/"); sBuf.append(getSize(allSize)); sBuf.append(" "); sBuf.append(getPercentSize(downSize, allSize)); return sBuf.toString(); } /** * 獲取大小 * @param size * @return */ public static String getSize(long size) { if (size >= 0 && size < SIZE_BT) { return (double) (Math.round(size * 10) / 10.0) + "B"; } else if (size >= SIZE_BT && size < SIZE_KB) { return (double) (Math.round((size / SIZE_BT) * 10) / 10.0) + "KB"; } else if (size >= SIZE_KB && size < SIZE_MB) { return (double) (Math.round((size / SIZE_KB) * 10) / 10.0) + "MB"; } return ""; } /** * 獲取到當前的下載百分比 * @param downSize 下載大小 * @param allSize 總共大小 * @return */ public static String getPercentSize(long downSize, long allSize) { String percent = (allSize == 0 ? "0.0" : new DecimalFormat("0.0") .format((double) downSize / (double) allSize * 100)); return "(" + percent + "%)"; } /** * 創建file文件 * @param sd_available sdcard是否可用 */ private void createFile(boolean sd_available) { if (sd_available) { updateDir = new File(Environment.getExternalStorageDirectory(), UpdateInformation.downloadDir); } else { updateDir = getFilesDir(); } updateFile = new File(updateDir.getPath(), appName + ".apk"); if (!updateDir.exists()) { updateDir.mkdirs(); } if (!updateFile.exists()) { try { updateFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } else { updateFile.delete(); try { updateFile.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } } }
當然了哈,這裏我寫的還是有點問題的,每次進入都會提示,如果有必要,也可以實現是否要自動更新,用服務,也就是點擊是否自動更新,如果不是自動更新,就不會去觸發服務端接口信息,如果是自動更新,就去觸發,來獲取最新的app版本。 -