由于Android项目开源所致,市面上出现了N多安卓软件市场。为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量。因此我们有必要给我们的Android应用增加自动更新的功能。
既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软件的版本信息:
|
|
|
|
|
|
|
在这里我使用的是XML文件,方便读取。由于XML文件内容比较少,因此可通过DOM方式进行文件的解析:
1 | public class ParseXmlService |
2 | { |
3 | public HashMap<String, String> parseXml(InputStream inStream) throws Exception |
4 | { |
5 | HashMap<String, String> hashMap = new HashMap<String, String>(); |
6 | |
7 | // 实例化一个文档构建器工厂 |
8 | DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); |
9 | // 通过文档构建器工厂获取一个文档构建器 |
10 | DocumentBuilder builder = factory.newDocumentBuilder(); |
11 | // 通过文档通过文档构建器构建一个文档实例 |
12 | Document document = builder.parse(inStream); |
13 | //获取XML文件根节点 |
14 | Element root = document.getDocumentElement(); |
15 | //获得所有子节点 |
16 | NodeList childNodes = root.getChildNodes(); |
17 | for ( int j = 0 ; j < childNodes.getLength(); j++) |
18 | { |
19 | //遍历子节点 |
20 | Node childNode = (Node) childNodes.item(j); |
21 | if (childNode.getNodeType() == Node.ELEMENT_NODE) |
22 | { |
23 | Element childElement = (Element) childNode; |
24 | //版本号 |
25 | if ( "version" .equals(childElement.getNodeName())) |
26 | { |
27 | hashMap.put( "version" ,childElement.getFirstChild().getNodeValue()); |
28 | } |
29 | //软件名称 |
30 | else if (( "name" .equals(childElement.getNodeName()))) |
31 | { |
32 | hashMap.put( "name" ,childElement.getFirstChild().getNodeValue()); |
33 | } |
34 | //下载地址 |
35 | else if (( "url" .equals(childElement.getNodeName()))) |
36 | { |
37 | hashMap.put( "url" ,childElement.getFirstChild().getNodeValue()); |
38 | } |
39 | } |
40 | } |
41 | return hashMap; |
42 | } |
43 | } |
通过parseXml()方法,我们可以获取服务器上应用的版本、文件名以及下载地址。紧接着我们就需要获取到我们手机上应用的版本信息:
1 | /** |
2 | * 获取软件版本号 |
3 | * |
4 | * @param context |
5 | * @return |
6 | */ |
7 | private int getVersionCode(Context context) |
8 | { |
9 | int versionCode = 0 ; |
10 | try |
11 | { |
12 | // 获取软件版本号, |
13 | versionCode = context.getPackageManager().getPackageInfo( "com.szy.update" , 0 ).versionCode; |
14 | } catch (NameNotFoundException e) |
15 | { |
16 | e.printStackTrace(); |
17 | } |
18 | return versionCode; |
19 | } |
通过该方法我们获取到的versionCode对应AndroidManifest.xml下android:versionCode。android:versionCode和android:versionName两个属性分别表示版本号,版本名称。versionCode是整数型,而versionName是字符串。由于versionName是给用户看的,不太容易比较大小,升级检查时,就可以检查versionCode。把获取到的手机上应用版本与服务器端的版本进行比较,应用就可以判断处是否需要更新软件。
处理流程
处理代码
1 | package com.szy.update; |
2 | |
3 | import java.io.File; |
4 | import java.io.FileOutputStream; |
5 | import java.io.IOException; |
6 | import java.io.InputStream; |
7 | import java.net.HttpURLConnection; |
8 | import java.net.MalformedURLException; |
9 | import java.net.URL; |
10 | import java.util.HashMap; |
11 | |
12 | import android.app.AlertDialog; |
13 | import android.app.Dialog; |
14 | import android.app.AlertDialog.Builder; |
15 | import android.content.Context; |
16 | import android.content.DialogInterface; |
17 | import android.content.Intent; |
18 | import android.content.DialogInterface.OnClickListener; |
19 | import android.content.pm.PackageManager.NameNotFoundException; |
20 | import android.net.Uri; |
21 | import android.os.Environment; |
22 | import android.os.Handler; |
23 | import android.os.Message; |
24 | import android.view.LayoutInflater; |
25 | import android.view.View; |
26 | import android.widget.ProgressBar; |
27 | import android.widget.Toast; |
28 | |
29 | /** |
30 | *@author coolszy |
31 | *@date 2012-4-26 |
32 | *@bloghttp://blog.92coding.com |
33 | */ |
34 | |
35 | public class UpdateManager |
36 | { |
37 | /* 下载中 */ |
38 | private static final int DOWNLOAD = 1; |
39 | /* 下载结束 */ |
40 | private static final int DOWNLOAD_FINISH = 2; |
41 | /* 保存解析的XML信息 */ |
42 | HashMap<String, String> mHashMap; |
43 | /* 下载保存路径 */ |
44 | private String mSavePath; |
45 | /* 记录进度条数量 */ |
46 | private int progress; |
47 | /* 是否取消更新 */ |
48 | private boolean cancelUpdate = false; |
49 | |
50 | private Context mContext; |
51 | /* 更新进度条 */ |
52 | private ProgressBar mProgress; |
53 | private Dialog mDownloadDialog; |
54 | |
55 | private Handler mHandler = new Handler() |
56 | { |
57 | public void handleMessage(Message msg) |
58 | { |
59 | switch (msg.what) |
60 | { |
61 | // 正在下载 |
62 | case DOWNLOAD: |
63 | // 设置进度条位置 |
64 | mProgress.setProgress(progress); |
65 | break; |
66 | case DOWNLOAD_FINISH: |
67 | // 安装文件 |
68 | installApk(); |
69 | break; |
70 | default: |
71 | break; |
72 | } |
73 | }; |
74 | }; |
75 | |
76 | public UpdateManager(Context context) |
77 | { |
78 | this.mContext = context; |
79 | } |
80 | |
81 | /** |
82 | * 检测软件更新 |
83 | */ |
84 | public void checkUpdate() |
85 | { |
86 | if (isUpdate()) |
87 | { |
88 | // 显示提示对话框 |
89 | showNoticeDialog(); |
90 | } else |
91 | { |
92 | Toast.makeText(mContext, R.string.soft_update_no, Toast.LENGTH_LONG).show(); |
93 | } |
94 | } |
95 | |
96 | /** |
97 | * 检查软件是否有更新版本 |
98 | * |
99 | * @return |
100 | */ |
101 | private boolean isUpdate() |
102 | { |
103 | // 获取当前软件版本 |
104 | int versionCode = getVersionCode(mContext); |
105 | // 把version.xml放到网络上,然后获取文件信息 |
106 | InputStream inStream = ParseXmlService.class.getClassLoader().getResourceAsStream("version.xml"); |
107 | // 解析XML文件。 由于XML文件比较小,因此使用DOM方式进行解析 |
108 | ParseXmlService service = new ParseXmlService(); |
109 | try |
110 | { |
111 | mHashMap = service.parseXml(inStream); |
112 | } catch (Exception e) |
113 | { |
114 | e.printStackTrace(); |
115 | } |
116 | if (null != mHashMap) |
117 | { |
118 | int serviceCode = Integer.valueOf(mHashMap.get("version")); |
119 | // 版本判断 |
120 | if (serviceCode > versionCode) |
121 | { |
122 | return true; |
123 | } |
124 | } |
125 | return false; |
126 | } |
127 | |
128 | /** |
129 | * 获取软件版本号 |
130 | * |
131 | * @param context |
132 | * @return |
133 | */ |
134 | private int getVersionCode(Context context) |
135 | { |
136 | int versionCode = 0; |
137 | try |
138 | { |
139 | // 获取软件版本号,对应AndroidManifest.xml下android:versionCode |
140 | versionCode = context.getPackageManager().getPackageInfo("com.szy.update", 0).versionCode; |
141 | } catch (NameNotFoundException e) |
142 | { |
143 | e.printStackTrace(); |
144 | } |
145 | return versionCode; |
146 | } |
147 | |
148 | /** |
149 | * 显示软件更新对话框 |
150 | */ |
151 | private void showNoticeDialog() |
152 | { |
153 | // 构造对话框 |
154 | AlertDialog.Builder builder = new Builder(mContext); |
155 | builder.setTitle(R.string.soft_update_title); |
156 | builder.setMessage(R.string.soft_update_info); |
157 | // 更新 |
158 | builder.setPositiveButton(R.string.soft_update_updatebtn, new OnClickListener() |
159 | { |
160 | @Override |
161 | public void onClick(DialogInterface dialog, int which) |
162 | { |
163 | dialog.dismiss(); |
164 | // 显示下载对话框 |
165 | showDownloadDialog(); |
166 | } |
167 | }); |
168 | // 稍后更新 |
169 | builder.setNegativeButton(R.string.soft_update_later, new OnClickListener() |
170 | { |
171 | @Override |
172 | public void onClick(DialogInterface dialog, int which) |
173 | { |
174 | dialog.dismiss(); |
175 | } |
176 | }); |
177 | Dialog noticeDialog = builder.create(); |
178 | noticeDialog.show(); |
179 | } |
180 | |
181 | /** |
182 | * 显示软件下载对话框 |
183 | */ |
184 | private void showDownloadDialog() |
185 | { |
186 | // 构造软件下载对话框 |
187 | AlertDialog.Builder builder = new Builder(mContext); |
188 | builder.setTitle(R.string.soft_updating); |
189 | // 给下载对话框增加进度条 |
190 | final LayoutInflater inflater = LayoutInflater.from(mContext); |
191 | View v = inflater.inflate(R.layout.softupdate_progress, null); |
192 | mProgress = (ProgressBar) v.findViewById(R.id.update_progress); |
193 | builder.setView(v); |
194 | // 取消更新 |
195 | builder.setNegativeButton(R.string.soft_update_cancel, new OnClickListener() |
196 | { |
197 | @Override |
198 | public void onClick(DialogInterface dialog, int which) |
199 | { |
200 | dialog.dismiss(); |
201 | // 设置取消状态 |
202 | cancelUpdate = true; |
203 | } |
204 | }); |
205 | mDownloadDialog = builder.create(); |
206 | mDownloadDialog.show(); |
207 | // 现在文件 |
208 | downloadApk(); |
209 | } |
210 | |
211 | /** |
212 | * 下载apk文件 |
213 | */ |
214 | private void downloadApk() |
215 | { |
216 | // 启动新线程下载软件 |
217 | new downloadApkThread().start(); |
218 | } |
219 | |
220 | /** |
221 | * 下载文件线程 |
222 | * |
223 | * @author coolszy |
224 | *@date 2012-4-26 |
225 | *@ |
226 | */ |
227 | private class downloadApkThread extends Thread |
228 | { |
229 | @Override |
230 | public void run() |
231 | { |
232 | try |
233 | { |
234 | // 判断SD卡是否存在,并且是否具有读写权限 |
235 | if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) |
236 | { |
237 | // 获得存储卡的路径 |
238 | String sdpath = Environment.getExternalStorageDirectory() + "/"; |
239 | mSavePath = sdpath + "download"; |
240 | URL url = new URL(mHashMap.get("url")); |
241 | // 创建连接 |
242 | HttpURLConnection conn = (HttpURLConnection) url.openConnection(); |
243 | conn.connect(); |
244 | // 获取文件大小 |
245 | int length = conn.getContentLength(); |
246 | // 创建输入流 |
247 | InputStream is = conn.getInputStream(); |
248 | |
249 | File file = new File(mSavePath); |
250 | // 判断文件目录是否存在 |
251 | if (!file.exists()) |
252 | { |
253 | file.mkdir(); |
254 | } |
255 | File apkFile = new File(mSavePath, mHashMap.get("name")); |
256 | FileOutputStream fos = new FileOutputStream(apkFile); |
257 | int count = 0; |
258 | // 缓存 |
259 | byte buf[] = new byte[1024]; |
260 | // 写入到文件中 |
261 | do |
262 | { |
263 | int numread = is.read(buf); |
264 | count += numread; |
265 | // 计算进度条位置 |
266 | progress = (int) (((float) count / length) * 100); |
267 | // 更新进度 |
268 | mHandler.sendEmptyMessage(DOWNLOAD); |
269 | if (numread <= 0) |
270 | { |
271 | // 下载完成 |
272 | mHandler.sendEmptyMessage(DOWNLOAD_FINISH); |
273 | break; |
274 | } |
275 | // 写入文件 |
276 | fos.write(buf, 0, numread); |
277 | } while (!cancelUpdate);// 点击取消就停止下载. |
278 | fos.close(); |
279 | is.close(); |
280 | } |
281 | } catch (MalformedURLException e) |
282 | { |
283 | e.printStackTrace(); |
284 | } catch (IOException e) |
285 | { |
286 | e.printStackTrace(); |
287 | } |
288 | // 取消下载对话框显示 |
289 | mDownloadDialog.dismiss(); |
290 | } |
291 | }; |
292 | |
293 | /** |
294 | * 安装APK文件 |
295 | */ |
296 | private void installApk() |
297 | { |
298 | File apkfile = new File(mSavePath, mHashMap.get( "name" )); |
299 | if (!apkfile.exists()) |
300 | { |
301 | return ; |
302 | } |
303 | // 通过Intent安装APK文件 |
304 | Intent i = new Intent(Intent.ACTION_VIEW); |
305 | i.setDataAndType(Uri.parse( "file://" + apkfile.toString()), "application/vnd.android.package-archive" ); |
306 | mContext.startActivity(i); |
307 | } |
308 | } |