android APP版本检测升级

1、本次服务端采用的是HTTP Servlet方式返回json数据

首先建立一个javaweb工程 创建一个Servlet类,代码在doGet中实现

/**
	 * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse
	 *      response)
	 */
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		// TODO Auto-generated method stub
		// response.getWriter().append("Served at:
		// ").append(request.getContextPath());	
		HttpSession session = request.getSession();
		String str = request.getParameter("function");
		if (str == null) {
			str = "main";
		}
		// 判断session是不是新创建的 减少读取xml频次
		if (session.isNew()) {
			//System.out.println("session创建成功,session的id是:" + session.getId());
		} else {
			//System.out.println("服务器已经存在该session了,session的id是:" + session.getId());
			String function = session.getAttribute("function").toString();
			//判断功能是否一致
			if (function.equals(str)) {
				String data = session.getAttribute("data").toString();
				response.setCharacterEncoding("utf-8");
				response.getWriter().append(data);
				return;
			}			
		}
		session.setAttribute("function", str.toString());
		if (str != null && str.equals("你的APP别名")) {//function代表你的APP,支持多个APP版本更新,放在不同的xml里面
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = null;
			try {
				db = dbf.newDocumentBuilder();
			} catch (ParserConfigurationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			String path = this.getServletContext().getRealPath("version2.xml");
			File file = new File(path);
			InputStream is = new FileInputStream(file);

			org.w3c.dom.Document doc = null;
			try {
				doc = db.parse(is);
			} catch (SAXException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			Element elem = ((org.w3c.dom.Document) doc).getDocumentElement();
			JSONObject jsonObjectRoot = new JSONObject();
			NodeList appversion = elem.getChildNodes();
			if (appversion != null) {
				JSONObject jsonObjectUpdate = new JSONObject();
				int n = 0;// 更新条目
				for (int i = 0; i < appversion.getLength(); i++) {
					Node app = appversion.item(i);
					for (Node node = app.getFirstChild(); node != null; node = node.getNextSibling()) {
						if (app.getNodeName().equals("version")) {
							String ver = app.getFirstChild().getNodeValue();
							jsonObjectRoot.put("version", ver);
						}
						if (app.getNodeName().equals("date")) {
							String da = app.getFirstChild().getNodeValue();
							jsonObjectRoot.put("date", da);
						}
						if (app.getNodeName().equals("update")) {
							n++;
							String ud = app.getFirstChild().getNodeValue();
							jsonObjectUpdate.put(n + "", ud);
						}
						if (app.getNodeName().equals("address")) {
							String addr = app.getFirstChild().getNodeValue();
							jsonObjectRoot.put("address", addr);
						}
					}
				}
				jsonObjectRoot.put("update", jsonObjectUpdate);
			}
			response.setCharacterEncoding("utf-8");
			response.getWriter().append(jsonObjectRoot.toString());
			session.setAttribute("data", jsonObjectRoot.toString());
		} else {
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			DocumentBuilder db = null;
			try {
				db = dbf.newDocumentBuilder();
			} catch (ParserConfigurationException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			String path = this.getServletContext().getRealPath("version.xml");
			File file = new File(path);
			InputStream is = new FileInputStream(file);

			org.w3c.dom.Document doc = null;
			try {
				doc = db.parse(is);
			} catch (SAXException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			Element elem = ((org.w3c.dom.Document) doc).getDocumentElement();
			JSONObject jsonObjectRoot = new JSONObject();
			NodeList appversion = elem.getChildNodes();
			if (appversion != null) {
				JSONObject jsonObjectUpdate = new JSONObject();
				int n = 0;// 更新条目
				for (int i = 0; i < appversion.getLength(); i++) {
					Node app = appversion.item(i);
					for (Node node = app.getFirstChild(); node != null; node = node.getNextSibling()) {
						if (app.getNodeName().equals("version")) {
							String ver = app.getFirstChild().getNodeValue();
							jsonObjectRoot.put("version", ver);
						}
						if (app.getNodeName().equals("date")) {
							String da = app.getFirstChild().getNodeValue();
							jsonObjectRoot.put("date", da);
						}
						if (app.getNodeName().equals("update")) {
							n++;
							String ud = app.getFirstChild().getNodeValue();
							jsonObjectUpdate.put(n + "", ud);
						}
						if (app.getNodeName().equals("address")) {
							String addr = app.getFirstChild().getNodeValue();
							jsonObjectRoot.put("address", addr);
						}
					}
				}
				jsonObjectRoot.put("update", jsonObjectUpdate);
			}
			response.setCharacterEncoding("utf-8");
			response.getWriter().append(jsonObjectRoot.toString());
			session.setAttribute("data", jsonObjectRoot.toString());
		}
	}
将XML文件放在WebContent目录下,结构自己定义,例如我的version.xml如下:

<?xml version="1.0" encoding="UTF-8"?>
<appupdate>
	<title>XX系统</title>
	<version>1.1.0</version>
	<date>2018.02.04</date>
	<update>1:XXX优化</update>
	<update>2:XXX修改</update>
	<update>3:XXX增加</update>
	<update>4:XXX其他</update>	
	<address>http://1.1.1.1:8888/download/YourApp.apk</address><!--你更新的APP的下载地址-->
</appupdate>
APP下载地址你可以放到tamcat服务器的一个虚拟下载目录下,搜索tomcat搭建文件下载服务器,很简单的配置。

这样访问地址 http://1.1.1.1:8888/YourWebName/YourServletName?function=你的APP别名 就能得到一个json字符串,例如我的如下:
{"version":"1.1.0","date":"2018.02.04","address":"http://1.1.1.1:8888/download/YourApp.apk","update":{"1":"1:XXX优化","2":"2:XXX修改","3":"3:XXX增加","4":"4:XXX其他"}}
现在web服务器端已经完成,下面进行android端编写

2、android客户端
   一、建立一个版本更新类,如下
public class AppVersion extends AsyncTask<String, integer, StringBuffer> {
    private Handler handle;

    public String getVersion() {
        return version;
    }

    private String version = "";

    public String getUpdate() {
        return update;
    }

    private String update = "";

    private String updateURL = "";

    static private String updateAPKname = "";

    static public String getAPKname() {
        return updateAPKname;
    }
    private boolean bAuto = true;

    public AppVersion(Handler handle, boolean bAuto/*自动检查更新、手动检查更新*/) {
        this.handle = handle;
        this.bAuto = bAuto;
    }

    @Override
    protected StringBuffer doInBackground(String... params) {
        String html = "";
        try {
            //HTTP GET请求
            URL url = null;
            url = new URL(params[0]);        
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            conn.setRequestMethod("GET");
            conn.setConnectTimeout(2000);
            conn.setReadTimeout(1000);
            InputStream inStream = conn.getInputStream();
            byte[] data = readInputStream(inStream);
            html = new String(data, "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new StringBuffer(html);
    }

    public static byte[] readInputStream(InputStream inStream) throws Exception {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        inStream.close();
        return outStream.toByteArray();
    }

    @Override
    protected void onPostExecute(StringBuffer result) {
        String Html = new String(result);
        if (!Html.equals("")) {
            try {
                JSONObject jsonObjectRoot = new JSONObject(Html);
                version = jsonObjectRoot.getString("version");
                JSONObject jsonObjectUpdate = jsonObjectRoot.getJSONObject("update");
                Iterator<String> keys = jsonObjectUpdate.keys();
                while (keys.hasNext()) {
                    String key = (String) keys.next();
                    update = update + jsonObjectUpdate.getString(key) + "\r\n\r\n";
                }
                updateURL = jsonObjectRoot.getString("address");
                updateAPKname = updateURL.substring(updateURL.lastIndexOf("/") + 1);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            handle.sendEmptyMessage(4);
        } else {
            if (!bAuto)
                Toast.makeText(SearchApplication.getInstance(), "无法获取更新,请检查网络是否畅通", Toast.LENGTH_LONG).show();
        }
        super.onPostExecute(result);
    }

    public String getUpdateURL() {
        return updateURL;
    }

    public boolean isbAuto() {
        return bAuto;
    }

    public void setbAuto(boolean bAuto) {
        this.bAuto = bAuto;
    }
}
二、在MainActivity的onCreate方法中调用

     

private String path = "http://1.1.1.1:8888/YourWebName/YourServletName?function=你的APP别名";
private AppVersion appVersion
private MyHandler myHandler;
appVersion = new AppVersion(myHandler, true);
appVersion.execute(path);
public class MyHandler extends Handler {
    @Override
    public void handleMessage(Message msg) {
        switch (msg.what) {              
            case 4://查询更新返回
                try {
                    if (compareVersion(appVersion.getVersion(), getVersion()) > 0) {
                        //获得最后版本后,如果服务器版本>当前版本,则显示更新内容 只提示1次,1次后在菜单显示
                        String csTipUpdate = sp.getString(Constant.csTipUpdate, "0");//获取是否提示过
                        //如果未提示或者是手动更新的 则显示更新弹窗
                        if (csTipUpdate.equals("0") || !appVersion.isbAuto()) {
                            showUpdateDialog(true);//显示更新的弹窗
                            sp.edit().putString(Constant.csTipUpdate, "1").apply();
                        }
                        bUpdate = true;//菜单标志位
                        btnDowloadMap.setImageResource(R.mipmap.set1);
                    } else {
                        Log.d("version:", "没有更新的版本!");
                        sp.edit().putString(Constant.csTipUpdate, "0").apply();
                        //获得最后版本后,如果和当前不一致,则显示更新内容
                        String csVersion = sp.getString(Constant.csVersion, "");
                        if (csVersion.equals("") || !csVersion.equals(appVersion.getVersion())) {
                            showUpdateDialog(false);//显示内容
                        }
                        sp.edit().putString(Constant.csVersion, appVersion.getVersion()).apply();
                        if (!appVersion.isbAuto())
                            Toast.makeText(MainActivity.this, "无可用更新", Toast.LENGTH_LONG).show();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                break;  
            case 8://下载进度
            double percent = queryDownloadStatus();
            if (progressBar_download != null)
                progressBar_download.setProgress((int) percent);
            if (percent >= 100)
                progressBar_download.setVisibility(View.INVISIBLE);
            break;          
        }
    }
}
/**
* 比较版本号的大小,前者大则返回一个正数,后者大返回一个负数,相等则返回0
*
* @param version1
* @param version2
*/
public static int compareVersion(String version1, String version2) throws Exception {
    if (version1 == null || version2 == null) {
        throw new Exception("compareVersion error:illegal params.");
    }
    String[] versionArray1 = version1.split("\\.");//注意此处为正则匹配,不能用.;
    String[] versionArray2 = version2.split("\\.");
    int idx = 0;
    int minLength = Math.min(versionArray1.length, versionArray2.length);//取最小长度值
    int diff = 0;
    while (idx < minLength
            && (diff = versionArray1[idx].length() - versionArray2[idx].length()) == 0//先比较长度
            && (diff = versionArray1[idx].compareTo(versionArray2[idx])) == 0) {//再比较字符
        ++idx;
    }
    //如果已经分出大小,则直接返回,如果未分出大小,则再比较位数,有子版本的为大;
    diff = (diff != 0) ? diff : versionArray1.length - versionArray2.length;
    return diff;
}
 
/**
* Author:
* CreateTime: 2017/11/15 11:09
* Comment: 获取软件版本号
* Param:
*/
public String getVersion() {
    try {
        PackageManager manager = this.getPackageManager();
        PackageInfo info = manager.getPackageInfo(this.getPackageName(), 0);
        String version = info.versionName;
        return version;
    } catch (Exception e) {
        String s = e.toString();
        e.printStackTrace();
        return "";
    }
}

三、android端下载APK安装

private DownloadManager downloadManager;//下载管理类
private long lastDownloadId;
/**
* Author: 
* CreateTime: 2018/1/3 15:39
* Comment: 更新窗口
* Param:
*/

private void showUpdateDialog(boolean b) {
    if (b) {
        LayoutInflater inflater = LayoutInflater.from(this);
        View layout = inflater.inflate(R.layout.pop_needupdate, null);//资源文件不贴出了
        final Dialog dialog = new Dialog(MainActivity.this, R.style.dialogstyle);
        TextView tv = (TextView) layout.findViewById(R.id.tvupdate);//显示内容的文件控件
        final TextView tvdownload = (TextView) layout.findViewById(R.id.btn_download);//下载按钮
        TextView tvclose = (TextView) layout.findViewById(R.id.btn_close);//弹窗关闭按钮
        tvclose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                dialog.dismiss();
            }
        });
        tvdownload.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
                Uri uri = Uri.parse(appVersion.getUpdateURL());//更新类获得的APK URL地址
                DownloadManager.Request request = new DownloadManager.Request(uri);
                request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE | DownloadManager.Request.NETWORK_WIFI);
                request.setVisibleInDownloadsUi(false);
                request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN);
                File rootFile = new File(Environment.getExternalStorageDirectory().getPath() + "/YourAPP/download/");//创建一个下载目录
                if (!rootFile.exists()) {
                    rootFile.mkdirs();
                }
                request.setDestinationInExternalPublicDir("/YourAPP/download/", getAPKname());//设置下载路径
                lastDownloadId = downloadManager.enqueue(request);
                app.setLastDownloadId(lastDownloadId);
                progressBar_download.setVisibility(View.VISIBLE);//弹窗上的下载进度条
                tvdownload.setText("正在下载");
                tvdownload.setClickable(false);
                final Timer timer = new Timer();
                TimerTask task = new TimerTask() {
                    @Override
                    public void run() {
                        double percent = queryDownloadStatus();
                        myHandler.sendEmptyMessage(8);
                        if (percent >= 100) {
                            timer.cancel();
                        }
                    }
                };
                timer.schedule(task, 100, 500);
            }
        });
        progressBar_download = (CustomLoading) layout.findViewById(R.id.progressBar_download);
        String update = appVersion.getUpdate();
        tv.setText(update);
        dialog.setCancelable(false);
        dialog.setContentView(layout);
        dialog.show();

        Window window = dialog.getWindow();
        WindowManager manager = window.getWindowManager();
        Display d = manager.getDefaultDisplay();
        WindowManager.LayoutParams params = window.getAttributes();
        params.width = (int) (d.getWidth() * 0.4);
        params.height = LinearLayout.LayoutParams.WRAP_CONTENT;
        dialog.getWindow().setAttributes(params);
    } else {
        LayoutInflater inflater = LayoutInflater.from(this);
        View layout = inflater.inflate(R.layout.pop_update, null);//更新后软件第一次进入显示本次更新内容弹窗
        final Dialog dialog = new Dialog(MainActivity.this, R.style.dialogstyle);
        TextView tv = (TextView) layout.findViewById(R.id.tvupdate);
        String update = appVersion.getUpdate();
        tv.setText(update);
        dialog.setCancelable(true);
        dialog.setContentView(layout);
        dialog.show();
        Window window = dialog.getWindow();
        WindowManager manager = window.getWindowManager();
        Display d = manager.getDefaultDisplay();
        WindowManager.LayoutParams params = window.getAttributes();
        params.width = (int) (d.getWidth() * 0.4);
        params.height = LinearLayout.LayoutParams.WRAP_CONTENT;
        dialog.getWindow().setAttributes(params);
    }
}
//你的Application加入代码
public static YourApplication app;

