Android软件的自动更新

今天重新写了一篇自动更新的文章,参考:http: aokunsang iteye com blog 1750440。本篇文章的源码整理了下,上传到了附件,需

今天重新写了一篇自动更新的文章,参考:http://aokunsang.iteye.com/blog/1750440。本篇文章的源码整理了下,上传到了附件,需要的去下载。

 

   看了几个博客,讲自动升级的程序,但是感觉都不是很完整,因为项目需要,自己手动写了个自动更新的程序,备忘下(本篇博客源码不上传了,免得误入歧途,需要源码的去新写的博客下载)。

 

    一、 需求:如下图流程所示,需要在后台检查APK是否需要升级,需要升级则弹出提示下载升级对话框,用户点击下载进行升级,然后自动安装。



 

 软件下载流程图:


 

    二、 思路:APK自动检查是否升级,这个当然需要在后台进行。因此需要使用异步线程操作,想到IntentService和AsyncTask。

    三、选择原因:使用IntentService异步检查升级,但是无法提示弹出对话框;因此需要使用广播通知BroadcastReceiver。但是我想直接在异步类中直接弹出下载对话框,IntentService没有提供Context这样的参数;并且需要提供一个异步检查升级,一个异步下载,需要两个IntentService,而IntentService是可以执行多个任务的,客户端只需通过startService(Intent) 方法调用,那么intentService就一个接着一个的顺序来处理。那么我要是建立两个IntentService类,有点大材小用。那就使用AsyncTask类吧,每个任务启动一个新的asycnTask来工作,一个asyncTask只能使用一次。正好符合我的要求。

     四、执行顺序:时序图就不画了,说下类的执行流程吧。
                              1>mainActivity(主UI),
                              2>UpdateReceiver(更新广播通知),
                              3>CheckUpdateAsyncTask(检查更新),
                              4>UpdateAsyncTask(下载APK)
进入mainActivity,注册UpdateReceiver,同时执行CheckUpdateAsyncTask;检查完更新,由CheckUpdateAsyncTask广播通知UpdateReceiver,
UpdateReceiver的onReceive(Context,Intent)接收广播,并且启动UpdateAsyncTask下载。
     五、代码展示:
 1、mainActivity.java
public class MainActivity extends Activity {
@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	        .............
		//注册一个广播
		IntentFilter intentFilter = new IntentFilter(UpdateReceiver.ACTION_PROCRESS);
		intentFilter.addCategory(Intent.CATEGORY_DEFAULT);   //添加一个Category属性,CheckUpdateAsyncTask发送广播时候也要添加该属性。保持遥相呼应
		receiver = new UpdateReceiver();
		registerReceiver(receiver, intentFilter);
		//启动后台异步执行检查更新
		CheckUpdateAsyncTask checkAsyncTask = new CheckUpdateAsyncTask(WholeMainActivity.this);
		checkAsyncTask.execute(10);
	}
}

  2、CheckUpdateAsyncTask.java

/**
 * 检查是否有更新
 * @author: aokunsang
 * @date: 2012-4-13
 */
public class CheckUpdateAsyncTask extends AsyncTask<Integer, Integer, String> {
	
	private Context mContext;
	private final static String NOTE = "亲,有最新的软件包,赶紧下载吧~";
	private final static String SETTING_UPDATE_APK_INFO = "setting_updateapkinfo";
	private final static String CHECK_DATE = "checkdate";
	private final static String UPDATE_DATE = "updatedate";
	private final static String APK_VERSION = "apkversion";
	private final static String APK_VERCODE = "apkvercode";
	
	private AlertDialog noticeDialog;    //提示弹出框
	private UpdateApkInfo apkInfo;
	
	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	
	public CheckUpdateAsyncTask(Context mContext){
		this.mContext= mContext;
	}
	
	@Override
	protected String doInBackground(Integer... params) {
		String result = "";
		//检查是否能够连接网络,根据日期判断是否需要进行更新
		if(checkTodayUpdate() && PeaceUtil.isNetworkAvailable(mContext)){
			getUpateApkInfo(); 
			if(apkInfo!=null && checkApkVersion()){  //检查版本号
				alreayCheckTodayUpdate();    //设置今天已经检查过更新
				result = "success";
			}else{
				Log.i("---------检查应用更新-------------", "从服务器获取下载数据失败或者该版本code不需要升级");
				result = "fail";
			}
		}else{
			Log.i("---------检查应用更新-------------", "无法连接网络或者根据日期判断不需要更新软件");
			result = "fail";
		}
		return result;
	}

