Android服务器——使用TomCat实现软件的版本检测,升级,以及下载更新进度!
Android服务器——使用TomCat实现软件的版本检测,升级,以及下载更新进度!
算下来,TomCat服务器已经写了很长一段时间了,一直说拿他来搞点事情,也一直没做,今天刚好有空,交流群还有人请教,就寻思着把一些相关性的原理和基础操作写下来,虽然我网络这一块还是不怎么扎实,嘿嘿,还记得我们怎么搭建的服务器吗?
地址:Android服务器——TomCat服务器的搭建
我们新建一个项目TomCatVersion
这边先来说一下原理,我们做的小例子也是十分的简单,一个首页,我们用Handler实现,然后同步检测当前版本号和系统的版本号对比,如果有升级则弹出提示框提示升级,点击确定开始下载apk,同时显示进度,等下载完成之后启动新下载的APK进行安装,如果点击取消,进入主页,如果没有升级,则直接进入主页,思路应该很清晰吧!那好,我们开始!
一,准备工作
1.搭建TomCat服务器
我们没有服务器,所以使用TomCat服务器模拟,不会搭建的请看Android服务器——TomCat服务器的搭建
2.新建一个IndexActivity类,并且在AndroidManifest.xml里注册并且设置为主入口
<activity android:name="com.lgl.tomcatversion.IndexActivity" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
3.准备一张首页的图片
可有可无,这里作为演示就去网上下载了一张
4.版本更新接口
也就是服务器的地址,我们这里也就直接自己写一段简单的json了
{ "versionName": "2.0", "versionCode": 1, "content": "修复多项bug!", "url": "http://localhost:192.168.1.101/lgl/TomCatVersion.apk" }
我们把他放在服务器里面】
乱码请无视,浏览器的锅
- 5.网络权限
<uses-permission android:name="android.permission.INTERNET"/>
二.layout_index.xml
布局就没什么内容了,一个进度,一个文本
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/index" android:gravity="center_horizontal"> <TextView android:gravity="center" android:layout_alignParentBottom="true" android:id="@+id/tv_version" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="20dp" android:textColor="@android:color/white" android:textSize="20sp" /> <ProgressBar android:layout_centerInParent="true" android:layout_alignParentBottom="true" android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="60dp" /></RelativeLayout>
三.逻辑分析
1.获取版本号
首先你的首页的文本上需要获取当前应用的版本号吧
/** * 获取APP版本号 * * @return */ private String getAppVersion() { try { //PackageManager管理器 PackageManager pm = getPackageManager(); //获取相关信息 packageInfo = pm.getPackageInfo(getPackageName(), 0); //版本名称 String name = packageInfo.versionName; //版本号 int version = packageInfo.versionCode; Log.i("版本信息", "版本名称:"+name+"版本号"+version); return name; } catch (NameNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } //如果出现异常抛出null return null; }
我们打印出来的Log
我们需要的就是这个name(版本名称)
2.解析JSON
这段json还是十分的简单的,我们直接就用原生的方式解析了,本来想用Volley的,但是演示的话,希望各位自己根据需求使用
/** * 解析JSON */ private void getJSON() { // 子线程访问,耗时操作 new Thread() { public void run() { try { // JSON地址 HttpURLConnection conn = (HttpURLConnection) new URL( //模拟器一般有一个预留IP:10.0.2.2 "http://192.168.1.103:8080/lgl/update.json") .openConnection(); //请求方式GRT conn.setRequestMethod("GET"); //连接超时 conn.setConnectTimeout(5000); //响应超时 conn.setReadTimeout(3000); //连接 conn.connect(); //获取请求码 int responseCode = conn.getResponseCode(); //等于200说明请求成功 if(responseCode == 200){ //拿到他的输入流 InputStream in = conn.getInputStream(); String stream = Utils.toStream(in); Log.i("JSON", stream); } } catch (MalformedURLException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }.start(); }
这里我们写了一个Utils来转换流
package com.lgl.tomcatversion;import java.io.ByteArrayOutputStream;import java.io.IOException;import java.io.InputStream;/** * 流转换Stream的工具栏 * * @author LGL * */public class Utils { // 对外发放 public static String toStream(InputStream in) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); // 长度 int length = 0; byte[] buffer = new byte[1024]; // -1代表读完了 while ((length = in.read(buffer)) != -1) { out.write(buffer, 0, length); } // 读完关闭 in.close(); out.close(); // 我们把返回的数据转换成String return out.toString(); }}
这样我们就可以把Json打印出来了
既然获取到了,那我们就开始解析JSON吧
在Log后面继续写代码
// 解析JSONObject jsonObject = new JSONObject(stream);// 获取版本名String versionName = jsonObject.getString("versionName");// 获取版本号int versionCode = jsonObject.getInt("versionCode");// 获取更新内容String content = jsonObject.getString("content");// 获取下载地址String url = jsonObject.getString("url");
这样我们就解析完成了
3.版本比较以及提示更新
这里我们可以根据name或者code的比较来判断是否有更新,有更新的话,弹出提示框,点确定再更新,我这里就比较Code了,先写以恶搞获取code的方法
/** * 获取versionCode */ private int getCode() { // PackageManager管理器 PackageManager pm = getPackageManager(); // 获取相关信息 try { packageInfo = pm.getPackageInfo(getPackageName(), 0); // 版本号 int version = packageInfo.versionCode; return version; } catch (NameNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return 0; }
然后我们接着刚才解析完JSON的地方比对,我们先把服务器的JSON数据改了
{ "versionName": "2.0", "versionCode": 2, "content": "修复多项bug!", "url": "http://192.168.1.103:8080/lgl/TomCatVersion.apk" }
这样就会提示更新了,我们检修
// 版本判断if (versionCode > getCode()) { // 提示更新 msg.what = UPDATE_YES;} else { // 不更新,跳转到主页 msg.what = UPDATE_NO;}
子线程中我们是不能弹框的,所以我们用Handler,当我们发送UPDATE_YES的时候就弹框,也就是执行我们弹框的方法
/** * 升级弹框 */ private void showUpdateDialog() { updateDialog = new CustomDialog(this, 0, 0, R.layout.dialog_update, R.style.Theme_dialog, Gravity.CENTER, 0); // 更新内容 dialog_update_content = (TextView) updateDialog .findViewById(R.id.dialog_update_content); dialog_update_content.setText(content); // 确定更新 dialog_confrim = (TextView) updateDialog .findViewById(R.id.dialog_confrim); dialog_confrim.setOnClickListener(this); // 取消更新 dialog_cancel = (TextView) updateDialog .findViewById(R.id.dialog_cancel); dialog_cancel.setOnClickListener(this); updateDialog.show(); }
这里我用了一个自定义的Dialog’
CustomDialog
package com.lgl.tomcatversion;import android.app.Dialog;import android.content.Context;import android.view.Gravity;import android.view.Window;import android.view.WindowManager;public class CustomDialog extends Dialog { public CustomDialog(Context context, int layout, int style) { this(context, WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT, layout, style, Gravity.CENTER); } public CustomDialog(Context context, int width, int height, int layout, int style, int gravity, int anim) { super(context, style); setContentView(layout); // set window params Window window = getWindow(); WindowManager.LayoutParams params = window.getAttributes(); // set width,height by density and gravity // float density = getDensity(context); params.width = WindowManager.LayoutParams.MATCH_PARENT; params.height = WindowManager.LayoutParams.WRAP_CONTENT; params.gravity = gravity; window.setAttributes(params); window.setWindowAnimations(anim); } public CustomDialog(Context context, int width, int height, int layout, int style, int gravity) { this(context, width, height, layout, style, gravity, R.style.pop_anim_style); }}
他需要用到一些资源
dialog_update.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/main_layout" android:layout_width="match_parent" android:layout_height="150dip" android:layout_marginEnd="20dp" android:layout_marginStart="20dp" android:background="@drawable/dialog_bg" android:orientation="vertical" > <LinearLayout android:id="@+id/dialog_notifly_bottom" android:layout_width="match_parent" android:layout_height="45dp" android:layout_alignParentBottom="true" android:layout_gravity="bottom" android:orientation="horizontal" > <TextView android:id="@+id/dialog_cancel" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:singleLine="true" android:text="暂不更新" android:textSize="18sp" /> <View android:layout_width="0.2dp" android:layout_height="match_parent" android:background="#AAAAAA" /> <TextView android:id="@+id/dialog_confrim" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:gravity="center" android:singleLine="true" android:text="立即下载" android:textSize="18sp" /> </LinearLayout> <View android:id="@+id/dialog_notifly_line" android:layout_width="match_parent" android:layout_height="0.2dp" android:layout_above="@id/dialog_notifly_bottom" android:background="#AAAAAA" /> <TextView android:id="@+id/dialog_update_content" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_above="@+id/dialog_notifly_line" android:layout_below="@+id/TextView01" android:layout_marginBottom="10dip" android:layout_marginLeft="10dip" android:layout_marginRight="10dip" android:gravity="center" android:singleLine="true" android:textSize="18sp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:layout_marginTop="20dp" android:gravity="center" android:singleLine="true" android:text="检测到有新版本,是否下载?" android:textSize="18sp" /></RelativeLayout>
styles.xml
<style name="pop_anim_style"> <item name="android:windowEnterAnimation">@anim/pop_in</item> <item name="android:windowExitAnimation">@anim/pop_out</item> </style> <style name="Theme_dialog" parent="@android:style/Theme.Dialog"> <item name="android:windowBackground">@android:color/transparent</item> <item name="android:windowNoTitle">true</item> </style>
以及用到的动画
pop_in.xml
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="@android:integer/config_shortAnimTime" android:fromXDelta="0" android:fromYDelta="100%" android:toXDelta="0" android:toYDelta="0" /></set>
pop_out.xml
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" > <translate android:duration="@android:integer/config_shortAnimTime" android:fromXDelta="0" android:fromYDelta="0" android:toXDelta="0" android:toYDelta="100%" /></set>
接着我们再执行一下
我们现在再来处理一下没有更新的逻辑,没有更新的话直接跳主页面,我们写一个方法
/** * 跳转主页面 */ private void goHome() { startActivity(new Intent(this, MainActivity.class)); finish(); }
但是这样还是有个问题,他没有更新一下子就跳过去了,所以我们在发消息的时候先让线程睡一会儿
try { //停留三秒钟 Thread.sleep(3000);} catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); }// 全部走完发消息handler.sendMessage(msg);
但是这里又出现了一个新的问题,毕竟是网络问题,他是耗时的,这样的话,万一等太久了用户体验也上不去啊,所以我们这里要做一个优化
我们在开始网络请求的时间记录一个时间
// 开始访问网络的时间long startTime = System.currentTimeMillis();
然后再网络请求结束的时候去计算时间并且计算一共用了多少时间
// 网络访问结束的时间long endTime = System.currentTimeMillis();// 计算网络用了多少时间long time = endTime - startTime;try { if (time < 3000) { // 停留三秒钟 Thread.sleep(3000 - time);} } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace();}
4.下载更新以及下载进度
涉及到下载,这里你可以使用很多的开源框架,我这里使用的是xutils
地址:https://github.com/wyouflf/xUtils
我们下载之后拷贝在libs里就可以用了,用起来也很简单,使用之前先加个权限
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
然后开始下载
/** * 下载更新 */ private void downloadAPK() { tv_pro.setVisibility(View.VISIBLE); // 判断是否有SD卡 if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { // 获取手机根目录 String path = Environment.getExternalStorageDirectory() .getAbsolutePath() + "/TomCatVersion.apk"; HttpUtils httpUtils = new HttpUtils(); /** * 1.网络地址 2.存放地址 3.回调 */ httpUtils.download(url, path, new RequestCallBack<File>() { // 下载进度 @Override public void onLoading(long total, long current, boolean isUploading) { // TODO Auto-generated method stub super.onLoading(total, current, isUploading); // 显示进度 tv_pro.setText(100 * current / total + "%"); } // 成功 @Override public void onSuccess(ResponseInfo<File> responseInfo) { } // 失败 @Override public void onFailure(HttpException error, String msg) { Log.i("error", msg); } }); } else { Toast.makeText(getApplicationContext(), "未找到SD卡", Toast.LENGTH_LONG) .show(); } }
这里的代码逻辑也是十分的简单的,多吧,我们来看一下效果‘
更新的包体积有点小,所以一下子就百分之百了,我们去SD卡更目录看一下
确定是下载完成了,但是下完完之后啥也没发生啊,这就要我们再次优化了
下载完之后软起动
下载完之后自动进入安装界面,这才是真正的体验,我们在onSuccess()方法中
// 跳转系统安装页面Intent i = new Intent();i.setAction(Intent.ACTION_VIEW);i.addCategory(Intent.CATEGORY_DEFAULT);i.setDataAndType(Uri.fromFile(new File(path)),"application/vnd.android.package-archive");startActivity(i);
我们来看看效果
大致的一个逻辑思路就是这样了
完整代码
IndexActivity
package com.lgl.tomcatversion;import java.io.File;import java.io.IOException;import java.io.InputStream;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import org.json.JSONException;import org.json.JSONObject;import android.app.Activity;import android.content.DialogInterface;import android.content.DialogInterface.OnCancelListener;import android.content.Intent;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.pm.PackageManager.NameNotFoundException;import android.net.Uri;import android.os.Bundle;import android.os.Environment;import android.os.Handler;import android.os.Message;import android.util.Log;import android.view.Gravity;import android.view.View;import android.view.View.OnClickListener;import android.widget.TextView;import android.widget.Toast;import com.lidroid.xutils.HttpUtils;import com.lidroid.xutils.exception.HttpException;import com.lidroid.xutils.http.ResponseInfo;import com.lidroid.xutils.http.callback.RequestCallBack;/** * 首页 * * @author LGL * */public class IndexActivity extends Activity implements OnClickListener { // 更新 private static final int UPDATE_YES = 1; // 不更新 private static final int UPDATE_NO = 2; // URL错误 private static final int URL_ERROR = 3; // 没有网络 private static final int IO_ERROR = 4; // 数据异常 private static final int JSON_ERROR = 5; private TextView tv_version; private PackageInfo packageInfo; private JSONObject jsonObject; private String versionName; private int versionCode; private String content; private String url; private TextView tv_pro; // 升级提示框 private CustomDialog updateDialog; private TextView dialog_update_content; private TextView dialog_confrim; private TextView dialog_cancel; private Handler handler = new Handler() { public void handleMessage(android.os.Message msg) { switch (msg.what) { case UPDATE_YES: showUpdateDialog(); break; case UPDATE_NO: goHome(); break; case URL_ERROR: Toast.makeText(getApplicationContext(), "地址错误", Toast.LENGTH_LONG).show(); goHome(); break; case IO_ERROR: Toast.makeText(getApplicationContext(), "请检查网络", Toast.LENGTH_LONG).show(); goHome(); break; case JSON_ERROR: Toast.makeText(getApplicationContext(), "Json解析错误", Toast.LENGTH_LONG).show(); goHome(); break; // 就算你报任何错,爸比是爱你的,依然让你进主页面 } } }; private String path; @Override protected void onCreate(Bundle savedInstanceState) { // TODO Auto-generated method stub super.onCreate(savedInstanceState); setContentView(R.layout.activity_index); initView(); getJSON(); } /** * 初始化控件 * * @author LGL */ private void initView() { tv_pro = (TextView) findViewById(R.id.tv_pro); tv_version = (TextView) findViewById(R.id.tv_version); // 设置版本号 tv_version.setText("版本号:" + getAppVersion()); } /** * 获取APP版本号 * * @return */ private String getAppVersion() { try { // PackageManager管理器 PackageManager pm = getPackageManager(); // 获取相关信息 packageInfo = pm.getPackageInfo(getPackageName(), 0); // 版本名称 String name = packageInfo.versionName; // 版本号 int version = packageInfo.versionCode; Log.i("版本信息", "版本名称:" + name + "版本号" + version); return name; } catch (NameNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 如果出现异常抛出 return "无法获取"; } /** * 解析JSON */ private void getJSON() { // 子线程访问,耗时操作 new Thread() { public void run() { Message msg = Message.obtain(); // 开始访问网络的时间 long startTime = System.currentTimeMillis(); try { // JSON地址 HttpURLConnection conn = (HttpURLConnection) new URL( // 模拟器一般有一个预留IP:10.0.2.2 "http://192.168.1.103:8080/lgl/update.json") .openConnection(); // 请求方式GRT conn.setRequestMethod("GET"); // 连接超时 conn.setConnectTimeout(5000); // 响应超时 conn.setReadTimeout(3000); // 连接 conn.connect(); // 获取请求码 int responseCode = conn.getResponseCode(); // 等于200说明请求成功 if (responseCode == 200) { // 拿到他的输入流 InputStream in = conn.getInputStream(); String stream = Utils.toStream(in); Log.i("JSON", stream); jsonObject = new JSONObject(stream); versionName = jsonObject.getString("versionName"); versionCode = jsonObject.getInt("versionCode"); content = jsonObject.getString("content"); url = jsonObject.getString("url"); // 版本判断 if (versionCode > getCode()) { // 提示更新 msg.what = UPDATE_YES; } else { // 不更新,跳转到主页 msg.what = UPDATE_NO; } } } catch (MalformedURLException e) { // URL错误 e.printStackTrace(); msg.what = URL_ERROR; } catch (IOException e) { // 没有网络 e.printStackTrace(); msg.what = IO_ERROR; } catch (JSONException e) { // 数据错误 e.printStackTrace(); msg.what = JSON_ERROR; } finally { // 网络访问结束的时间 long endTime = System.currentTimeMillis(); // 计算网络用了多少时间 long time = endTime - startTime; try { if (time < 3000) { // 停留三秒钟 Thread.sleep(3000 - time); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 全部走完发消息 handler.sendMessage(msg); } } }.start(); } /** * 获取versionCode */ private int getCode() { // PackageManager管理器 PackageManager pm = getPackageManager(); // 获取相关信息 try { packageInfo = pm.getPackageInfo(getPackageName(), 0); // 版本号 int version = packageInfo.versionCode; return version; } catch (NameNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } return 0; } /** * 升级弹框 */ private void showUpdateDialog() { updateDialog = new CustomDialog(this, 0, 0, R.layout.dialog_update, R.style.Theme_dialog, Gravity.CENTER, 0); //如果他点击其他地方,不安装,我们就直接去 updateDialog.setOnCancelListener(new OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { goHome(); } }); // 更新内容 dialog_update_content = (TextView) updateDialog .findViewById(R.id.dialog_update_content); dialog_update_content.setText(content); // 确定更新 dialog_confrim = (TextView) updateDialog .findViewById(R.id.dialog_confrim); dialog_confrim.setOnClickListener(this); // 取消更新 dialog_cancel = (TextView) updateDialog .findViewById(R.id.dialog_cancel); dialog_cancel.setOnClickListener(this); updateDialog.show(); } /** * 点击事件 */ @Override public void onClick(View v) { switch (v.getId()) { case R.id.dialog_confrim: updateDialog.dismiss(); downloadAPK(); break; case R.id.dialog_cancel: // 跳主页面 goHome(); break; } } /** * 跳转主页面 */ private void goHome() { startActivity(new Intent(this, MainActivity.class)); finish(); } /** * 下载更新 */ private void downloadAPK() { tv_pro.setVisibility(View.VISIBLE); // 判断是否有SD卡 if (Environment.getExternalStorageState().equals( Environment.MEDIA_MOUNTED)) { path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/TomCatVersion.apk"; HttpUtils httpUtils = new HttpUtils(); /** * 1.网络地址 2.存放地址 3.回调 */ httpUtils.download(url, path, new RequestCallBack<File>() { // 下载进度 @Override public void onLoading(long total, long current, boolean isUploading) { // TODO Auto-generated method stub super.onLoading(total, current, isUploading); // 显示进度 tv_pro.setText(100 * current / total + "%"); } // 成功 @Override public void onSuccess(ResponseInfo<File> responseInfo) { // 跳转系统安装页面 Intent i = new Intent(); i.setAction(Intent.ACTION_VIEW); i.addCategory(Intent.CATEGORY_DEFAULT); i.setDataAndType(Uri.fromFile(new File(path)), "application/vnd.android.package-archive"); startActivity(i); } // 失败 @Override public void onFailure(HttpException error, String msg) { Log.i("error", msg); } }); } else { Toast.makeText(getApplicationContext(), "未找到SD卡", Toast.LENGTH_LONG) .show(); } }}
layout_index.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@drawable/index" android:gravity="center_horizontal" > <TextView android:id="@+id/tv_version" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginBottom="20dp" android:gravity="center" android:textColor="@android:color/white" android:textSize="20sp" /> <ProgressBar android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_centerInParent="true" android:layout_marginBottom="60dp" /> <TextView android:id="@+id/textView2" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" android:layout_marginTop="207dp" android:gravity="center" android:text="欢迎" android:textColor="#fff" android:textSize="50sp" /> <TextView android:textColor="#220000" android:visibility="invisible" android:id="@+id/tv_pro" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_above="@+id/progressBar" android:layout_centerHorizontal="true" android:layout_marginBottom="95dp" android:textAppearance="?android:attr/textAppearanceLarge" /></RelativeLayout>
Demo下载地址:http://download.csdn.net/detail/qq_26787115/9463814
更多相关文章
- Android studio如何使用SVN进行版本控制?
- Android 系统最近几个版本的更新,你了解吗?
- Android Studio如何更改SDK的版本(针对非gradle)
- 2012.5.2微博热报:Android版本混乱、网站导航设计
- [置顶] 关于android:lineSpacingExtra属性 在android5.0与之前版
- Android获取应用程序的名称,包名,版本号
- android各版本差异--后期慢慢补充