🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
### 配置应用程序在手机桌面显示的名称和图标-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://box.kancloud.cn/2016-02-18_56c5a95037b7d.jpg "image")](http://img.blog.csdn.net/20140926150151180) [![image](https://box.kancloud.cn/2016-02-18_56c5a9504848c.jpg "image")](http://img.blog.csdn.net/20140926150010078)   ②[![image](https://box.kancloud.cn/2016-02-18_56c5a9505c2d2.jpg "image")](http://img.blog.csdn.net/20140926150201164) ### 获取更新的服务器配置流程: [![手机安全卫士](https://box.kancloud.cn/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://box.kancloud.cn/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://box.kancloud.cn/2016-02-18_56c5a950d0f4a.jpg "image")](http://img.blog.csdn.net/20140926163408847)[![image](https://box.kancloud.cn/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://box.kancloud.cn/2016-02-18_56c5a95111481.jpg "image")](http://img.blog.csdn.net/20140926163411327) 之后,test项目便可以暂时不用了。 3、创建测试类 [![image](https://box.kancloud.cn/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://box.kancloud.cn/2016-02-18_56c5a9514b590.jpg "image")](http://img.blog.csdn.net/20140926163433015) [![image](https://box.kancloud.cn/2016-02-18_56c5a95170790.jpg "image")](http://img.blog.csdn.net/20140928130533450) [![image](https://box.kancloud.cn/2016-02-18_56c5a95185550.jpg "image")](http://img.blog.csdn.net/20140928130534605) 保存,即可。 5、运行测试代码: [![image](https://box.kancloud.cn/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://box.kancloud.cn/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)