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

配置应用程序在手机桌面显示的名称和图标-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"<span style="color:#FF0000;">《在程序管理列表中显示的图标》 ①</span>
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:icon="@drawable/icon5"<span style="color:#FF0000;">《在桌面显示为自己配置的icon5图标》 ②</span>
            android:name="com.liuhao.mobilesafe.ui.SplashActivity"
            android:label="@string/app_name" ><span style="color:#FF0000;">《名称为自己配置的app_name》 ②</span>
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest> 
配置后,显示如图:

    ②


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


服务器配置:

以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

 


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,将我们的项目放在测试项目中。

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

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

3、创建测试类

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的权限:

保存,即可。

5、运行测试代码:

出现异常!!!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命令就可以查看到:

异常处理后,运行成功!


在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() { //设置用户选择确定时的按键操作
			
			@Override
			public void onClick(DialogInterface dialog, int which) {
				Log.i(TAG, "下载pak文件:" + info.getApkurl());
			}
		});
    	
    	builder.setNegativeButton("取消", new OnClickListener() {
			
			@Override
			public 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

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。