手机安全卫士03:获取更新的服务器配置,显示更新对话框
最后更新于:2022-04-01 16:15:58
### 配置应用程序在手机桌面显示的名称和图标-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](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95037b7d.jpg "image")](http://img.blog.csdn.net/20140926150151180) [![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9504848c.jpg "image")](http://img.blog.csdn.net/20140926150010078) ②[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9505c2d2.jpg "image")](http://img.blog.csdn.net/20140926150201164)
### 获取更新的服务器配置流程:
[![手机安全卫士](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a950a1ae9.jpg "手机安全卫士")](http://img.blog.csdn.net/20140926150020046)
### 服务器配置:
以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](http://localhost:18081/update.xml "http://localhost:18081/update.xml")
[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a950bc9cc.jpg "image")](http://img.blog.csdn.net/20140926150204066)
### 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](http://localhost:18081/update.xml "http://localhost: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系统异常的处理准则](http://blog.csdn.net/bruce_6/article/details/39579243)
#### 解析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,将我们的项目放在测试项目中。
[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a950d0f4a.jpg "image")](http://img.blog.csdn.net/20140926163408847)[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a950eef2f.jpg "image")](http://img.blog.csdn.net/20140926163227828)
2、将test项目中AndroidManifest.xml的<uses-library android:name="android.test.runner" />内容和<instrumentation>节点下的内容拷贝到项目的AndroidManifest.xml中,注意节点的对应。
[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95111481.jpg "image")](http://img.blog.csdn.net/20140926163411327)
之后,test项目便可以暂时不用了。
3、创建测试类
[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9512b83f.jpg "image")](http://img.blog.csdn.net/20140926163412388)
~~~
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](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a9514b590.jpg "image")](http://img.blog.csdn.net/20140926163433015)
[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95170790.jpg "image")](http://img.blog.csdn.net/20140928130533450)
[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a95185550.jpg "image")](http://img.blog.csdn.net/20140928130534605)
保存,即可。
5、运行测试代码:
[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a951af84d.jpg "image")](http://img.blog.csdn.net/20140928130535665)
出现异常!!!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](http://localhost:18081/update.xml "http://localhost:18081/update.xml")"; 修改成 String url = "[http://192.168.1.123:18081/update.xml](http://localhost:18081/update.xml "http://localhost:18081/update.xml")";
主机ip不能使用localhost或者127.0.0.1,使用本机真实ip地址即可。使用ipconfig命令就可以查看到:
[![image](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-02-18_56c5a951c926e.jpg "image")](http://img.blog.csdn.net/20140928130357812)
异常处理后,运行成功!
### 在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](http://blog.csdn.net/bruce_6/article/details/39640587)