簡單實現安卓app自動更新功能

weixin_33686714發表於2018-01-03

簡單實現安卓自動更新

一般的安卓app都有自動更新功能,實現app的更新,以讓使用者體驗新版本的功能,這裡也是專案中用到的,今天就來總結一下,程式碼應該有點多,還請耐心點哈。 安卓應用實現自動更新比較簡單,這裡跟大家介紹下: ####第一步 伺服器端:

  • 服務端提供一個藉口,或者網址,我這裡就用的伺服器是tomcat,這裡提供一個網址如下:
//也就是一個json資料介面
  public static final String UPDATE_URL = "http://192.168.1.103:8080/update.json";
複製程式碼

我們來看下json資料引數:

{
//app名字
appname: "愛新聞1.1",
//伺服器版本號
serverVersion: "2",
//伺服器標誌
serverFlag: "1",
//是否強制更新
lastForce: "1",
//apk下載地址,這裡我已經下載了官方的apk,放到了伺服器裡面
updateurl: "http://192.168.1.103:8080/36Kr.apk",
//版本的更新的描述
upgradeinfo: "V1.1版本更新,你想不想要試一下哈!!!"
}
複製程式碼

好了以上的是伺服器端的資訊,在這裡不需要多說了,我們來看下客戶端的吧。 ####第二步 客戶端需要實現: 首先我們要去解析服務端給的json,那麼我們就要來建立一個model類了(程式碼過多,這裡只有欄位,getter和setter方法自己建立):

//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";// 下載目錄
}

複製程式碼

我們知道,我們在進入app的時候,這個時候如果檢測到伺服器端有了新的版本,就回彈出提示框,提示我們更新。這個我們在MainActivity裡面處理邏輯(onCreate()方法裡面):

 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:


/**
 * 版本更新升級 廣播接受者
 *
 */
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();
			}
		}
	}
}

複製程式碼

這個時候,可能看到服務怎麼這麼多程式碼啊,我頭都大了,不要著急,我們一步一步說明一下,這裡邏輯很簡單,就是在通知欄中,用到了通知,這個時候我們有三種情況,造成了我們好多程式碼的重複,(你也可以不必考慮那麼多情況),還有,裡面有了幾個工具類,沒有提取出來,分別是獲取sdcard大小是否可用(建立資料夾),獲取當前下載進度,獲取應用大小,下載檔案,這裡也可以使用第三方框架來下載。 裡面的重要的地方都有註釋,如果有疑問,可用互相討論一下。 這裡我們就簡單的上幾張圖看看吧: 提示更新圖:

更新下載通知:

下載完成後安裝圖:
最新版應用主介面圖(這裡我下載的是36kr官方的app,我在應用中心下載好的,嘿嘿):
當然了哈,這裡我寫的還是有點問題的,每次進入都會提示,如果有必要,也可以實現是否要自動更新,用服務,也就是點選是否自動更新,如果不是自動更新,就不會去觸發服務端介面資訊,如果是自動更新,就去觸發,來獲取最新的app版本。

相關文章