private long lastDownloadId;

public static YourApplication getInstance() {
    return app;
}

public long getLastDownloadId() {
    return lastDownloadId;
}

public void setLastDownloadId(long lastDownloadId) {
    this.lastDownloadId = lastDownloadId;
}
//查询下载进度
private int queryDownloadStatus() {
    DownloadManager.Query query = new DownloadManager.Query();
    query.setFilterById(lastDownloadId);
    Cursor c = downloadManager.query(query);
    if (c != null && c.moveToFirst()) {
        int fileSizeIdx = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
        int bytesDLIdx = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
        int fileSize = c.getInt(fileSizeIdx);
        int bytesDL = c.getInt(bytesDLIdx);
        double percent = bytesDL * 100.0 / fileSize;
        return (int) percent;
    }
    return 0;
}
四、加入一个广播,下载完成后安装
      AndroidManifest.xml加入如下代码

<!-- 权限下载更新时不在通知栏显示 其他网络权限就不贴出来了-->
    <uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />

    </application>
        <receiver android:name=".BroadcastReceiver.CompleteReceiver">
            <intent-filter>
                <action android:name="android.intent.action.DOWNLOAD_COMPLETE" />
            </intent-filter>
        </receiver>
    </application>
广播类

public class CompleteReceiver extends BroadcastReceiver {

