Android之——应用更新功能

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46916021

一、概述

相信大家都遇到过这种情况,就是在Android手机中的应用,往往在应用的设置里面,都会有一个检查更新的功能,如果应用开发商或者运营商发布了新的应用版本,点击设置里面检查更新的按钮就会提示用户有新版本,是否需要更新,如果用户点击了“立即更新”后,会将应用开发商或运营商服务器上最新的应用版本更新到手机中,如果用户没有选择立即更新,则不会更新手机应用程序。

二、实现原理

服务端存放有一个描述应用版本信息的文件,我们这个程序中姑且将这个版本描述文件定义为XML格式,在这个文件中定义了应用的版本号,名称大小,下载链接,更新描述等信息。这个文件中所描述的应用版本号与服务器上发布的版本信息对应。当手机用户点击应用设置中检查更新的功能时,会将手机中安装的应用版本号与服务器中描述应用版本信息的文件版本号比较,如果手机中的版本号小于文件中的版本号,则提示用户有新版本,是否需要更新。否则,提示用户手机中安装的应用程序已是最新版本。

三、服务端实现

这里服务端需要一个描述应用更新信息的XML文件,一个新版本的APK文件和一个下载APK文件的接口路径。

1、描述应用更新信息的XML文件

在这个文件中定义了要更新的应用的版本号,名称大小,下载链接,更新描述等信息,它作为手机客户端应用程序与服务端应用程序对比的媒介。这个文件中描述的版本信息与更新信息与服务端存放的APK信息完全一致。

具体实现如下:

<?xml version="1.0" encoding="utf-8"?>
<update>
	<version>2</version>
	<versiondesc>0.2</versiondesc>
	<name>update.apk</name>
	<size>6313406</size>
	<url>http://192.168.254.103:9999/Wechat/app/get-app/update.apk</url>
	<description>我们的产品上线啦</description>
</update>

2、实现更新APK文件的接口类MobileVersionAPI

在这个类中主要是实现了以文件流的形式来响应Android客户端的请求,从而实现XML文件的下载与APK文件的下载功能。

具体实现代码如下:

package com.cdsmartlink.system.mobile.api.version;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

import com.cdsmartlink.utils.common.FileCopyUtils;
import com.cdsmartlink.utils.common.StringUtils;

/**
 * 获取App版本的api
 * @author liuyazhuang
 *
 */
@Controller
public class MobileVersionAPI {
	