	@Override
	protected void onCancelled() {
		// TODO Auto-generated method stub
		super.onCancelled();
	}

	@Override
	protected void onPostExecute(String result) {
		if("success".equals(result)){
			showNoticeDialog();
		}
		super.onPostExecute(result);
	}
	
	/**
	 * 弹出软件更新提示对话框
	 */
	private void showNoticeDialog(){
		Builder builder = new AlertDialog.Builder(mContext);
		builder.setTitle("软件版本更新").setMessage(NOTE);
		builder.setPositiveButton("下载", new DialogInterface.OnClickListener(){
			@Override
			public void onClick(DialogInterface dialog, int which) {
				Intent intent = new Intent();
				intent.setAction(UpdateReceiver.ACTION_PROCRESS);
				intent.addCategory(Intent.CATEGORY_DEFAULT);   //一定要添加这个属性,不然onReceive(Context,Intent)中的Context参数不等于mContext,并且报错
				intent.putExtra(UpdateReceiver.PARAM_IN, apkInfo);
				dialog.dismiss();
				mContext.sendBroadcast(intent);
			}
		});
		builder.setNegativeButton("以后再说", new DialogInterface.OnClickListener(){
			@Override
			public void onClick(DialogInterface dialog, int which) {
				dialog.dismiss();
			}
		});
		noticeDialog = builder.create();
		noticeDialog.show();
	}
	/**
	 * 获取升级APK详细信息
	 * {apkVersion:'1.10',apkVerCode:2,apkName:'1.1.apk',apkDownloadUrl:'http://localhost:8080/myapp/1.1.apk'}
	 * @return
	 */
	private void getUpateApkInfo(){
		
		String updateApkJson = NetWorkAction.getnetworkInfo(mContext, Const.checkUpdateApk, null);
		updateApkJson = Escape.unescape(updateApkJson);
		try {
			JSONObject obj = new JSONObject(updateApkJson);
			String apkVersion = obj.getString("apkVersion");
			int apkVerCode = obj.getInt("apkVerCode");
			String apkName = obj.getString("apkName");
			String apkDownloadUrl = obj.getString("apkDownloadUrl");
			apkInfo = new UpdateApkInfo(apkVersion, apkName, apkDownloadUrl, apkVerCode);
		} catch (JSONException e) {
			e.printStackTrace();
		}
	}
	/**
	 * 根据日期检查是否需要进行软件升级
	 * @throws Exception 
	 */
	private boolean checkTodayUpdate() {
		SharedPreferences sharedPreference = mContext.getSharedPreferences(SETTING_UPDATE_APK_INFO, 0);
		String checkDate = sharedPreference.getString(CHECK_DATE, "");
		String updateDate = sharedPreference.getString(UPDATE_DATE, "");
		Log.i("-------------------checkDate------------","检查时间:"+checkDate);
		Log.i("-------------------updateDate------------","最近更新软件时间:"+updateDate);
		if("".equals(checkDate) && "".equals(updateDate)){  //刚安装的新版本,设置详细信息
			int verCode = 0;
			String versionName = "";
			try {
				verCode = mContext.getPackageManager().getPackageInfo("com.peacemap.sl.jyg", 0).versionCode;
				versionName = mContext.getPackageManager().getPackageInfo("com.peacemap.sl.jyg", 0).versionName;
			} catch (NameNotFoundException e) {
				e.printStackTrace();
			}
			String dateStr = sdf.format(new Date());
			sharedPreference.edit().putString(CHECK_DATE, dateStr)
			.putString(UPDATE_DATE, dateStr)
			.putString(APK_VERSION, versionName)
			.putInt(APK_VERCODE, verCode).commit();
			return false;
		}
		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 boolean checkApkVersion(){
		SharedPreferences sharedPreference = mContext.getSharedPreferences(SETTING_UPDATE_APK_INFO, 0);
		int verCode = sharedPreference.getInt(APK_VERCODE, 0);
		if(apkInfo.getAplVerCode()>verCode){  //如果新版本Code大于系统更新后的Code,则升级
			return true;
		}else{
			return false;
		}
	}
	/**
	 * 设置今天已经检查过升级
	 * @return
	 */
	private void alreayCheckTodayUpdate(){
		String date = sdf.format(new Date());
		SharedPreferences sharedPreference = mContext.getSharedPreferences(SETTING_UPDATE_APK_INFO, 0);
		sharedPreference.edit().putString(CHECK_DATE, date).commit();
	}
}

