今天重新写了一篇自动更新的文章,参考:http://aokunsang.iteye.com/blog/1750440。本篇文章的源码整理了下,上传到了附件,需要的去下载。
看了几个博客,讲自动升级的程序,但是感觉都不是很完整,因为项目需要,自己手动写了个自动更新的程序,备忘下(本篇博客源码不上传了,免得误入歧途,需要源码的去新写的博客下载)。
一、 需求:如下图流程所示,需要在后台检查APK是否需要升级,需要升级则弹出提示下载升级对话框,用户点击下载进行升级,然后自动安装。
软件下载流程图:
二、 思路:APK自动检查是否升级,这个当然需要在后台进行。因此需要使用异步线程操作,想到IntentService和AsyncTask。
三、选择原因:使用IntentService异步检查升级,但是无法提示弹出对话框;因此需要使用广播通知BroadcastReceiver。但是我想直接在异步类中直接弹出下载对话框,IntentService没有提供Context这样的参数;并且需要提供一个异步检查升级,一个异步下载,需要两个IntentService,而IntentService是可以执行多个任务的,客户端只需通过startService(Intent) 方法调用,那么intentService就一个接着一个的顺序来处理。那么我要是建立两个IntentService类,有点大材小用。那就使用AsyncTask类吧,每个任务启动一个新的asycnTask来工作,一个asyncTask只能使用一次。正好符合我的要求。
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..... }
/** * 检查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; }