Android项目实战:手机安全卫士开发案例解析
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

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