   3.UpdateAsyncTask.java

/**
 * 异步更新软件
 * @author: aokunsang
 * @date: 2012-4-13
 */
public class UpdateAsyncTask extends AsyncTask<Integer, Integer, String> {

	private final static String SETTING_UPDATE_APK_INFO = "setting_updateapkinfo";
	private final static String UPDATE_DATE = "updatedate";
	private final static String APK_VERSION = "apkversion";
	private final static String APK_VERCODE = "apkvercode";
	
	private final static String savePath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + Const.apkSaveDir;
	
	private String fileName;
	
	private Context mContext;
	private ProgressBar progressView;      //进度条
	private TextView textView;
	private AlertDialog downloadDialog;    //下载弹出框
	
	private UpdateApkInfo apkInfo;   //APK更新的详细信息
	
	private boolean interceptFlag = false;  //是否取消下载
	private boolean sdExists = false;   //是否存在SD卡
	
	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
	
	public UpdateAsyncTask(Context mContext,UpdateApkInfo apkInfo) {
		this.mContext = mContext;
		this.apkInfo = apkInfo;
		if(apkInfo!=null){
			fileName = savePath + "/" + apkInfo.getApkName();
		}
	}
	
	/**
	 * 升级成功,更新升级日期和版本号,和版本code
	 */
	private void alearyUpdateSuccess(){
		SharedPreferences sharedPreference = mContext.getSharedPreferences(SETTING_UPDATE_APK_INFO, 0);
		sharedPreference.edit().putString(UPDATE_DATE, sdf.format(new Date()))
		.putString(APK_VERSION, apkInfo.getApkVersion()).putInt(APK_VERCODE, apkInfo.getAplVerCode()).commit();
	}
	
