1.1.4 连接服务器获取更新信息
1.SplashActivity.java对应的核心业务代码
Public class SplashActivity extends Activity { private UpdateInfo info; private static final int GET_INFO_SUCCESS = 10; private static final int SERVER_ERROR = 11; private static final int SERVER_URL_ERROR = 12; private static final int PROTOCOL_ERROR = 13; private static final int IO_ERROR = 14; private static final int XML_PARSE_ERROR = 15; protected static final String TAG = "SplashActivity"; private long startTime; private RelativeLayout rl_splash; private long endTime; private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) {
case XML_PARSE_ERROR: Toast.makeText(getApplicationContext(), "xml解析错误",1).show(); break; case IO_ERROR: Toast.makeText(getApplicationContext(), "I/O错误", 1).show(); break; case PROTOCOL_ERROR: Toast.makeText(getApplicationContext(), "协议不支持", 1).show(); break; case SERVER_URL_ERROR: Toast.makeText(getApplicationContext(), "服务器路径不正确", 1).show(); break; case SERVER_ERROR: Toast.makeText(getApplicationContext(), "服务器内部异常", 1).show(); break; case GET_INFO_SUCCESS: String serverversion = info.getVersion(); String currentversion = getVersion(); if (currentversion.equals(serverversion)) { Log.i(TAG, "版本号相同进入主界面"); } else { Log.i(TAG, "版本号不相同,升级对话框"); showUpdateDialog();//显示升级对话框 } break; } }; }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //设置为无标题栏 requestWindowFeature(Window.FEATURE_NO_TITLE); //设置为全屏模式 getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); setContentView(R.layout.activity_splash); rl_splash = (RelativeLayout) findViewById(R.id.rl_splash); tv_splash_version = (TextView) findViewById(R.id.tv_splash_version); tv_splash_version.setText("版本号:" + getVersion()); AlphaAnimation aa = new AlphaAnimation(0.3f, 1.0f); aa.setDuration(2000); rl_splash.startAnimation(aa); //连接服务器获取服务器上的配置信息
new Thread(new CheckVersionTask()) { }.start(); } /** * 连网检查应用的版本号与服务端上的版本号是否相同* */ private class CheckVersionTask implements Runnable { public void run() { startTime = System.currentTimeMillis(); Message msg = Message.obtain(); try { //获取服务端的配置信息的连接地址 String serverurl = getResources().getString(R.string. serverurl); URL url = new URL(serverurl); HttpURLConnection conn = (HttpURLConnection) url .openConnection(); conn.setRequestMethod("GET");//设置请求方式 conn.setConnectTimeout(5000); int code = conn.getResponseCode();//获取响应码 if (code == 200) {//响应码为200时,表示与服务端连接成功 InputStream is = conn.getInputStream(); info = UpdateInfoParser.getUpdateInfo(is); endTime = System.currentTimeMillis(); long resulttime = endTime - startTime; if (resulttime < 2000) { try { Thread.sleep(2000 - resulttime); } catch (InterruptedException e) { e.printStackTrace(); } } msg.what = GET_INFO_SUCCESS; handler.sendMessage(msg); } else { //服务器状态错误 msg.what = SERVER_ERROR; handler.sendMessage(msg); endTime = System.currentTimeMillis(); long resulttime = endTime - startTime; if (resulttime < 2000) { try { Thread.sleep(2000 - resulttime); } catch (InterruptedException e) { e.printStackTrace(); } }
} } catch (MalformedURLException e) { e.printStackTrace(); msg.what = SERVER_URL_ERROR; handler.sendMessage(msg); } catch (ProtocolException e) { msg.what = PROTOCOL_ERROR; handler.sendMessage(msg); e.printStackTrace(); } catch (IOException e) { msg.what = IO_ERROR; handler.sendMessage(msg); e.printStackTrace(); } catch (XmlPullParserException e) { msg.what = XML_PARSE_ERROR; handler.sendMessage(msg); e.printStackTrace(); } } } /** * 显示升级提示的对话框 */ protected void showUpdateDialog() { //创建对话框的构造器 AlertDialog.Builder builder = new Builder(this); //设置对话框提示标题左边的提示图标 builder.setIcon(getResources().getDrawable(R.drawable.notification)); //设置对话框的标题 builder.setTitle("升级提示"); //设置对话框的提示内容 builder.setMessage(info.getDescription()); //设置升级按钮 builder.setPositiveButton("升级", new OnClickListener() { //设置取消按钮 public void onClick(DialogInterface dialog, int which) { }); builder.setNegativeButton("取消", new OnClickListener() { }); //创建并显示对话框 builder.create().show(); }
代码解析:
(1)rl_splash = (RelativeLayout) findViewById(R.id.rl_splash)找到Splash界面的根节点,为其播放动画做准备。
(2)为Splash界面播放一个动画。
其中,AlphaAnimation aa = new AlphaAnimation(0.3f, 1.0f)设置一个透明度由0.3f~1.0f的淡入的动画效果。0.0f表示完全透明,1.0f表示正常显示效果。
aa.setDuration(2000)为设置动画的执行时间(0.3f~1.0f),单位为毫秒。
rl_splash.startAnimation(aa)为启动动画。
(3)
new Thread(new CheckVersionTask()) { }.start()
由于连网的过程一般是一个耗时的操作,为了避免出现ANR异常,我们在主线程中开启一个子线程用于连网核对版本号信息。此时我们还应当在清单文件中配置网络权限信息:<uses-permission android:name="android.permission.INTERNET"/>。
(4)startTime = System.currentTimeMillis():用于记录子线程开始执行的时间。
(5)Message msg = Message.obtain():得到一个向主线程发送消息的消息对象。
(6)String serverurl =getResources().getString(R.string.serverurl):得到访问服务端配置信息(即服务端的info.xml文件)的URL地址。
该URL地址存放在values文件夹下的config.xml文件中(将请求服务端的URL存放在这里的目的在于:当这个 URL 需要变更时,不需要在代码中进行修改,只需要修改这个配置文件即可实现,降低了开发的成本),代配置信息如下:
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="serverurl">http://192.168.0.4:8080/info.xml</string> </resources>
(7)取得与服务器的连接。
URL url = new URL(serverurl)为使用serverurl建立URL类对象HttpURLConnection conn =(HttpURLConnection) url.openConnection()通过URL对象打开路径的链接,实际上这个类内部是使用HttpURLConnection来实现的,得到urlConnection对象conn.setConnectTimeout(5000),在conn对象中调用setConnectTimeout()方法,设置链接的超时时间。之所以要设置这个超时时间,是因为:如果请求时间比较长,比如要等 30 秒,那么这个程序就处在一个阻塞过程中,而Android组件也有一个阻塞时间,笔者没有精确计算过,但是建议最好不要超过6秒,超过6 秒时,Android 系统就会自动判断,只要超过了它的阻塞时间,不管你的程序是否有错,都会被系统回收。有些请求工作时间比较长的,千万不能在主线程中处理,主线程一旦被阻塞了,一定会被回收。碰到这种情况我们最好抛开主线程去做,而不要在主线程中处理。如果不是开发Android应用,而是J2SE应用,不设也无所谓,因为它没有超时这个说法。
(8)得到数据。
InputStream is = conn.getInputStream(),用conn对象调用getInputStream()方法得到服务端配置信息文件的输入流。
info = UpdateInfoParser.getUpdateInfo(is)将输入流传入并通过对XML的解析获取XML中的数据,并将获取的数据存放在info对象(UpdateInfo)中返回来。
(9)endTime = System.currentTimeMillis(),其中endTime表示的是从服务端返回数据时的当前时间。
(10)long resulttime = endTime - startTime,其中resulttime表示的是向服务端发送请求并得到对应的数据后的所用时间。
(11)
if (resulttime < 2000) { try{ Thread.sleep(2000-resulttime); }catch(InterruptedException e){ e.printStackTrace(); } }
(12)
private Handler handler=new Handler(){ public void handleMessage(android.os.Message msg){} }
该对象是为了接收子线程发送过来的消息(主线程与子线程进行通信),将子线程发送过来的消息在handleMessage(android.os.Message msg)方法中进行处理。
(13)msg.what = GET_INFO_SUCCESS:为该 msg 做一个标记,这样,在 Handler 的handleMessage(android.os.Message msg)的Switch可以获取到该标记,以便识别是哪个消息。
(14)handler.sendMessage(msg):通过 handler 对象向主线程中发送消息,然后在 Handler的handleMessage(android.os.Message msg)方法中可以处理该消息。
Splash界面播放动画的时间设置为2秒,在播放这个Splash动画的过程中也进行连网检查更新数据的操作,连网检查更新数据结束后,如果不需要更新数据,那么将直接跳转到主界面(MainActivity)。在网络较好的情况下,连网时间不会超过2秒(可能不到0.5秒),这时如果没有(11)的代码就会直接跳转到主界面,当然,如果连网检查更新的过程超过了2秒,就需要等到检查更新后进入主界面。
2.解析XML的业务方法
在解析服务端的 info.xml 文件时,需要创建出一个解析 XML 的业务方法。将该业务方法放在“com.guoshisp.mobilesafe.engine”包下的UpdateInfoParser类中。具体代码如下:
package com.guoshisp.mobilesafe.engine; import java.io.IOException; import java.io.InputStream;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.util.Xml; import com.guoshisp.mobilesafe.domain.UpdateInfo; /** * * 解析XML数据 * */ public class UpdateInfoParser { /** * @param is XML文件的输入流 * @return updateinfo的对象 * @throws XmlPullParserException * @throws IOException */ public static UpdateInfo getUpdateInfo(InputStream is) throws XmlPullParserException, IOException { //获得一个Pull解析的实例 XmlPullParser parser = Xml.newPullParser(); //将要解析的文件流传入 parser.setInput(is, "UTF-8"); //创建UpdateInfo实例,用于存放解析得到的XML中的数据,最终将该对象返回 UpdateInfo info = new UpdateInfo(); //获取当前触发的事件类型 int type = parser.getEventType(); //使用while循环,如果获得的事件码是文档结束,那么就结束解析 while (type != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG) {//开始元素 if ("version".equals(parser.getName())) {//判断当前元素是否 是读者需要检索的元素,下同 //因为内容也相当于一个节点,所以获取内容时需要调用parser对象的nextText() 方法才可以得到内容 String version = parser.nextText(); info.setVersion(version); } else if ("description".equals(parser.getName())) { String description = parser.nextText(); info.setDescription(description); } else if ("apkurl".equals(parser.getName())) { String apkurl = parser.nextText(); info.setApkurl(apkurl); } }
type = parser.next(); } return info; } }
代码解析:
(1)int type = parser.getEventType(),这是Pull解析器的第一个事件。读者可以看到,这个方法的返回值是int类型,Pull解析器返回的是一个数字,类似于一个信号。那么这些信号都代表什么意思呢?Pull解析器已经定义了这五个常量,而且对于事件,仅仅只有这五个,如下:
• XmlPullParser.START_DOCUMENT——开始解析;
• XmlPullParser.START_TAG——开始元素;
• XmlPullParser.TEXT——解析文本;
• XmlPullParser.END_TAG——结束元素;
• XmlPullParser.END_DOCUMENT——结束解析。
(2)parser.getEventType()触发了第一个事件,根据XML 的语法,也就是从它开始来解析文档。那么,怎样触发下一个事件呢?要通过parser中最重要的方法:parser.next()。
注意:该方法是有返回值的,在Pull触发下一个事件的同时,我们也获得该事件的“信号”,通过获得的信号进行switch操作。
3.实体数据封装
当解析完XML文件后,将解析出来的实体数据封装在“com.guoshisp.mobilesafe. domain”包下的UpdateInfo类中。具体代码如下:
package com.guoshisp.mobilesafe.domain; public class UpdateInfo { private String version;//服务端的版本号 private String description;//服务端的升级提示 private String apkurl;//服务端的apk下载地址 public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public String getDescription() { return description; } public void setDescription(String description) { this.description = description; }
public String getApkurl() { return apkurl; } public void setApkurl(String apkurl) { this.apkurl = apkurl; } }
测试运行:服务端的版本配置信息中的version设置为2.0(本地的apk的版本号是1.0,服务端的apk版本号修改为2.0),运行效果如图1-8所示,日志打印如图1-9所示。
图1-8
图1-9