标签:android ota downloadmanager 系统升级 在线更新
对之前做的OTA系统升级项目做一个总结,包括4个部分:OTA系统的介绍,OTA包的制作,代码结构以及待改善的问题。
1. OTA介绍:
OTA 全称 over the air , OTA 升级是 Android 系统提供的标准软件升级方式。 它功能强大,提供了完全升级、增量升级模式,可以通过 SD 卡升级,也可以通过网络升级。在系统升级中,主要要做的就是在本地编译出完整包和差分包,放到服务器供用户选择。
2.
OTA包的制作:
完整包就是变异整个系统生成的OTA包,大小可能在几百M左右,但是它相对于OTA差分包来说更加的稳定,差分包体积比较小,升级比较方便,这个就看用户自己的选择。在linux下,完整包的生成方法是:make clean; make; make otapackage; 之后会在out/target/product/torsby 生成一个zip包:vargo_torsby-ota.zip,这就是一个完整包可以直接拿去升级。同时,也在out/target/product/torsby/obj/PACKAGING/target_files_intermediates这个目录生成一个用来编译差分包的包,我们可以先重命名为old.zip,然后把第二次的包命名为new.zip, 接下来就可以来生成差分包,在 build/tools/releasetools 目录下有个ota_from_target_files的系统自带脚本,在linux下:./build/tools/releasetools/ota_from_target_files -i ~/old.zip ~/new.zip ~/update.zip,就会在当前目录生成update.zip的差分包 , 注意要把两个ota包放在当前目录执行这句命令。那么这里的update.zip差分包必须在old.zip这个系统上升级,才能到new.zip这个版本。
3. 项目结构:
整个项目的的功能是用户从设置进入系统升级后,会自动请求服务器检查是否有版本需要更新,如果没有则进入一个提示界面:您的系统已经是最新!如果不是最新系统,那么会在界面显示当前系统版本号和最新的系统版本号,以及更多里面的版本更新日志,用户点击立即安装就会进入一个版本列表,上面是服务器返回的所有可更细版本,选择一个版本就可以进行安装更新。
代码的核心类就是 IradarUpdateSystemFragment.class, 他继承自PreferenceFragment 是为了和设置Settings的UI设计保持同步,然后它归属于IradarUpdateSystemActivity,所以真正的代码实现就在这个fragment中。在onCreate()方法中,首先进行actionBar和Preference的初始化,紧接着使用公司自己封装的网络框架RequestManager 来请求服务器获得最新版本,在这里要注意一点: 在使用RequestManager请求服务器之前要先初始化:
Options opts = new Options.Builder().enableNet().enablePush().build(); VargoHelper.Init(this,opts);
在这里还有一个就是”了解更多“这个Preference,是用来看更新日志的:点击后跳转到UpdateLogActivity, 他是一个窗口Activity的实现,用WebView.loadUrl()的方法加载一份更新日志。
那么界面更新完成之后,假如当前有版本可以更新,用户点击现在安装就会弹出一个版本列表,在此之前会有一个WIFI和电量判断,我们规定是必须连接WIFI并且电量不低于50%的情况下才能继续更新,检查WIFI是否开启的代码:
// 检查当前网络是否为WIFI private Boolean isWifiNet(){ ConnectivityManager connectionManager = (ConnectivityManager)context.getSystemService(context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = connectionManager.getActiveNetworkInfo(); if(networkInfo==null){ return false; } else{ String netState = networkInfo.getTypeName(); if(netState.equals("WIFI")){ return true; } else return false; } }
// 直接获取现在的电量 private boolean getBattery(){ String s = ""; boolean isOk = false; try { fr = new FileReader(file); BufferedReader br = new BufferedReader(fr); if((s=br.readLine())!=null){ if(Integer.parseInt(s)>49){ isOk = true; Log.d(LOG_TAG, "当前电量是>>>>>>>>>"+s); } else{ isOk = false; } } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return isOk; }
当电量和WIFI都满足条件后就可以开始下载安装系统版本了。下载这一块我采用系统自带的DownloadManager框架,android原生系统就是用的这个框架,有已经封装好的通知栏,功能还是很完善,下面详述DownloadManager在本项目中的用法:
DownloadManager是一个下载管理类,在OnCreate()中获取:.
manger=(DownloadManager)getActivity().getSystemService(context.DOWNLOAD_SERVICE) ;
DownloadManager.Request down=new DownloadManager.Request (Uri.parse(url)); //down.addRequestHeader(header, value); down.setNotificationVisibility(android.app.DownloadManager.Request.VISIBILITY_VISIBLE); down.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI); down.setTitle(getActivity().getResources().getString(R.string.down_title)); if(getFile(OtaConstant.romName).exists()){ boolean isDelete = getFile(OtaConstant.romName).delete(); Log.d(LOG_TAG, "原来的update.zip删除?>>>>>>>"+ isDelete); } down.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, OtaConstant.romName); downloadId = manger.enqueue(down); SharedPreferences sharedPreferences = getActivity().getSharedPreferences("ota", Context.MODE_PRIVATE); //私有数据 Editor editor = sharedPreferences.edit();//获取编辑器 editor.putLong("downloadId", downloadId); editor.commit();//提交修改
context.getContentResolver().registerContentObserver(Uri.parse("content://downloads/my_downloads"), true,downloadObserver);
// 监听下载进度 class DownloadChangeObserver extends ContentObserver { public DownloadChangeObserver(){ super(handler); } @Override public void onChange(boolean selfChange) { updateView(); Log.d(LOG_TAG, "监听到 正在下载"); } } public void updateView() { SharedPreferences sharedPreferences = getActivity().getSharedPreferences("ota", Context.MODE_PRIVATE); downloadId = sharedPreferences.getLong("downloadId", -1L); Log.d(LOG_TAG, "重新进入OTA 继续下载id>>>>>>>"+downloadId); if(downloadId!=-1){ int[] bytesAndStatus = getBytesAndStatus(downloadId); handler.sendMessage(handler.obtainMessage(0,bytesAndStatus)); } } public int[] getBytesAndStatus(long downloadId) { int[] bytesAndStatus = new int[] { -1, -1, 0 ,0}; DownloadManager.Query query = new DownloadManager.Query().setFilterById(downloadId); Cursor c = null; try { c = manger.query(query); if (c != null && c.moveToFirst()) { bytesAndStatus[0] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)); bytesAndStatus[1] = c.getInt(c.getColumnIndexOrThrow(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)); bytesAndStatus[2] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); bytesAndStatus[3] = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_REASON)); Log.d(LOG_TAG, " COLUMN_STATUS>>>>>>>>>"+bytesAndStatus[2] + "COLUMN_REASON>>>>>>>>> "+bytesAndStatus[3] ); } } finally { if (c != null) { c.close(); } } return bytesAndStatus; }
还要处理的就是后台下载中,哪怕是关机重启,我重新进入软件,进度条要能够接着当前下载来显示。于是我在开始下载时马上用SharedPreferences保存了ID, 并且在开始一个下载前先清空了ID,在下次进入后就判断这个ID是否有值:有就说明是正在下载,没有数值(其实代码给的-1默认值)就是一次新下载。整个下载过程就是这样了,在实践中基本都能够顺利下载。完成后会在SD卡的DOWMLOAD文件夹中看到update.zip这个更新包,安装方法:
// 执行安装更新包 private void installRom(){ // 比对MD5 判断rom的完整性 Log.d(LOG_TAG, "下载的文件的md5是>>>>>>>>> "+Utils.getFileMD5(getFile(OtaConstant.romName))); SharedPreferences sp = context.getSharedPreferences("ota", 0); String romMd5 = sp.getString("md5", " "); Log.d(LOG_TAG, "服务器给定的md5是>>>>>>>>> "+romMd5); if(Utils.getFileMD5(getFile(OtaConstant.romName)).equalsIgnoreCase(romMd5)){ try { RecoverySystem.installPackage(context, getFile(OtaConstant.romName)); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } else{ Toast.makeText(getActivity(), getActivity().getResources().getString(R.string.system_broken), 1).show(); getActivity().finish(); } if(startInstall!=null){ startInstall.dismiss(); } }
OTA还有问题就是:我们不能指望用户在当前界面等待下载,完成安装,这个安装很有可能是在后台完成的,因此必须把这个安装过程放在service里面,就是代码中的InstallService这个类。但是后面的实践告诉我,仅仅把安装放在service是不够的,因为在下载过程中有可能失败,必须给出失败提示,所以整个下载进度过程都应该放在service里面, 这个是当时没有考虑清楚,那么其实fragment里面其实是不需要管下载和安装的,这部分代码还没有精简。还有一个就是手工安装和自动安装,项目要求是下载完成后15开始自动安装,或者用户直接点击马上安装,这部分用handler控制一下吧,加一个FLAG标注一下手动自动,不要让两个安装都进行了.下面是下载失败的自定义通知栏的实现:
<pre name="code" class="java">private void shwoNotify(Context context){ Log.d(LOG_TAG, "开始show 一个状态栏"); mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); RemoteViews view_custom = new RemoteViews(getPackageName(), R.layout.view_custom); view_custom.setImageViewResource(R.id.custom_icon, R.drawable.update); view_custom.setTextViewText(R.id.tv_custom_title, context.getResources().getString(R.string.service_prompt)); view_custom.setTextViewText(R.id.tv_custom_content, context.getResources().getString(R.string.system_broken)); mBuilder = new Builder(this); mBuilder.setContent(view_custom) .setWhen(System.currentTimeMillis()) .setContentIntent(getDefalutIntent(Notification.FLAG_AUTO_CANCEL)) .setWhen(System.currentTimeMillis())// .setTicker(context.getResources().getString(R.string.service_prompt)) .setPriority(Notification.PRIORITY_DEFAULT)// .setOngoing(false)// .setSmallIcon(R.drawable.update); Notification notify = mBuilder.build(); notify.contentView = view_custom; mNotificationManager.notify(notifyId, notify); Log.d(LOG_TAG, "结束show 一个状态栏"); } public PendingIntent getDefalutIntent(int flags){ Intent intent = new Intent(); intent.setClassName("cn.com.vargo.ota", "cn.com.vargo.ota.IradarUpdateSystemActivity"); PendingIntent pendingIntent= PendingIntent.getActivity(this, 1, intent, flags); return pendingIntent; }
public class Utils { /** * 计算文件的MD5 * @param file * @return */ public static String getFileMD5(File file) { if (!file.isFile()) { return ""; } MessageDigest digest = null; FileInputStream in = null; byte buffer[] = new byte[1024]; int len; try { digest = MessageDigest.getInstance("MD5"); in = new FileInputStream(file); while ((len = in.read(buffer, 0, 1024)) != -1) { digest.update(buffer, 0, len); } in.close(); } catch (Exception e) { e.printStackTrace(); return null; } BigInteger bigInt = new BigInteger(1, digest.digest()); return bigInt.toString(16).toUpperCase(); } /** * // 获取mac地址: * @param context * @return */ public static String getMacAddress(Context context) { String macAddress = "000000000000"; try { WifiManager wifiMgr = (WifiManager) context .getSystemService(Context.WIFI_SERVICE); WifiInfo info = (null == wifiMgr ? null : wifiMgr .getConnectionInfo()); if (null != info) { if (!TextUtils.isEmpty(info.getMacAddress())) macAddress = info.getMacAddress().replace(":", ""); else return macAddress; } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); return macAddress; } return macAddress; } }
4. 待解决:
从上面描述总结看出,还有几个问题待完善:
第一个,是DownloadManager支持断点续传,但是不知如何手动暂停。它具体在神马情况下给出暂停或失败状态,还不是很确定
第二个,就是OTA包的安装过程没有深入去研究,以及下载和安装应该全部移出fragment 只放在service。
标签:android ota downloadmanager 系统升级 在线更新
原文地址:http://blog.csdn.net/dengliulin/article/details/44174745