`
aokunsang
  • 浏览: 812455 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Android软件的自动更新【升级改革版】

 
阅读更多

 

1、之前写过一篇说版本升级的,用到广播。感觉乱用,搞的有点复杂,且混乱。现在又用到了版本升级功能,然后梳理下思路,使用回调接口重新写了个。

2、需求同http://aokunsang.iteye.com/blog/1487429,部分源码已上传。

3、增加了点小功能:

   1>、可以手动检查升级;

   2>、显示升级日志;

    3>、修改上篇博客潜在问题:

              问题:后台查询到更新,提示更新的AlertDialog只能在启动更新的页面弹出;如果离开此页面,抛异常。

              解决:在app的所有页面顶层弹出,参考代码:

 

//设置dialog
dialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
//加入权限
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

 4、截图如下:


 

 

5、上代码(代码才能说明一切):

(1)、DownloadCallback.java

 

/**
 * 下载数据接口
 * @author: aokunsang
 * @date: 2012-12-17
 */
public interface DownloadCallback {

	/**
	 * 下载前准备
	 */
	public void onDownloadPreare();
	/**
	 * 下载进度更新
	 * @param progress 进度值
	 */
	public void onChangeProgress(int progress);
	/**
	 * 下载完成
	 * @param success  下载成功标示
	 * @param errorMsg 下载失败显示内容
	 */
	public void onCompleted(boolean success,String errorMsg);
	/**
	 * 取消下载
	 */
	public boolean onCancel();
}

 (2)、DownloadInstall.java

 

/**
 * @author: aokunsang
 * @date: 2012-12-18
 */
public class DownloadInstall implements DownloadCallback {

	private Context mContext;
	private String apkPath,apkVersion;
	private int apkCode;
	private LayoutInflater inflater;
	
	private TextView textView;
	private ProgressBar progressView;
	private AlertDialog downloadDialog;    //下载弹出框
	private boolean interceptFlag = false;  //是否取消下载
	
	public DownloadInstall(Context mContext,String apkPath,String apkVersion,int apkCode) {
		this.mContext = mContext;
		this.apkCode = apkCode;
		this.apkPath = apkPath;
		this.apkVersion = apkVersion;
		inflater = LayoutInflater.from(mContext);
	}
	
	@Override
	public boolean onCancel() {
		return interceptFlag;
	}

	@Override
	public void onChangeProgress(int progress) {
		progressView.setProgress(progress);   //设置下载进度
		textView.setText("进度:"+progress+"%");
	}

	@Override
	public void onCompleted(boolean success, String errorMsg) {
		if(downloadDialog!=null){
			downloadDialog.dismiss();
		}
		if(success){  //更新成功
			alearyUpdateSuccess();
			installApk();
		}else{
			Toast.makeText(mContext, errorMsg, Toast.LENGTH_SHORT).show();
		}
	}

	@Override
	public void onDownloadPreare() {
		if(IntentUtil.checkSoftStage(mContext)){
			File file = new File(Const.apkSavepath);
			if(!file.exists()){
				file.mkdir();
			}
			Builder builder = new AlertDialog.Builder(mContext);
			builder.setIcon(R.drawable.upgrade).setTitle("正在更新版本");
			//---------------------------- 设置在对话框中显示进度条 --------------------
			View view = inflater.inflate(R.layout.upgrade_apk, null);
			textView = (TextView)view.findViewById(R.id.progressCount_text);
			textView.setText("进度:0");
			progressView = (ProgressBar)view.findViewById(R.id.progressbar);
			builder.setView(view);
			
			builder.setNegativeButton("取消", new DialogInterface.OnClickListener(){
				@Override
				public void onClick(DialogInterface dialog, int which) {
					dialog.dismiss();
					interceptFlag = true; 
				}
			});
			downloadDialog = builder.create();
			downloadDialog.show();
		}
	}
	
	/**
	 * 升级成功,更新升级日期和版本号,和版本code
	 */
	private void alearyUpdateSuccess(){
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
		SharedPreferences sharedPreference = mContext.getSharedPreferences(UpdateShared.SETTING_UPDATE_APK_INFO, 0);
		sharedPreference.edit().putString(UpdateShared.UPDATE_DATE, sdf.format(new Date()))
		.putString(UpdateShared.APK_VERSION, apkVersion).putInt(UpdateShared.APK_VERCODE, apkCode).commit();
	}
	/**
	 * 安装apk
	 */
	private void installApk(){ 
		File file = new File(apkPath);
		if(!file.exists()){
			return;
		}
		Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
		mContext.startActivity(intent);
	}
}

 (3)、DownloadAsyncTask.java

 

/**
 * 异步下载数据
 * @author: aokunsang
 * @date: 2012-12-17
 */
public class DownloadAsyncTask extends AsyncTask<String, Integer, String> {

	private DownloadCallback downCallBack;
	private HttpURLConnection urlConn;
	
	public DownloadAsyncTask(DownloadCallback downloadCallback){
		this.downCallBack = downloadCallback;
	}
	
	@Override
	protected void onPreExecute() {
		downCallBack.onDownloadPreare();
		super.onPreExecute();
	}
	
	@Override
	protected String doInBackground(String... args) {
		String apkDownloadUrl = args[0]; //apk下载地址
		String apkPath = args[1];   //apk在sd卡中的安装位置
		String result = "";
		if(!IntentUtil.checkURL(apkDownloadUrl)){
			result = "netfail";
		}else{

			InputStream is = null;
			FileOutputStream fos = null;
			try {
				URL url = new URL(apkDownloadUrl);
				urlConn = (HttpURLConnection)url.openConnection();
				is = urlConn.getInputStream();
				int length = urlConn.getContentLength();   //文件大小
				fos = new FileOutputStream(apkPath);
				
				int count = 0,numread = 0;
				byte buf[] = new byte[1024];
				
				while(!downCallBack.onCancel()&& (numread = is.read(buf))!=-1){
					count+=numread;
					int progressCount =(int)(((float)count / length) * 100);
					publishProgress(progressCount);
					fos.write(buf, 0, numread);
				}
				fos.flush();
				result = "success";
			} catch (Exception e) {
				e.printStackTrace();
				result = "fail";
			}finally{
				try {
					if(fos!=null)
						fos.close();
					if(is!=null)
						is.close();
				} catch (IOException e) {
					e.printStackTrace();
					result = "fail";
				}
			}
		}
		return result;
	}
	
	@Override
	protected void onProgressUpdate(Integer... values) {
		downCallBack.onChangeProgress(values[0]);
		super.onProgressUpdate(values);
	}
	
	@Override
	protected void onPostExecute(String result) {
		if(downCallBack.onCancel()){
			downCallBack.onCompleted(false, "版本更新下载已取消。");
		}else if("success".equals(result)){
			downCallBack.onCompleted(true, null);
		}else if("netfail".equals(result)){
			downCallBack.onCompleted(false, "连接服务器失败,请稍后重试。");
		}else{
			downCallBack.onCompleted(false, "版本更新失败,请稍后重试。");
		}
		super.onPostExecute(result);
	}
	
	@Override
	protected void onCancelled() {
		if(urlConn!=null){
			urlConn.disconnect();
		}
		super.onCancelled();
	}
}

 (4)、DownloadManager.java

 

/**
 * 下载管理
 * @author: aokunsang
 * @date: 2012-12-18
 */
public class DownloadManager{

	private Context mContext;
	
	final static int CHECK_FAIL = 0;
	final static int CHECK_SUCCESS = 1;
	final static int CHECK_NOUPGRADE = 2;
	final static int CHECK_NETFAIL = 3;
	
	private ApkInfo apkinfo;
	private AlertDialog noticeDialog;    //提示弹出框
	private ProgressDialog progressDialog;
	
	private boolean isAccord;  //是否主动检查软件升级
	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	
	
	public DownloadManager(Context mContext,boolean isAccord){
		this.mContext = mContext;
		this.isAccord = isAccord;
	}
	
	Handler handler = new Handler(){
		public void handleMessage(android.os.Message msg) {
			if(progressDialog!=null){
				progressDialog.dismiss();
			}
			switch(msg.what){
				case CHECK_SUCCESS:{
					showNoticeDialog();
					break;
				}
				case CHECK_NOUPGRADE:{  //不需要更新
					if(isAccord) Toast.makeText(mContext, "当前版本是最新版。", Toast.LENGTH_SHORT).show();
					break;
				}
				case CHECK_NETFAIL:{
					if(isAccord) Toast.makeText(mContext, "网络连接不正常。", Toast.LENGTH_SHORT).show();
					break;
				}
				case CHECK_FAIL:{
					if(isAccord) Toast.makeText(mContext, "从服务器获取更新数据失败。", Toast.LENGTH_SHORT).show();
					break;
				}
			}
		};
	};
	
	/* 检查下载更新 [apk下载入口] */
	public void checkDownload(){
		if(isAccord) progressDialog = ProgressDialog.show(mContext, "", "请稍后,正在检查更新...");
		new Thread() {
			@Override
			public void run() {
				if(!IntentUtil.isConnect(mContext)){ //检查网络连接是否正常
					handler.sendEmptyMessage(CHECK_NETFAIL);
				}else if(checkTodayUpdate() || isAccord){//判断今天是否已自动检查过更新 ;如果手动检查更新,直接进入  
					String result = HttpRequestUtil.getSourceResult(Const.apkCheckUpdateUrl, null, mContext);
					try {
						//从服务器下载数据有中文,所以服务器对数据进行了编码;在这里需要解码
						result = Escape.unescape(result);
						JSONObject obj = new JSONObject(result);
						String apkVersion = obj.getString("apkVersion");
						int apkCode = obj.getInt("apkVerCode");
						String apkSize = obj.getString("apkSize");
						String apkName = obj.getString("apkName");
						String downloadUrl = obj.getString("apkDownloadUrl");
						String apkLog = obj.getString("apklog");
						apkinfo = new ApkInfo(downloadUrl, apkVersion, apkSize, apkCode, apkName, apkLog);
						if(apkinfo!=null && checkApkVercode()){  //检查版本号
							alreayCheckTodayUpdate();    //设置今天已经检查过更新
							handler.sendEmptyMessage(CHECK_SUCCESS);
						}else{
							handler.sendEmptyMessage(CHECK_NOUPGRADE);
						}
					} catch (Exception e) {
						e.printStackTrace();
						handler.sendEmptyMessage(CHECK_FAIL);
					}
				}
			}
		}.start();
	}
	/* 弹出软件更新提示对话框*/
	private void showNoticeDialog(){
		StringBuffer sb = new StringBuffer();
		sb.append("版本号:"+apkinfo.getApkVersion()+"\n")
		.append("文件大小:"+apkinfo.getApkSize()+"\n")
		.append("更新日志:\n"+apkinfo.getApkLog());
		Builder builder = new AlertDialog.Builder(mContext);
		builder.setIcon(R.drawable.upgrade).setTitle("版本更新").setMessage(sb.toString());
		builder.setPositiveButton("下载", new DialogInterface.OnClickListener(){
			@Override
			public void onClick(DialogInterface dialog, int which) {
				String apkPath = Const.apkSavepath + apkinfo.getApkName();
				DownloadCallback downCallback = new DownloadInstall(mContext, apkPath, apkinfo.getApkVersion(), apkinfo.getApkCode());
				DownloadAsyncTask request = new DownloadAsyncTask(downCallback);
				request.execute(apkinfo.getDownloadUrl(),apkPath);
				dialog.dismiss();
			}
		});
		builder.setNegativeButton("以后再说", new DialogInterface.OnClickListener(){
			@Override
			public void onClick(DialogInterface dialog, int which) {
				dialog.dismiss();
			}
		});
		noticeDialog = builder.create();
		noticeDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);   //设置最顶层Alertdialog
		noticeDialog.show();
	}
	
	/**
	 * 根据日期检查是否需要进行软件升级
	 * @throws Exception 
	 */
	private boolean checkTodayUpdate() {
		SharedPreferences sharedPreference = mContext.getSharedPreferences(UpdateShared.SETTING_UPDATE_APK_INFO, 0);
		String checkDate = sharedPreference.getString(UpdateShared.CHECK_DATE, "");
		String updateDate = sharedPreference.getString(UpdateShared.UPDATE_DATE, "");
		if("".equals(checkDate) && "".equals(updateDate)){  //刚安装的新版本,设置详细信息
			int verCode = IntentUtil.getCurrentVersionCode(mContext);
			String versionName = IntentUtil.getCurrentVersionName(mContext);
			String dateStr = sdf.format(new Date());
			sharedPreference.edit().putString(UpdateShared.CHECK_DATE, dateStr)
			.putString(UpdateShared.UPDATE_DATE, dateStr)
			.putString(UpdateShared.APK_VERSION, versionName)
			.putInt(UpdateShared.APK_VERCODE, verCode).commit();
			return true;
		}
		try {
			//判断defaultMinUpdateDay天内不检查升级
			if((new Date().getTime()-sdf.parse(updateDate).getTime())/1000/3600/24<Const.defaultMinUpdateDay){
				return false;
			}else if(checkDate.equalsIgnoreCase(sdf.format(new Date()))){//判断今天是否检查过升级
				return false;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
	/**
	 * 设置今天已经检查过升级
	 * @return
	 */
	private void alreayCheckTodayUpdate(){
		String date = sdf.format(new Date());
		SharedPreferences sharedPreference = mContext.getSharedPreferences(UpdateShared.SETTING_UPDATE_APK_INFO, 0);
		sharedPreference.edit().putString(UpdateShared.CHECK_DATE, date).commit();
	}
	/**
	 * 检查版本是否需要更新
	 * @return
	 */
	private boolean checkApkVercode(){
		SharedPreferences sharedPreference = mContext.getSharedPreferences(UpdateShared.SETTING_UPDATE_APK_INFO, 0);
		int verCode = sharedPreference.getInt(UpdateShared.APK_VERCODE, 0);
		if(apkinfo.getApkCode()>verCode){
			return true;
		}else{
			return false;
		}
	}
	
	static interface UpdateShared{
	   String SETTING_UPDATE_APK_INFO = "cbt_upgrade_setting";
	   String UPDATE_DATE = "updatedate";
	   String APK_VERSION = "apkversion";
	   String APK_VERCODE = "apkvercode";
	   String CHECK_DATE = "checkdate";
	}
}
   

 代码内容我不讲解,使用的是回调接口。里面的各种检查是否升级,参考上一篇软件更新博客http://aokunsang.iteye.com/blog/1487429

(5)、其他操作类:

     a、Const.java是一个常量存储类,保存检查更新地址等;

     b、Escape.java是个URL解码编码类;

     c、HttpRequestUtil.java获取远程数据类;

     d、IntentUtil.java工具类,如:检查网络连接状态等。

     (以上类参考附件源码)

/**
 * apk更新信息
 * @author: aokunsang
 * @date: 2012-12-18
 */
public class ApkInfo implements Serializable {
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private String downloadUrl;  //下载地址
	private String apkVersion;  //apk版本
	private String apkSize;    //apk文件大小
	private int apkCode;   //apk版本号(更新必备)
	private String apkName;  //apk名字
	private String apkLog;   //apk更新日志
        
        setter and getter...
}

(6)、服务器代码(这里的apklog可以是个txt文档,在UI上下载展示,我做的比较简单):

 

/**
	 *  获取apk更新信息
	 * {apkVersion:'1.10',apkSize:'36K',apkVerCode:2,apkName:'1.1.apk',apkDownloadUrl:'http://localhost:8080/myapp/1.1.apk',apklog:'1、修改页面;\n2、修改字体'}  
	 */
	@Action(value="checkUpdateApk")
	public String updateApk(){
		
		ResourceLoader loader = new DefaultResourceLoader();
		Properties pp = null;
		try {
			InputStream is = loader.getResource("classpath:config/sysconf.properties").getInputStream();
			pp = new Properties();
			pp.load(new InputStreamReader(is, "utf-8"));
		} catch (Exception e) {
			e.printStackTrace();
		}
		if(pp!=null){
			String apkVersion = pp.getProperty("apkVersion");
			String apkDownloadUrl = pp.getProperty("apkDownloadUrl");
			String apkName = pp.getProperty("apkName");
			int apkVerCode = NumberUtils.toInt(pp.getProperty("apkVerCode"),0);
			String apklog = pp.getProperty("apkLog");
			String apkSize = pp.getProperty("apkSize");
			Httptear.ResponseResultByEscape("{apkVersion:'"+apkVersion+"',apkSize:'"+apkSize+"',apkVerCode:"+apkVerCode+",apkName:'"+apkName+"',apkDownloadUrl:'"+apkDownloadUrl+"',apklog:'"+apklog+"'}");
		}else{
			Httptear.ResponseResultByEscape("{}");
		}
		return NONE;
	}

    6、upgrade_apk.xml

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
	
    <TextView 
        android:id="@+id/progressCount_text"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:textColor="@color/white"
        android:textSize="14dip"
        />
    
	<ProgressBar 
	    android:id="@+id/progressbar"
	    style="?android:attr/progressBarStyleHorizontal"
		android:layout_width="fill_parent" 
		android:layout_height="wrap_content"
    />
</LinearLayout>
 

    7、需要加入以下权限:

 

    <!-- 在SD卡中创建和删除文件权限 -->
    <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
    <!-- 向SD卡中写入东西权限 -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>

    8、启动更新检查代码:

 

  /* 升级程序[主动] (因为会弹出一个ProgressDialog窗口,不能使用getApplicationContext())*/

DownloadManager downManger = new DownloadManager(this,true);
downManger.checkDownload();
     
/* 升级程序启动[被动](使用this引用会导致:如在1页面启动升级,当前页面为2页面,此时弹出Dialog抛异常)*/
DownloadManager downManger = new DownloadManager(getApplicationContext(),false);
downManger.checkDownload();
 
  • 大小: 89.8 KB
  • 大小: 86.7 KB
分享到:
评论
10 楼 aokunsang 2015-06-27  
一直被忽略 写道
博主,,,为什么我下载的源码,,是一个  login.html  文件?表示不理解。。。。。

这个不科学,重新下载试试。
9 楼 一直被忽略 2015-06-10  
博主,,,为什么我下载的源码,,是一个  login.html  文件?表示不理解。。。。。
8 楼 live_family 2015-04-29  
aokunsang 写道
live_family 写道
博主 我得手机系统升级到5.0.2 后 。测试程序得时候 用这种方法  就不会弹窗更新提示了  貌似是设置在app最顶层显示 失效了  博主有好的方法没?

这是很久以前做android项目时候的blog,现在已不做android很多年。

哦 谢谢
7 楼 aokunsang 2015-04-28  
live_family 写道
博主 我得手机系统升级到5.0.2 后 。测试程序得时候 用这种方法  就不会弹窗更新提示了  貌似是设置在app最顶层显示 失效了  博主有好的方法没?

这是很久以前做android项目时候的blog,现在已不做android很多年。
6 楼 live_family 2015-04-27  
博主 我得手机系统升级到5.0.2 后 。测试程序得时候 用这种方法  就不会弹窗更新提示了  貌似是设置在app最顶层显示 失效了  博主有好的方法没?
5 楼 freebsdqd 2015-03-19  
谢谢你的代码
4 楼 aokunsang 2014-04-23  
king_tt 写道
敢不敢把工程共享出来 。这样是不是太装x了啊 。

项目工程没法公开,只能根据该文章结合理解实现。如有问题,可发出来共同解决。
3 楼 king_tt 2014-04-16  
敢不敢把工程共享出来 。这样是不是太装x了啊 。
2 楼 aokunsang 2013-11-08  
chenwei7298 写道
我想问,能给一个工程不

亲,你下载下来放项目中不能使用么。  这就好比一个可插拔的控件,放项目中都能用的啊。
1 楼 chenwei7298 2013-11-07  
我想问,能给一个工程不

相关推荐

Global site tag (gtag.js) - Google Analytics