【边做项目边学Android】手机安全卫士03:获取更新的服务器配置,显示更新对话框

来源:互联网 发布:电力造价软件 编辑:程序博客网 时间:2024/06/06 00:45

配置应用程序在手机桌面显示的名称和图标-AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"    package="com.liuhao.mobilesafe"    android:versionCode="1"    android:versionName="1.0" >    <uses-sdk        android:minSdkVersion="8"        android:targetSdkVersion="19" />    <application        android:allowBackup="true"        android:icon="@drawable/ic_launcher"《在程序管理列表中显示的图标》 ①        android:label="@string/app_name"        android:theme="@style/AppTheme" >        <activity            android:icon="@drawable/icon5"《在桌面显示为自己配置的icon5图标》 ②            android:name="com.liuhao.mobilesafe.ui.SplashActivity"            android:label="@string/app_name" >《名称为自己配置的app_name》 ②            <intent-filter>                <action android:name="android.intent.action.MAIN" />                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>    </application></manifest> 
配置后,显示如图:

image image   ②image


获取更新的服务器配置流程:

手机安全卫士


服务器配置:

以tomcat作为服务器,在TOMCAT_HOME/ROOT目录下新建update.xml文件,在其中添加新版本的相关信息;

<?xml version="1.0" encoding="utf-8"?><info>    <version>2.0</version>    <description>亲,最新的版本,速度来下载!</description>    <apkurl>http://localhost:18081/newapk.apk</apkurl></info>
 在浏览器访问:http://localhost:18081/update.xml

image

 


xml配置文件的获取和解析

那么我们的应用程序启动时就要尝试到上述地址获取新版本的信息,同时对xml配置文件进行解析。

那么应用程序如何获取到上述的版本信息的地址呢?一般在资源文件中以配置文件的方式存储。

config.xml<?xml version="1.0" encoding="utf-8"?><resources>    <string name="updateurl">http://192.168.1.123:18081/update.xml</string></resources>

下面要建立update.xml文件对应的实体类-UpdateInfo.java:

package com.liuhao.mobilesafe.domain;/*** @author liuhao* 升级信息*/public class UpdateInfo {    String version;    String description;    String apkurl;    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;    }}


