这只是初步的实现,并没有加入自动编译等功能。需要手动更改更新的xml文件和最新的apk。
共涉及到四个文件!
一、客户端
androidupdatetestactivity:程序首页
main.xml:首页布局
update:更新类
softupdate_progress:更新等待界面
updage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
|
package majier.test; import java.io.file; import java.io.fileoutputstream; import java.io.ioexception; import java.io.inputstream; import java.net.httpurlconnection; import java.net.malformedurlexception; import java.net.url; import java.util.hashmap; import javax.xml.parsers.documentbuilder; import javax.xml.parsers.documentbuilderfactory; import org.w3c.dom.document; import org.w3c.dom.element; import org.w3c.dom.node; import org.w3c.dom.nodelist; import android.app.alertdialog; import android.app.dialog; import android.app.alertdialog.builder; import android.content.context; import android.content.dialoginterface; import android.content.intent; import android.content.dialoginterface.onclicklistener; import android.content.pm.packagemanager.namenotfoundexception; import android.net.uri; import android.os.environment; import android.os.handler; import android.os.message; import android.view.layoutinflater; import android.view.view; import android.widget.progressbar; import android.widget.toast; public class update { private static final int download = 1 ; private static final int download_finish = 2 ; private static final int connect_failed = 0 ; private static final int connect_success = 1 ; hashmap<string, string> mhashmap; private string msavepath; private int progress; private boolean cancelupdate = false ; private context mcontext; private progressbar mprogress; private dialog mdownloaddialog; private string mxmlpath; // 服务器更新xml存放地址 public update(context context, string xmlpath, string savepath) { this .mcontext = context; this .mxmlpath = xmlpath; this .msavepath = savepath; } private handler mhandler = new handler() { public void handlemessage(message msg) { switch (msg.what) { case download: mprogress.setprogress(progress); break ; case download_finish: installapk(); break ; default : break ; } }; }; /** * 检查更新 */ public void checkupdate() { new thread( new runnable() { @override public void run() { try { url url = new url(mxmlpath); httpurlconnection conn = (httpurlconnection) url .openconnection(); conn.setconnecttimeout( 5000 ); inputstream instream = conn.getinputstream(); mhashmap = parsexml(instream); message msg = new message(); msg.what = connect_success; handler.sendmessage(msg); } catch (exception e) { message msg = new message(); msg.what = connect_failed; handler.sendmessage(msg); } } }).run(); } /** * 访问服务器更新xml */ handler handler = new handler() { @override public void handlemessage(message msg) { super .handlemessage(msg); switch (msg.what) { case connect_failed: toast.maketext(mcontext, "访问服务器失败!" , toast.length_short).show(); break ; case connect_success: if ( null != mhashmap) { int servicecode = integer.valueof(mhashmap.get( "version" )); if (servicecode > getversioncode(mcontext)) { shownoticedialog(); } } break ; } } }; /** * 获取程序版本号 */ private int getversioncode(context context) { int versioncode = 0 ; try { versioncode = context.getpackagemanager().getpackageinfo( mcontext.getpackagename(), 0 ).versioncode; } catch (namenotfoundexception e) { e.printstacktrace(); } return versioncode; } /** * 是否更新提示窗口 */ private void shownoticedialog() { alertdialog.builder builder = new builder(mcontext); builder.settitle( "软件更新" ); builder.setmessage( "检测到新版本,是否更新?" ); builder.setpositivebutton( "更新" , new onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { dialog.dismiss(); showdownloaddialog(); } }); builder.setnegativebutton( "取消" , new onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { dialog.dismiss(); } }); dialog noticedialog = builder.create(); noticedialog.show(); } /** * 下载等待窗口 */ private void showdownloaddialog() { alertdialog.builder builder = new builder(mcontext); builder.settitle( "正在更新" ); final layoutinflater inflater = layoutinflater.from(mcontext); view v = inflater.inflate(r.layout.softupdate_progress, null ); mprogress = (progressbar) v.findviewbyid(r.id.update_progress); builder.setview(v); builder.setnegativebutton( "取消下载" , new onclicklistener() { @override public void onclick(dialoginterface dialog, int which) { dialog.dismiss(); cancelupdate = true ; } }); mdownloaddialog = builder.create(); mdownloaddialog.show(); downloadapk(); } /** * 涓嬭浇apk鏂囦欢 */ private void downloadapk() { new downloadapkthread().start(); } /** * 下载程序 */ private class downloadapkthread extends thread { @override public void run() { try { if (environment.getexternalstoragestate().equals( environment.media_mounted)) { url url = new url(mhashmap.get( "url" )); httpurlconnection conn = (httpurlconnection) url .openconnection(); conn.connect(); int length = conn.getcontentlength(); inputstream is = conn.getinputstream(); file file = new file(msavepath); if (!file.exists()) { file.mkdir(); } file apkfile = new file(msavepath, mhashmap.get( "name" )); fileoutputstream fos = new fileoutputstream(apkfile); int count = 0 ; byte buf[] = new byte [ 1024 ]; do { int numread = is.read(buf); count += numread; progress = ( int ) ((( float ) count / length) * 100 ); mhandler.sendemptymessage(download); if (numread <= 0 ) { mhandler.sendemptymessage(download_finish); break ; } fos.write(buf, 0 , numread); } while (!cancelupdate); fos.close(); is.close(); } } catch (malformedurlexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } mdownloaddialog.dismiss(); } }; /** * 安装apk */ private void installapk() { file apkfile = new file(msavepath, mhashmap.get( "name" )); if (!apkfile.exists()) { return ; } intent i = new intent(intent.action_view); i.setdataandtype(uri.parse( "file://" + apkfile.tostring()), "application/vnd.android.package-archive" ); mcontext.startactivity(i); } private hashmap<string, string> parsexml(inputstream instream) throws exception { hashmap<string, string> hashmap = new hashmap<string, string>(); // 实例化一个文档构建器工厂 documentbuilderfactory factory = documentbuilderfactory.newinstance(); // 通过文档构建器工厂获取一个文档构建器 documentbuilder builder = factory.newdocumentbuilder(); // 通过文档通过文档构建器构建一个文档实例 document document = builder.parse(instream); // 获取xml文件根节点 element root = document.getdocumentelement(); // 获得所有子节点 nodelist childnodes = root.getchildnodes(); for ( int j = 0 ; j < childnodes.getlength(); j++) { // 遍历子节点 node childnode = (node) childnodes.item(j); if (childnode.getnodetype() == node.element_node) { element childelement = (element) childnode; // 版本号 if ( "version" .equals(childelement.getnodename())) { hashmap.put( "version" , childelement.getfirstchild() .getnodevalue()); } // 软件名称 else if (( "name" .equals(childelement.getnodename()))) { hashmap.put( "name" , childelement.getfirstchild() .getnodevalue()); } // 下载地址 else if (( "url" .equals(childelement.getnodename()))) { hashmap.put( "url" , childelement.getfirstchild() .getnodevalue()); } } } return hashmap; } } |
androidupdatetestactivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package majier.test; import android.app.activity; import android.os.bundle; import android.os.environment; public class androidupdatetestactivity extends activity { /** called when the activity is first created. */ @override public void oncreate(bundle savedinstancestate) { super .oncreate(savedinstancestate); setcontentview(r.layout.main); update(); } private void update() { string sdpath = environment.getexternalstoragedirectory() + "/" ; string msavepath = sdpath + "boiler/" ; update updatemanager = new update( this , "http://localhost:8011/abcd.xml" , msavepath); updatemanager.checkupdate(); } } |
main.xml
1
2
3
4
5
6
7
8
9
10
11
12
|
<?xml version= "1.0" encoding= "utf-8" ?> <linearlayout xmlns:android= "http://schemas.android.com/apk/res/android" android:layout_width= "fill_parent" android:layout_height= "fill_parent" android:orientation= "vertical" > <textview android:layout_width= "fill_parent" android:layout_height= "wrap_content" android:text= "@string/hello" /> </linearlayout> |
softupdate_progress.xml
1
2
3
4
5
6
7
8
9
10
11
|
<?xml version= "1.0" encoding= "utf-8" ?> <linearlayout xmlns:android= "http://schemas.android.com/apk/res/android" android:layout_width= "fill_parent" android:layout_height= "wrap_content" > <progressbar android:id= "@+id/update_progress" android:layout_width= "fill_parent" android:layout_height= "wrap_content" style= "?android:attr/progressbarstylehorizontal" /> </linearlayout> |
每次生成新的apk前,需要修改系统的版本号。
修改version code 和version name。上面的代码可以看出,系统是根据version code来判断是否需要更新的。version name作为一个版本名称。
这里我建议version code从10开始,这样方面名称修改(1.1、1.2)。
修改完成后,生成系统。然后将apk文件放在服务端的文件下。
二、服务端
服务端主要是建立一个网址供用户下载apk。在iis上新建网站
http://localhost:8011/。将更新文件和更新的xml放在目录下。
version.xml格式
1
2
3
4
5
|
<update> <version> 12 </version> <name>boilerandroid_1. 1 </name> <url>http: //192.168.0.33:8011/boilerandroid.apk</url> </update> |
version对应着新程序的version code;
name随便起名;
url对应apk的下载路径。
在这里有可能会遇见一个问题,访问url路径时iis报错。主要是因为iis并不认识apk,不知道如何处理。
这里我们在iis中新增安卓程序的mime类型,来使apk支持下载。
在“iis管理器”中查看所建立的网站——mime类型——添加。
文件扩展名:.apk
mime类型:application/vnd.android.package-archive
这样就可以下载了。
目前只是一个简单的自动更新程序。我们可以看出,其中版本号需要自己填写,而且要与xml中的对应,apk需要生成后放在更新网址下。
这么的人为操作,很容易造成失误。因此,接下来我们要研究下自动发布更新版本,并且版本号与svn对应,在提交svn后,自动改变程序的版本号。