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应用更新示例代码

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-23 22:34:52

Android之——应用更新功能的相关文章

Android应用自动更新功能的实现!!!

自动更新功能的实现原理,就是我们事先和后台协商好一个接口,我们在应用的主Activity里,去访问这个接口,如果需要更新,后台会返回一些数据(比如,提示语:最新版本的url等).然后我们给出提示框,用户点击开始下载,下载完成开始覆盖安装程序,这样用户的应用就保持最新的拉. 为了让大家容易理解,我像往常一样准备一个小例子,这里为了方便我就省去了和后台交互部分了.步骤分别如下: 第一步:新建一个Android工程命名为:UpdateDemo.代码结构如下图所示: 第二步:新建一个UpdateMana

Android应用自动更新功能的代码实现

由于Android项目开源所致,市面上出现了N多安卓软件市场.为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量.因此我们有必要给我们的Android应用增加自动更新的功能. 既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软件的版本信息: <update> <version>2</version> <name>baidu

Android 应用自动更新功能的代码

由于Android项目开源所致,市面上出现了N多安卓软件市场.为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量.因此我们有必要给我们的Android应用增加自动更新的功能. 既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软件的版本信息: <update> <version>2</version> <name>baidu

[转]Android应用自动更新功能的代码实现

本文转自:http://www.cnblogs.com/coolszy/archive/2012/04/27/2474279.html 由于Android项目开源所致,市面上出现了N多安卓软件市场.为了让我们开发的软件有更多的用户使用,我们需要向N多市场发布,软件升级后,我们也必须到安卓市场上进行更新,给我们增加了工作量.因此我们有必要给我们的Android应用增加自动更新的功能. 既然实现自动更新,我们首先必须让我们的应用知道是否存在新版本的软件,因此我们可以在自己的网站上放置配置文件,存放软

Android App补丁更新

上一周比较忙,忙的不可开交,写的文章也就两篇,在此希望大家见谅.这周呢,突然闲下来了,有时间了,就重构了下代码,捣鼓点前卫的技术,沉淀沉淀.所以呢,今天就分享下这几天研究的东西. 移动互联网主打的就是用户体验和产品的快速迭代,通过用户反馈和用户行为跟踪及时调整产品方向,这样才能持续保持生命力和创造力.说的接地气点就是,你频繁的升级更新,有时只是修复了几个bug或者微调了下界面,就让用户下载10几兆甚至更大的apk,而且在目前国内这个4G还不是普及的时候,对用户来说是很不友好的.有没有这样一种策略

Android接入Google+分享功能

Android使用Google+分享功能 1.首先将<android-sdk-folder>/extras/google/google_play_services 导入到Eclipse中 如果没有这个工程首先更新到最新的ADT,然后打开Android SDK Manager进行下载 2.然后将其作为lib工程add到我们的工程上 3.主要的分享代码: //google+分享 public void doGooglePlusShare(Activity mContext) { // 判断是否安装

[Android 新特性] Android 4.3新功能(正式发布前)

腾讯数码讯(编译:徐萧梓丞)虽然谷歌公司目前尚未正式对外发布最新的Android 4.3果冻豆操作系统,但是在上周我们已经看到了关于三星正 在为原生版Galaxy S4进行Android 4.3系统进行测试的消息.虽然Android 4.3本身看起来并没有令人非常重大的升级,但是谷歌公司已经开始对旗下包括Gmail.日历.键盘.游戏服务等众多应用进行升级以支持即将到来的 Android 4.3操作系统. 到目前为止,已经对外确认的Android 4.3新特性包括以下几点: Wi-Fi后台自动搜索

Eclipse Android 代码自动提示功能

对于一个后端管理系统,最重要内容之一的就是登陆页了,无论是安全验证.用户在线记录.相关日志记录.单用户或多用户使用帐号控制等,都是在这个页面进行处理的. 1.在解决方案中创建一个Web项目,并将它设置为启动项 2.添加引用 3.添加WebManage文件夹与Login.aspx文件 4.添加登陆页面HTML代码 1 <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx

简单实现安卓app自动更新功能

一般的安卓app都有自动更新功能,实现app的更新,以让用户体验新版本的功能,这里也是项目中用到的,今天就来总结一下,代码应该有点多,还请耐心点哈. 安卓应用实现自动更新比较简单,这里跟大家介绍下: 第一步 服务器端: 服务端提供一个借口,或者网址,我这里就用的服务器是tomcat,这里提供一个网址如下: //也就是一个json数据接口 public static final String UPDATE_URL = "http://192.168.1.103:8080/update.json&q