如何获取这个config.xml里url对应的文件内容(即http://192.168.1.123:18081/update.xml)?

新建更新信息服务类:UpdateInfoService.java:

package com.liuhao.mobilesafe.engine;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.URL;import android.content.Context;import com.liuhao.mobilesafe.domain.UpdateInfo;public class UpdateInfoService {    private Context context; // 应用程序环境的上下文信息    public UpdateInfoService(Context context) {        this.context = context;    }    /**     * @param urlId     *            服务器资源路径对应的id     * @return 更新信息     * @throws Exception     */    public UpdateInfo getUpdateInfo(int urlId) throws Exception {        String path = context.getResources().getString(urlId);// 根据urlId获取资源文件中对应的内容        URL url = new URL(path);               HttpURLConnection conn = (HttpURLConnection) url.openConnection();        conn.setReadTimeout(2000);        conn.setRequestMethod("GET");               InputStream is = conn.getInputStream(); //得到url对应的文件流,应该是xml文件流,需要对其进行解析               return UpdateInfoParser.getUpdateInfo(is);    }}

  • 知识点:为什么在业务类中不对异常进行捕获,而是直接抛出了?

向外传播给更高层处理,以便异常的错误原因不丢失,便于排查错误或进行捕获处理。对于异常处理,应该从设计、需要、维护等多个角度综合考虑,有一个通用准则:千万别捕获了异常什么事情都不干,这样一旦出现异常了,你没法依据异常信息来排错。

见:J2EE系统异常的处理准则


解析xml文件:

获取到xml文件流后,要对其进行解析,使用XmlPullParser

XmlPullParser将xml分解成不同的事件类型(EventType)

常用的有:
XmlPullParser.END_DOCUMENT:文档的结束
XmlPullParser.START_DOCUMENT:文档的开始
XmlPullParser.START_TAG:标签的开始
XmlPullParser.END_TAG:标签的结束
XmlPullParser.TEXT :内容

并且该类中的方法主要是用于获取EventType的内容,以及在EventType之间进行跳转。

创建解析更新信息的工具服务类UpdateInfoParser:

package com.liuhao.mobilesafe.engine;import java.io.InputStream;import org.xmlpull.v1.XmlPullParser;import android.util.Xml;import com.liuhao.mobilesafe.domain.UpdateInfo;public class UpdateInfoParser {    /**     * @param is xml格式的文件输入流     * @return 解析好的UpdateInfo     */    public static UpdateInfo getUpdateInfo(InputStream is) throws Exception{        XmlPullParser parser = Xml.newPullParser();        UpdateInfo info = new UpdateInfo();               // 初始化parser解析器,设置准备对哪个输入流进行解析        // 这个方法会对parser进行重置,同时会将事件类型(event type)定位到文档初始位置(START_DOCUMENT)        parser.setInput(is, "utf-8");               int type = parser.getEventType(); //获取当前的EventType        while(type != XmlPullParser.END_DOCUMENT){            switch (type) {            // 对其中的标签类型进行处理            case XmlPullParser.START_TAG:                if("version".equals(parser.getName())){                    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);                }                break;            }                       type = parser.next();        }        return info;    }   } 


测试

不知道Android如何测试?

1、新建一个Android Test Project,将我们的项目放在测试项目中。

imageimage

2、将test项目中AndroidManifest.xml的<uses-library android:name="android.test.runner" />内容和<instrumentation>节点下的内容拷贝到项目的AndroidManifest.xml中,注意节点的对应。

image

之后,test项目便可以暂时不用了。

3、创建测试类

image

package com.liuhao.mobilesafe.test;import junit.framework.Assert;import com.liuhao.mobilesafe.R;import com.liuhao.mobilesafe.domain.UpdateInfo;import com.liuhao.mobilesafe.engine.UpdateInfoService;import android.test.AndroidTestCase;public class TestGetUpdateInfo extends AndroidTestCase {    public void testGetInfo() throws Exception{        UpdateInfoService service = new UpdateInfoService(getContext());        UpdateInfo info = service.getUpdateInfo(R.string.updateurl);               Assert.assertEquals("2.0", info.getVersion());    }   } 

4、从服务器上获取更新信息的配置文件,需要程序有访问Internet的权限:

image

image

image

保存,即可。

5、运行测试代码:

image

出现异常!!!connect failed: ECONNREFUSED (Connection refused)


异常处理:java.net.ConnectException

android 从tomcat读取文件时出现以下异常:

08-10 14:53:09.118: W/System.err(12527): java.net.ConnectException: failed to connect to localhost/127.0.0.1 (port 8080): connect failed: ECONNREFUSED (Connection refused)

 

解决方法:

String url = "http://localhost:18081/update.xml";  修改成 String url = "http://192.168.1.123:18081/update.xml";

主机ip不能使用localhost或者127.0.0.1,使用本机真实ip地址即可。使用ipconfig命令就可以查看到:

image

异常处理后,运行成功!


在activity使用业务

所有的业务代码已经完成,回到splash的activity使用业务!

package com.liuhao.mobilesafe.ui;import com.liuhao.mobilesafe.R;import com.liuhao.mobilesafe.domain.UpdateInfo;import com.liuhao.mobilesafe.engine.UpdateInfoService;import android.os.Bundle;import android.app.Activity;import android.app.AlertDialog;import android.app.AlertDialog.Builder;import android.content.DialogInterface;import android.content.DialogInterface.OnClickListener;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.util.Log;import android.view.Menu;import android.view.Window;import android.view.WindowManager;import android.view.animation.AlphaAnimation;import android.widget.LinearLayout;import android.widget.TextView;import android.widget.Toast;public class SplashActivity extends Activity {private static final String TAG = "SplashActivity";private TextView tv_splash_version;private LinearLayout ll_splash_main;private UpdateInfo info;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);                //取消标题栏        requestWindowFeature(Window.FEATURE_NO_TITLE);                setContentView(R.layout.splash);                tv_splash_version = (TextView) this.findViewById(R.id.tv_splash_version);        ll_splash_main = (LinearLayout) this.findViewById(R.id.ll_splash_main);                String versiontext = getVersion();        tv_splash_version.setText(versiontext);                if(isNeedUpdate(versiontext)){        Log.i(TAG, "弹出升级对话框");        showUpdateDialog();        }                /* AlphaAnimation类:透明度变化动画类         * AlphaAnimation类是Android系统中的透明度变化动画类,用于控制View对象的透明度变化,该类继承于Animation类。         * AlphaAnimation类中的很多方法都与Animation类一致,该类中最常用的方法便是AlphaAnimation构造方法。         *          * public AlphaAnimation (float fromAlpha, float toAlpha)参数说明fromAlpha:开始时刻的透明度,取值范围0~1。toAlpha:结束时刻的透明度,取值范围0~1。         */        AlphaAnimation aa = new AlphaAnimation(0.0f, 1.0f);        aa.setDuration(2000); //Animation类的方法,设置持续时间         ll_splash_main.startAnimation(aa); //设置动画                 //完成窗体的全屏显示        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);    }    private void <span style="color:#FF6666;">showUpdateDialog</span>() {    //弹出一个消息框    <span style="color:#FF0000;">AlertDialog.Builder builder = new Builder(this);</span>    builder.setIcon(R.drawable.icon5); //设置消息框的标题图标    builder.setTitle("升级提醒"); //设置消息框的标题    builder.setMessage(info.getDescription()); //设置要显示的内容    builder.setCancelable(false); //让用户不能按后退键取消    builder.<span style="color:#FF6666;">setPositiveButton</span>("确定", new OnClickListener() { //设置用户选择确定时的按键操作@Overridepublic void onClick(DialogInterface dialog, int which) {Log.i(TAG, "下载pak文件:" + info.getApkurl());}});        builder.setNegativeButton("取消", new OnClickListener() {@Overridepublic void onClick(DialogInterface dialog, int which) {Log.i(TAG, "用户取消升级,进入程序主界面");}});        builder.create().show();    }/**     *      * @param versiontext 当前客户端的版本信息     * @return 是否需要更新     */    private boolean isNeedUpdate(String versiontext) {    UpdateInfoService service = new UpdateInfoService(this);    try {info = service.getUpdateInfo(R.string.updateurl);String version = info.getVersion();if(versiontext.equals(version)){Log.i(TAG, "版本号相同,无需升级,进入到主界面");return false;}else{Log.i(TAG, "版本号不同,需要升级");return true;}} catch (Exception e) {e.printStackTrace();/** * Toast使用场景 * 1、需要提示用户,但又不需要用户点击“确定”或者“取消”按钮。 * 2、不影响现有Activity运行的简单提示。 */Toast.makeText(this, "获取更新信息异常", 2).show();//弹出文本,并保持2秒Log.i(TAG, "获取更新信息异常,进入到主界面");return false;}    }@Override    public boolean onCreateOptionsMenu(Menu menu) {        // Inflate the menu; this adds items to the action bar if it is present.        getMenuInflater().inflate(R.menu.splash, menu);        return true;    }        /**     * 获取当前程序的版本号     * @return     */    private String getVersion(){    // 获取一个PackageManager的实例,从而可以获取全局包信息    PackageManager manager = getPackageManager();    try {    // Retrieve overall information about an application package that is installed on the system.PackageInfo info = manager.getPackageInfo(getPackageName(), 0);// The version name of this package, as specified by the <manifest> tag's versionName attribute.return info.versionName;} catch (Exception e) {e.printStackTrace();return "版本号未知";}        }    }


  • isNeedUpdate()方法:调用UpdateInfoService 的getUpdateInfo()方法,来获取更新信息。同时将服务器端的版本号和当前客户端的版本号进行对比,并做出是否让用户升级的操作。若发现两个版本号不一致,那么就要提醒用户进行升级:
  • 这里调用了showUpdateDialog()方法,在这个方法中,设置界面弹出一个消息框,其中有两个按钮:“确定”“取消”,用户点击不同的按钮则对应不同的操作。

异常处理android.os.NetworkOnMainThreadException--多线程问题

一切搞定,以为高枕无忧了,结果还是有问题!

log开始报错了,获取更新信息异常!!!debug一下,发现Exception:android.os.NetworkOnMainThreadException

这个异常大概意思是在主线程访问网络时出的异常。 Android在4.0之前的版本 支持在主线程中访问网络,但是在4.0以后对这部分程序进行了优化,也就是说访问网络的代码不能写在主线程中了。

处理方法:http://blog.csdn.net/bruce_6/article/details/39640587

0 0