【边做项目边学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

时间: 2024-10-19 19:23:37

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

【边做项目边学Android】手机安全卫士01:splash界面ui

手机安全卫士项目是跟着黑马的视频做的. splash是飞洒.飞溅的意思,主要是用于完成一个产品logo显示,期间可以: 后台完成数据库初始化的操作 联网访问服务器,获取服务器最新信息(升级提示) 不同的日期显示出来不同logo,判断当前系统时间,素材一般从服务器上下载下来. 判断时间,根据不同时间显示不同的加载页面 布局文件:splash.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayou

【边做项目边学Android】手机安全卫士07-手机防盗之进入限制

上次写到在进入手机但·防盗界面时需要有密码限制,首先第一次进入时会弹出对话框提示用户设置密码:再次进入时会要求用户输入密码:这次来具体实现上述功能. 首次登录,设置密码 首先,我们的密码是保存在SharePreference中的"password"字段里的,在登录时后台需要校验该字段是否已经设置了密码,若未设置则弹出对话框让用户设置,否则要用户输入密码进入手机防盗界面: 校验是否设置了密码 @Override protected void onCreate(Bundle savedIn

【边做项目边学Android】手机安全卫士04_01:界面(Activity)之间的切换,Activity和任务栈

上一回说到,用户选择是否升级,若用户选择不升级,那么就要进入程序的主界面.下面要做的是从splash界面跳转到main界面. MainActivity创建 1.首先新建MainActivity: package com.liuhao.mobilesafe.ui; import com.liuhao.mobilesafe.R; import android.app.Activity; import android.os.Bundle; public class MainActivity exten

【边做项目边学Android】手机安全卫士09-手机防盗界面设置向导1

本次主要做手机防盗界面的设置向导功能界面的设计. 需求: 当用户进入手机防盗界面时,判断用户是否已经进行过设置向导: 如果用户已经设置过手机防盗,则不再提示用户进入手机向导 若还没有设置,则提示用户进入设置向导界面. 具体实现: 1.当用户输入"手机防盗"密码正确时,进行判断用户是否进行过设置向导 /** * 判断用户是否进行过设置向导 * @return */ private boolean isSetup(){ return sp.getBoolean("isAlread

【边做项目边学Android】手机安全卫士10-设置向导之绑定SIM卡

上回主要做了设置向导界面的界面设计,主要涉及到界面的布局和一些控件的使用.这次要做设置向导界面的功能具体实现. 首先,4个界面分别是(重复度很大,这里就不再贴到正文中了) /mobilesafe/res/layout/setup_wizard1.xml /mobilesafe/res/layout/setup_wizard2.xml /mobilesafe/res/layout/setup_wizard3.xml /mobilesafe/res/layout/setup_wizard4.xml

【边做项目边学Android】手机安全卫士05_1:程序主界面

主界面布局(知识点:GridView) mainscreen.xml: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height=

【边做项目边学Android】手机安全卫士08-一些布局和显示的细节:State List

我们注意到有些应用里的按钮在点击时的显示状态和普通状态是不一样的,比如: 普通状态下: 选中状态下: 那这种效果是如何实现的呢?在Android系统中提供给我们一种方便与实现这种功能的方法即:state list drawable. StateListDrawable是在XML中定义的drawable对象,我们可以通过设置不同item下的图片来显示不同状态,这取决于 drawable对象的状态.例如,一个Button控件可以有不同的状态(点击.聚焦等),通过一系列的drawable对象,可以为每

【边做项目边学Android】手机安全卫士11-设置向导之设置安全号码

这次主要实现设置安全号码的功能,即当发现手机SIM卡信息发生改变时,会自动给安全号码发送一条报警短信.主要包括选择联系人功能.涉及到的知识点包括:带返回值的Intent,ListView数据适配器. 选择联系人功能 界面 用ListView来对读取的联系人进行展示 /mobilesafe/res/layout/select_contact.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayou

【边做项目边学Android】手机安全卫士05_2:程序主界面,为每个条目添加事件

为每个条目添加点击事件监听器 gv_main.setOnItemClickListener(this); 需要当前Activity实现OnItemClickListener接口,同时实现public void onItemClick(AdapterView<?> parent, View view, int position,long id)方法 /** * 当gridview的条目被点击的时候对应的回调 * parent : gridView * view : 当前被点击条目的 Linear