	private static final String TYPE_APK = "apk";
	private static final String TYPE_XML = "xml";
	/**
	 * 获取Android版本比对文件
	 * @param request
	 * @param response
	 */
	@RequestMapping(value="/app/get-android-version/android.xml")
	public void getAndroidVersionXml(HttpServletRequest request,HttpServletResponse response){
		try {
			this.downloadFile(request, response,TYPE_XML ,"version/android/version.xml");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 获取IOS版本对比文件
	 * @param request
	 * @param response
	 */
	@RequestMapping(value="/app/get-ios-version/ios.xml")
	public void getIOSVersionXml(HttpServletRequest request,HttpServletResponse response){
		try {
			this.downloadFile(request, response,TYPE_XML, "version/ios/version.xml");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 下载
	 * @param request
	 * @param response
	 */
	@RequestMapping(value="/app/get-app/update.ipa")
	public void getIOSApp(HttpServletRequest request,HttpServletResponse response){
		try {
			this.downloadFile(request, response,TYPE_APK, "app/update.ipa");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 获取IOS apk
	 * @param request
	 * @param response
	 */
	@RequestMapping(value="/app/get-web-app/update.ipa")
	public void getIOSWebApp(HttpServletRequest request,HttpServletResponse response){
		try {
			this.download(request, response, "app/update.ipa","application/octet-stream");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 下载app
	 * @param request
	 * @param response
	 */
	@RequestMapping(value="/app/get-app/update.apk")
	public void getApp(HttpServletRequest request,HttpServletResponse response){
		try {
			this.downloadFile(request, response,TYPE_APK, "app/update.apk");
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	@RequestMapping(value="/app/get-web-app/update.apk")
	public void getWebApp(HttpServletRequest request,HttpServletResponse response){
		try {
			this.download(request, response, "app/update.apk","application/octet-stream");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 网页下载
	 * @param request
	 * @param response
	 * @param fileName
	 * @param contentType
	 * @throws Exception
	 */
	public static void download(HttpServletRequest request,  
            HttpServletResponse response, String fileName, String contentType) throws Exception {  
		String path = request.getServletContext().getRealPath("/");
		if(StringUtils.isEmpty(path)) return;
		if(path.contains("\\"))
			path = path.replace("\\", "/");
		path = path.concat(fileName);
//        response.setContentType("text/html;charset=UTF-8");  
        request.setCharacterEncoding("UTF-8");  
        BufferedInputStream bis = null;  
        BufferedOutputStream bos = null;  
        File file = new File(path);
        if(file==null || !file.exists() || !file.isFile())
        	return;
        long fileLength = file.length();  
        response.setContentType(contentType);  
        response.setHeader("Content-disposition", "attachment; filename="+new String(fileName.getBytes("utf-8"), "ISO8859-1"));  
        response.setHeader("Content-Length", String.valueOf(fileLength));  
        bis = new BufferedInputStream(new FileInputStream(path));  
        bos = new BufferedOutputStream(response.getOutputStream());  
//        byte[] buff = new byte[2048];  
//        int bytesRead;  
//        while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {  
//            bos.write(buff, 0, bytesRead);  
//        }  
        FileCopyUtils.copy(bis, bos);
}  
	
	/**
	 * 下载文件(未下载的时候要显示文件的大小信息)
	 * @param request
	 * @param response
	 * @param type
	 */
	private void downloadFile(HttpServletRequest request,HttpServletResponse response,String type,String fileName)throws IOException{
		String path = request.getServletContext().getRealPath("/");
		if(StringUtils.isEmpty(path)) return;
		if(path.contains("\\"))
			path = path.replace("\\", "/");
		path = path.concat(fileName);
		File file = new File(path);
		if(file == null || !file.exists()|| !file.isFile())
			return;
		response.setHeader("Content-Length", String.valueOf(file.length()));  
		InputStream in = new FileInputStream(file);
		BufferedInputStream bin = new BufferedInputStream(in);
		OutputStream out = response.getOutputStream();
		BufferedOutputStream bout = new BufferedOutputStream(out);
		FileCopyUtils.copy(bin, bout);
	}
}

3、资源存放

在这个示例中我在WebRoot下面新建目录叫做app,将要更新的apk文件存放到目录下;在WebRoot目录下新建目录叫做version/android,将更新信息描述文件放在version/android目录中。

整体结构图如下:

四、Android实现

通过上面两篇博文《Android之——多线程下载示例》与《Android之——多线程断点续传下载示例》的学习,我们对Android中多线程的下载有了较为深入的理解,这里我们将版本更新的功能同样放在子线程中去执行。首先,用户点击界面上的更新按钮,向服务器发送请求,服务器将更新描述文件响应给Android客户端,客户端通过解析这个XML文件,获取文件中描述的版本号与本应用程序的版本号比较,如果文件中描述的版本号大于本应用程序的版本号,则提示用户有新版本,是否更新;否则提示用户当前已是最新版本。

1、主界面布局实现

此布局实现起来比较简单,就是在界面上显示一个按钮,用户点击按钮实现程序的更新操作。

具体实现的代码如下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context=".MainActivity" >

    <Button
        android:layout_centerInParent="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="update"
        android:text="更新"/>

</RelativeLayout>

2、更新操作界面布局实现

在这个界面中主要是显示更新过程中显示的更新信息,更新进度,版本说明等信息。

具体实现代码如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

    <!-- 版本号 -->

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="220dp" >

        <LinearLayout
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                android:orientation="horizontal" >

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="版本号:"
                    android:textSize="16sp" />

                <TextView
                    android:id="@+id/vesion_num_textview"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textSize="16sp" />
            </LinearLayout>
            <!-- 更新文件大小 -->

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                android:orientation="horizontal" >

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="文件大小:"
                    android:textSize="16sp" />

                <TextView
                    android:id="@+id/file_size_textview"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textSize="16sp" />
            </LinearLayout>
            <!-- 更新内容 -->

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                android:text="更新内容:"
                android:textSize="16sp" />

            <LinearLayout
                android:id="@+id/update_content_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_margin="8dp"
                android:orientation="vertical" >
            </LinearLayout>
        </LinearLayout>
    </ScrollView>

</RelativeLayout>

3、新建操作Android程序的工具类AppUtils

在这个程序中,我主要封装了一些获取应用程序基本信息的方法。比如获取应用版本号,版本名称和安装情况等等。

具体实现代码如下:

package com.lyz.utils.app;

import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.PixelFormat;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.view.WindowManager;

/**
 * 操作当前的App的工具类
 * 
 * @author liuyazhuang
 * 
 */
public final class AppUtils {
	
	/**
	 * 获取软件版本号
	 * 
	 * @param context
	 * @return
	 */
	public static int getVersionCode(Context context) {
		int versionCode = 0;
		try {
			// 获取软件版本号,对应AndroidManifest.xml下android:versionCode
			versionCode = context.getPackageManager().getPackageInfo(
					context.getPackageName(), 0).versionCode;
		} catch (NameNotFoundException e) {
			e.printStackTrace();
		}
		return versionCode;
	}
	/**
	 * 获取当前应用的版本号
	 * 
	 * @param pName
	 *            :应用程序包名
	 * @param mContext
	 *            :
	 * @return
	 */
	public static int getVersionCode(String pName, Context mContext) throws Exception {
		PackageInfo pinfo = mContext.getPackageManager().getPackageInfo(pName,
				PackageManager.GET_CONFIGURATIONS);
		return pinfo.versionCode;
	}

	/**
	 * 获取当前应用的版本名称
	 * 
	 * @param pName
	 * @param mContext
	 * @return
	 * @throws Exception
	 */
	public static String getVersionName(String pName, Context mContext) {
		try {
			PackageInfo pinfo = mContext.getPackageManager().getPackageInfo(pName,
					PackageManager.GET_CONFIGURATIONS);
			return pinfo.versionName;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * 判断该应用在手机中的安装情况
	 * 
	 * @param packageName
	 *            要判断应用的包名
	 */
	public static boolean checkAPK(String packageName, Context context) {
		List<PackageInfo> pakageinfos = context.getPackageManager()
				.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES);
		for (PackageInfo pi : pakageinfos) {
			String pi_packageName = pi.packageName;
			if (packageName.endsWith(pi_packageName)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * 退出当前应用程序
	 * 
	 * @param context
	 */
	public static void exitCurrentProgress(Context context) {
		Intent startMain = new Intent(Intent.ACTION_MAIN);
		startMain.addCategory(Intent.CATEGORY_HOME);
		startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		context.startActivity(startMain);
		System.exit(0);
	}

	/**
	 * 下载新的app
	 * 
	 * @param url
	 * @param context
	 */
	public static void downloadNewApp(String url, Context context) {
		Intent it = new Intent("android.intent.action.VIEW", Uri.parse(url));
		context.startActivity(it);
	}

	/**
	 * 隐藏软键盘
	 * 
	 * @param context
	 */
	public static void hideSoftKeyBoard(Activity context) {
		context.getWindow().setSoftInputMode(
				WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
	}

	/**
	 * 将Drawable转化为Bitmap
	 * 
	 * @param drawable
	 * @return
	 */
	public static Bitmap drawableToBitmap(Drawable drawable) {
		int width = drawable.getIntrinsicWidth();
		int height = drawable.getIntrinsicHeight();
		Bitmap bitmap = Bitmap.createBitmap(width, height, drawable
				.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
				: Bitmap.Config.RGB_565);
		Canvas canvas = new Canvas(bitmap);
		drawable.setBounds(0, 0, width, height);
		drawable.draw(canvas);
		return bitmap;
	}
}

4、自动以广播接收者来接收者PkInstallReceiver

主要接收更新功能发出的广播来进行应用程序的安装操作,在这个类中,主要实现了应用程序的自动安装功能

具体实现的代码如下:

package com.lyz.utils.update.broadcast;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;

/**
 * 自定义广播接收者
 * 来实现自动安装APK的功能
 * 
 * @author liuyazhuang
 * 
 */
public class PkInstallReceiver extends BroadcastReceiver {

	@Override
	public void onReceive(Context context, Intent intent) {
		// 监听安装程序
		if (intent.getAction().equals("android.intent.action.PACKAGE_ADDED")) {
			String packageName = intent.getDataString().substring(8);
			Log.i("安装应用", "安装:" + packageName + "包名的程序");
			Intent newIntent = new Intent();
			newIntent.setClassName("com.lyz.update.activity", "com.lyz.update.activity.MainActivity");
			newIntent.setAction("android.intent.action.MAIN");
			newIntent.addCategory("android.intent.category.LAUNCHER");
			newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
			context.startActivity(newIntent);
		}
		//监听卸载
//		if (intent.getAction().equals("android.intent.action.PACKAGE_REMOVED")) {
//			String packageName = intent.getDataString().substring(8);
//			Log.i("卸载应用", "卸载:" + packageName + "包名的程序");
//		}
	}

}

5、新建UpdateDatas类

为了使应用程序的开发更加符合面向对象的封装,我们将更新文件的长度和文件流信息封装为一个实体类,以实现对这些信息的封装操作。

具体实现的代码如下:

package com.lyz.utils.update;

import java.io.InputStream;
import java.io.Serializable;

/**
 * 封装更新文件的数据流和长度
 * @author liuyazhuang
 *
 */
public class UpdateDatas implements Serializable {
	private static final long serialVersionUID = 9210856546501387030L;
	//长度信息
	private Integer length;
	//文件流信息
	private InputStream in;
	
	public Integer getLength() {
		return length;
	}
	public void setLength(Integer length) {
		this.length = length;
	}
	public InputStream getIn() {
		return in;
	}
	public void setIn(InputStream in) {
		this.in = in;
	}
	
}

6、新建XML文件的解析类ParseXmlUtils

这个类主要负责XML文件的解析操作,将获取到的文件输入流解析成一个Map对象返回,其中Map中存放的每一个键值对就代表XML文件中的每一个节点信息。

具体实现的代码如下:

package com.lyz.utils.update;

import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import com.lyz.utils.common.XmlUtils;
/**
 * 解析版本更新文件
 * @author liuyazhuang
 *
 */
public class ParseXmlUtils {
	
	//版本信息
	public static final String Version = "version";
	//名称
	public static final String Name = "name";
	//下载的链接
	public static final String Url = "url";
	//项目更新描述
	public static final String Description = "description";
	//大小
	public static final String Size = "size";
	//用户看到的版本信息
	public static final String VERSION_DESC = "versiondesc";
	
	
	/**
	 * 将输入流转化为map
	 * @param inStream
	 * @return
	 * @throws Exception
	 */
	public static Map<String, String> parseXml(InputStream inStream) throws Exception {
		NodeList childNodes = XmlUtils.parseXml(inStream);
		if(childNodes == null) return null;
		Map<String, String> hashMap = new HashMap<String, String>();
		for (int j = 0; j < childNodes.getLength(); j++) {
			// 遍历子节点
			Node childNode = childNodes.item(j);
			if (childNode.getNodeType() == Node.ELEMENT_NODE) {
				Element childElement = (Element) childNode;
				// 版本号
				if (Version.equals(childElement.getNodeName())) {
					hashMap.put(Version, childElement.getFirstChild().getNodeValue());
				}
				// 软件名称
				else if ((Name.equals(childElement.getNodeName()))) {
					hashMap.put(Name, childElement.getFirstChild().getNodeValue());
				}
				// 下载地址
				else if ((Url.equals(childElement.getNodeName()))) {
					hashMap.put(Url, childElement.getFirstChild().getNodeValue());
				//应用更新描述
				}else if(Description.equals(childElement.getNodeName())){
					hashMap.put(Description, childElement.getFirstChild().getNodeValue());
				}
				//大小
				else if(Size.equals(childElement.getNodeName())){
					hashMap.put(Size, childElement.getFirstChild().getNodeValue());
				}
				//用户所看到的版本号
				else if(VERSION_DESC.equals(childElement.getNodeName())){
					hashMap.put(VERSION_DESC, childElement.getFirstChild().getNodeValue());
				}
			}
		}
		return hashMap;
	}
}

7、新建应用程序的核心类UpdateManager

这个类是整个应用程序的核心实现类,在这个类中,我们实现了文件的下载,版本对比与是否更新的操作,我们同样将文件的下载放在子线程中执行,子线程对过Handler与Message机制与主线程交互数据,通知主线程更新UI,同时在这个类的构造方法中注册广播接收者。在解析XML文件时,文件中的版本号不大于手机中应用版本号的时候取消注册广播接收者,同时在下载最新APK文件结束,安装完成后取消注册广播接收者。

具体实现代码如下:

package com.lyz.utils.update;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;

import com.lyz.update.activity.R;
import com.lyz.utils.app.AppUtils;
import com.lyz.utils.common.FileConnectionUtils;
import com.lyz.utils.common.FileUtils;
import com.lyz.utils.common.StringUtils;
import com.lyz.utils.update.broadcast.PkInstallReceiver;

/**
 * 应用更新类
 * @author liuyazhuang
 *
 */
public class UpdateManager {
	//获取更新文件的基本路径
	private static final String URL  = "http://192.168.254.103:9999/Wechat";
	
	private ProgressDialog mProgressDialog = null;
	/* 下载中 */
	private static final int DOWNLOAD = 1;
	/* 下载结束 */
	private static final int DOWNLOAD_FINISH = 2;
	// 解析下载的版本文件
	private static final int PARSE_XML = 3;
	//版本号
	private static final String VERSION = "version";
	//长度
	private static final String LENGTH = "length";
	/* 保存解析的XML信息 */
	private Map<String, String> mHashMap;
	/* 下载保存路径 */
	private String mSavePath;
	/* 记录进度条数量 */
	private int progress;
//	int count;
	/* 是否取消更新 */
	private boolean cancelUpdate = false;
	
	private int count;

	private Context mContext;
	/* 更新进度条 */
	private PkInstallReceiver pkInstallReceiver;
	

	private Handler mHandler = new Handler() {
		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
			// 正在下载
			case DOWNLOAD:
				// 设置进度条位置
				mProgressDialog.incrementProgressBy(progress);
				//设置第一进度值   (数值)
				mProgressDialog.setProgress(count);
//				mProgressDialog.setSecondaryProgress(10);
				break;
			case DOWNLOAD_FINISH:
				// 安装文件
				installApk();
				if(pkInstallReceiver != null){
					mContext.unregisterReceiver(pkInstallReceiver);
				}
				break;
			case PARSE_XML:
//				int serviceCode = (Integer) msg.obj;
				Map<String, Integer> map = (Map<String, Integer>) msg.obj;
				int serviceCode = map.get(VERSION);
				int length = map.get(LENGTH);
				// 获取当前软件版本
				int versionCode = AppUtils.getVersionCode(mContext);
				if (serviceCode > versionCode) {
					// 显示提示对话框
					showNoticeDialog(length);
				} else {
					Toast.makeText(mContext, R.string.soft_update_no,
							Toast.LENGTH_LONG).show();
					if(pkInstallReceiver != null){
						mContext.unregisterReceiver(pkInstallReceiver);
					}
				}
				break;
			default:
				break;
			}
		};
	};

	/**
	 * 构造方法
	 * @param context
	 */
	public UpdateManager(Context context) {
		this.mContext = context;
		pkInstallReceiver = new PkInstallReceiver();
		IntentFilter intentFilter=new IntentFilter(); 
		intentFilter.addAction("android.intent.action.PACKAGE_ADDED"); 
		intentFilter.addAction("android.intent.action.PACKAGE_REMOVED"); 
		intentFilter.addDataScheme("package");
		this.mContext.registerReceiver(pkInstallReceiver, intentFilter);
	}

	/**
	 * 检查软件是否有更新版本
	 * @return
	 */
	public void checkUpdate() {
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					InputStream inStream = FileUtils.getInputStreamFromUrl(URL+"/app/get-android-version/android.xml");
					// 解析XML文件。 由于XML文件比较小,因此使用DOM方式进行解析
					mHashMap = ParseXmlUtils.parseXml(inStream);
					
					if (null != mHashMap) {
						String size = mHashMap.get(ParseXmlUtils.Size);
						int length = StringUtils.isEmpty(size) ? 0 : Integer.parseInt(size);
						Map<String, Integer> map = new HashMap<String, Integer>();
						int serviceCode = Integer.valueOf(mHashMap.get(ParseXmlUtils.Version));
						map.put(VERSION, serviceCode);
						map.put(LENGTH, length);
						Message msg = new Message();
						msg.what = PARSE_XML;
						msg.obj = map;
						mHandler.sendMessage(msg);
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}).start();

	}

	/**
	 * 显示软件更新对话框
	 */
	private void showNoticeDialog(final int length) {
		// 构造对话框
		AlertDialog.Builder builder = new Builder(mContext);
		builder.setTitle(R.string.soft_update_title);
//		builder.setMessage(R.string.soft_update_info);
		//构建更新提示内容布局
		View view =  LayoutInflater.from(mContext).inflate(R.layout.softupdate_progress, null);
		TextView tvVersion = (TextView) view.findViewById(R.id.vesion_num_textview);
		TextView tvSize = (TextView) view.findViewById(R.id.file_size_textview);
		LinearLayout layout = (LinearLayout) view.findViewById(R.id.update_content_layout);
		//设置要更新至的版本号
		tvVersion.setText(mHashMap.get(ParseXmlUtils.VERSION_DESC));
		String size = Double.toString(((double)length) / (1000*1000));
		//获取更新文件的大小
		tvSize.setText(size.substring(0,size.indexOf(".") +3)+"M");
		//要更新的内容
		String [] updateContetnts = mHashMap.get(ParseXmlUtils.Description).split(";");
		for (int i = 0; i < updateContetnts.length; i++) {
			TextView tv = new TextView(mContext);
			tv.setText((i+1)+"."+updateContetnts[i]);
			tv.setTextSize(16);
			layout.addView(tv);
		}
		builder.setView(view);
		// 更新
		builder.setPositiveButton(R.string.soft_update_updatebtn,
				new OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						dialog.dismiss();
						// 显示下载对话框
						showDownloadDialog(length);
					}
				});
		// 稍后更新
		builder.setNegativeButton(R.string.soft_update_later,
				new OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						dialog.dismiss();
					}
				});
		Dialog noticeDialog = builder.create();
		noticeDialog.show();
	}

	/**
	 * 显示软件下载对话框
	 */
	@SuppressWarnings("deprecation")
	public void showDownloadDialog(int length) {
		
		mProgressDialog = new ProgressDialog(mContext);
//		mProgressDialog.setIcon(R.drawable.app_icon);
		
		mProgressDialog.setTitle(R.string.soft_updating);
		
//		按对话框以外的地方不起作用。按返回键也不起作用
		mProgressDialog.setCancelable(false);
		mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
		//设置文件大小的显示  --  使用
		mProgressDialog.setMax(length);
		
		mProgressDialog.setButton("取消",
				new DialogInterface.OnClickListener() {
					public void onClick(DialogInterface dialog,
							int whichButton) {
						// 这里添加点击后的逻辑
						mProgressDialog.dismiss();
						// 设置取消状态
						cancelUpdate = true;
					}
				});
		mProgressDialog.show();
		// 下载文件
		downloadApk();
	}

	/**
	 * 下载apk文件
	 */
	private void downloadApk() {
		// 启动新线程下载软件
		new downloadApkThread().start();
	}
	//下载APK的线程
	private class downloadApkThread extends Thread {
		@Override
		public void run() {
			try {
				// 判断SD卡是否存在,并且是否具有读写权限
				if (Environment.getExternalStorageState().equals(
						Environment.MEDIA_MOUNTED)) {
					// 获得存储卡的路径
					StringBuffer sb = new StringBuffer();
					sb.append(Environment.getExternalStorageDirectory()+ "/").append("download");
					mSavePath = sb.toString();
					UpdateDatas datas = FileConnectionUtils.getDatasFromUrl(mHashMap.get(ParseXmlUtils.Url));
					int length = datas.getLength();
					InputStream is = datas.getIn();
					File file = new File(mSavePath);
					// 判断文件目录是否存在
					if (!file.exists()) {
						file.mkdir();
					}
					File apkFile = new File(mSavePath, mHashMap.get(ParseXmlUtils.Name));
					FileOutputStream fos = new FileOutputStream(apkFile);
//					int count = 0;
					// 缓存
					byte buf[] = new byte[1024];
					// 写入到文件中
					do {
						int numread = is.read(buf);
						count = count + numread;
						// 计算进度条位置
						progress = (int) (((float) count / length) * 100);
//						Log.i("下载应用的进度", String.valueOf(progress));
						// 更新进度
						mHandler.sendEmptyMessage(DOWNLOAD);
						if (numread <= 0) {							// 下载完成
							mHandler.sendEmptyMessage(DOWNLOAD_FINISH);
							break;
						}
						// 写入文件
						fos.write(buf, 0, numread);
					} while (!cancelUpdate);// 点击取消就停止下载.
					fos.close();
					is.close();
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			// 取消下载对话框显示
			mProgressDialog.dismiss();
		}
	};

	/**
	 * 安装APK文件
	 */
	private void installApk() {
		File apkfile = new File(mSavePath, mHashMap.get(ParseXmlUtils.Name));
		if (!apkfile.exists()) {
			return;
		}
		// 通过Intent安装APK文件
		Intent i = new Intent(Intent.ACTION_VIEW);
		i.setDataAndType(Uri.parse("file://" + apkfile.toString()),"application/vnd.android.package-archive");
		mContext.startActivity(i);
	}
}

8、完善MainActivity

这个类中很简单,只是实现了按钮的点击事件。

具体代码实现如下:

package com.lyz.update.activity;

import com.lyz.utils.update.UpdateManager;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;

/**
 * 应用程序的入口
 * @author liuyazhuang
 *
 */
public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}
	
	public void update(View v){
		UpdateManager manager = new UpdateManager(this);
		manager.checkUpdate();
	}
}

五、应用授权

最后不要忘记我们的应用程序要联网操作,要给应用程序添加网络授权。

具体实现如下

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.lyz.update.activity"
    android:versionCode="2"
    android:versionName="0.2" >
 	<uses-permission android:name="android.permission.INTERNET" />
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.lyz.update.activity.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

六、运行效果

提醒:大家可以到http://download.csdn.net/detail/l1028386804/8906859链接获取完整Android应用更新示例代码

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冰 河

可以吃鸡腿么?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值