    private DownloadManager downloadManager;
    private long downloadId;

    @Override
    public void onReceive(Context context, Intent intent) {

        String action = intent.getAction();
        if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) {
            downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
            installApk(context, downloadId);
        }
    }

    private void installApk(Context context, long downloadId) {
        DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
        if (app.getLastDownloadId() == downloadId) {
            Uri downloadFileUri = downloadManager.getUriForDownloadedFile(downloadId);
            if (getAPKname() != null && !getAPKname().equals("")) {
                try {
                    if (downloadFileUri != null) {
                        Intent install = new Intent(Intent.ACTION_VIEW);
                        install.setDataAndType(downloadFileUri, "application/vnd.android.package-archive");
                        install.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                        context.startActivity(install);
                    }
                } catch (Exception e) {
                    Toast.makeText(context, "安装APP出现问题", Toast.LENGTH_LONG).show();
                }
            } else {
                Toast.makeText(context, "安装APP出现问题", Toast.LENGTH_LONG).show();
            }
        }
    }
}
好的,这里已经结束,学习安卓几个月时间,有不足写的不对的地方敬请见谅!


框架内部支持中/英文(其他语言只需要在对应的string.xml中取相同的名字即可)内部对话框背景图片、按钮支持自定义了查看版本中的Log只需要过滤AppUpdate开头的Tag重点: 如果没有设置downloadPath则默认为getExternalCacheDir()目录,同时不会申请[存储]权限!目录编译问题效果图功能介绍DownloadManagerUpdateConfiguration使用步骤Demo下载体验版本更新记录结语编译问题因为适配了Android O的通知栏,所以依赖的v7包版本比较高appcompat-v7:26.1.0使用的gradle版本为gradle-4.1-all,所以建议使用Android Studio 3.0及以上的版本打开此项目效果图     功能介绍 支持断点下载 支持后台下载 支持自定义下载过程 支持 设备 >= Android M 动态权限的申请 支持通知栏进度条展示(或者自定义显示进度) 支持Android N 支持Android O 支持中/英文双语 支持自定内置对话框的样式 使用HttpURLConnection下载,未集成其他第三方框架更加详细的文档参阅此处《AppUpdate API文档》DownloadManager:配置文档初始化使用DownloadManager.getInstance(this)属性描述默认值是否必须设置context上下文nulltrueapkUrlapk的下载地址nulltrueapkNameapk下载好的名字nulltruedownloadPathapk下载的位置getExternalCacheDir()falseshowNewerToast是否提示用户 "当前已是最新版本"falsefalsesmallIcon通知栏的图标(资源id)-1trueconfiguration这个库的额外配置nullfalseapkVersionCode更新apk的versionCode (如果设置了那么库中将会进行版本判断下面的属性也就需要设置了)1falseapkVersionName更新apk的versionNamenullfalseapkDescription更新描述nullfalseapkSize新版本的安装包大小(单位M)nullfalseauthorities兼容Android N uri授权应用包名falseUpdateConfiguration:配置文档属性描述默认值notifyId通知栏消息id1011notificationChannel适配Android O的渠道通知详情查阅源码httpManager设置自己的下载过程nullbreakpointDownload是否需要支持断点下载trueenableLog是否需要日志输出trueonDownloadListener下载过程的回调nulljumpInstallPage下载完成是否自动弹出安装页面trueshowNotification是否显示通知栏进度(后台下载提示)trueforcedUpgrade是否强制升级falseonButtonClickListener按钮点击事件回调nulldialogImage对话框背景图片资源(图片规范参考demo)-1dialogButtonColor对话框按钮的颜色-1dialogButtonTextColor对话框按钮的文字颜色-1所有版本:点击查看使用步骤第一步: app/build.gradle进行依赖implementation 'com.azhon:appupdate:1.7.3'第二步:创建DownloadManager,更多用法请查看这里示例代码DownloadManager manager = DownloadManager.getInstance(this); manager.setApkName("appupdate.apk")         .setApkUrl("https://raw.githubusercontent.com/azhon/AppUpdate/master/apk/appupdate.apk")         .setSmallIcon(R.mipmap.ic_launcher)         //可设置,可不设置         .setConfiguration(configuration)         .download();第三步:兼容Android N 及以上版本,在你应用的Manifest.xml添加如下代码<--! android:authorities="${applicationId}"  这个值必须与DownloadManager中的authorities一致(不设置则为应用包名)--> <provider     android:name="android.support.v4.content.FileProvider"     android:authorities="${applicationId}"     android:exported="false"     android:grantUriPermissions="true">     <meta-data         android:name="android.support.FILE_PROVIDER_PATHS"         android:resource="@xml/file_paths_public" /> </provider>第四步:资源文件res/xml/file_paths_public.xml内容<?xml version="1.0" encoding="utf-8"?> <paths>     <external-path         name="app_update_external"         path="/" />     <external-cache-path         name="app_update_cache"         path="/" /> </paths>兼容Android O及以上版本,需要设置NotificationChannel(通知渠道);库中已经写好可以前往查阅NotificationUtil.java温馨提示:升级对话框中的内容是可以上下滑动的哦!如果需要实现自己一套下载过程,只需要继承BaseHttpDownloadManager 并使用listener更新进度public class MyDownload extends BaseHttpDownloadManager {}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值