	/**
	 * 安装apk
	 */
	private void installApk(){ 
		File file = new File(fileName);
		if(!file.exists()){
			Log.i("---------软件更新之安装应用-------------", "找不到下载的软件");
			return;
		}
		Intent intent = new Intent(Intent.ACTION_VIEW);
		intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
		mContext.startActivity(intent);
	}
	/**
	 * 检测手机是否存在SD卡
	 */
	private boolean checkSoftStage(){
		if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){  //判断是否存在SD卡
			File file = new File(savePath);
			if(!file.exists()){
				file.mkdir();
			}
			sdExists = true;
			return true;
		}else{
			Toast.makeText(mContext, "检测到手机没有存储卡,请安装了内存卡后再升级。", Toast.LENGTH_LONG).show();
			return false;
		}
	}
	@Override
	protected void onPreExecute() {
		
		if(apkInfo!=null && checkSoftStage()){
			showDownloadDialog();
		}
		super.onPreExecute();
	}
	/**
	 * 弹出下载进度对话框
	 */
	private void showDownloadDialog(){
		Builder builder = new AlertDialog.Builder(mContext);
		builder.setTitle("正在更新版本");
		//---------------------------- 设置在对话框中显示进度条 ---------------------------------------
		final LayoutInflater inflater = LayoutInflater.from(mContext);
		View view = inflater.inflate(R.layout.updateprogressbar, 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();
	}
	
	@Override
	protected String doInBackground(Integer... params) {
		
		String result = "";
		if(apkInfo==null){
			result = "fail";
		}else if(!NetWorkAction.checkURL(apkInfo.getApkDownloadUrl())){   //检查apk的下载地址是否可用
			result = "netfail";
		}else if(apkInfo!=null && sdExists){
			InputStream is = null;
			FileOutputStream fos = null;
			File file = new File(savePath);
			if(!file.exists()){
				file.mkdirs();
			}
			try {
				URL url = new URL(apkInfo.getApkDownloadUrl());
				URLConnection urlConn = url.openConnection();
				is = urlConn.getInputStream();
				int length = urlConn.getContentLength();   //文件大小
				fos = new FileOutputStream(fileName);
				
				int count = 0,numread = 0;
				byte buf[] = new byte[1024];
				
				while(!interceptFlag && (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 onPostExecute(String result) {
		if(downloadDialog!=null){
			downloadDialog.dismiss();
		}
		if(!interceptFlag && "success".equals(result)){
			alearyUpdateSuccess();
			installApk();
		}else if("netfail".equals(result)){
			Toast.makeText(mContext, "连接服务器失败,请稍后重试。", Toast.LENGTH_LONG).show();
		}
		super.onPostExecute(result);
	}
	
	@Override
	protected void onProgressUpdate(Integer... values) {
		int count = values[0];
		progressView.setProgress(count);   //设置下载进度
		textView.setText("进度:"+count+"%");
		super.onProgressUpdate(values);
	}
}

  4、UpdateReceiver.java

/**
 * 升级广播通知
 * @author: aokunsang
 * @date: 2012-4-13
 */
public class UpdateReceiver extends BroadcastReceiver {
	
	public final static String ACTION_PROCRESS = "com.peacemap.sl.jyg.intent.action.ACTION_PROCRESS";
	public final static String PARAM_IN = "apkinfo";
	
	@Override
	public void onReceive(Context context, Intent intent) {
		//获取升级APK的详细信息
		UpdateApkInfo apkInfo = (UpdateApkInfo)intent.getExtras().getSerializable(PARAM_IN);
		//启动升级的异步进程
		UpdateAsyncTask asyncTask = new UpdateAsyncTask(context,apkInfo);
		asyncTask.execute(10);
	}
}

  注意:UpdateReceiver需要在AndroidManifest.xml中配置:

<!-- 广播 -->
<receiver android:name="com.peacemap.sl.jyg.receiver.UpdateReceiver" >
    <intent-filter>
    <!-- action的name值一定要和UpdateReceiver中的ACTION_PROCRESS一致 -->
    <action android:name="com.peacemap.sl.jyg.intent.action.ACTION_PROCRESS" />
    </intent-filter>
</receiver>

5.在下载的时候,有个下载进度对话框,需要一个XML或者用java代码写一个UI。

<?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>

6.其他实体类;

/**
 * 升级APK详细信息
 * @author: aokunsang
 * @date: 2012-4-13
 */
public class UpdateApkInfo implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private String apkVersion;     //apk版本号
	private String apkName;      //apk名字
	private String apkDownloadUrl;  //下载地址
	private int aplVerCode;    //apk升级标示

       setter和getter.....
}

  

注:Const.java是final类型,属于常用数据存储类。放置一些URL路径的。
7、服务器类方法(读取配置文件内容)
/**
	 * 检查apk是否可以升级
	 * {apkVersion:'1.10',apkVerCode:2,apkName:'1.1.apk',apkDownloadUrl:'http://localhost:8080/myapp/1.1.apk'}
	 * @return
	 */
	@Action(value="checkUpdateApk")
	public String checkApkUpdate(){
		
		Properties pp = new Properties();
		ResourceLoader loader = new DefaultResourceLoader();
		try {
			InputStream is = loader.getResource("classpath:config/sysconf.properties").getInputStream();
			pp.load(new InputStreamReader(is, "utf-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		String apkVersion = pp.getProperty("apkVersion");
		String apkDownloadUrl = pp.getProperty("apkDownloadUrl");
		String apkName = pp.getProperty("apkName");
		int apkVerCode = NumberUtils.toInt(pp.getProperty("apkVerCode"),0);
		
		String result = "{apkVersion:'"+apkVersion+"',apkVerCode:"+apkVerCode+",apkName:'"+apkName+"',apkDownloadUrl:'"+apkDownloadUrl+"'}";
		Httptear.ResponseResultByEscape(result);  //通过流写出去
//		Httptear.ResponseResult(result);
		return NONE;
	}