您的位置:首页 > 移动开发 > Android开发

android差异化更新(增量更新)

2016-03-09 16:59 525 查看

前提

自从 Android 4.1 开始,Google引入了应用程序的增量更新。

注意事项

增量更新的前提条件,是在手机客户端能让我们读取到当前应用程序安装后的源apk,如果获取不到源apk,那么就无法进行增量更新了。

另外,如果你的应用程序不是很大,比如只有2、3M,那么完全没有必要使用增量更新,增量更新适用于apk包比较大的情况,比如游戏客户端。

原理

首先将应用的旧版本Apk与新版本Apk做差分,得到更新的部分的补丁,例如旧版本的APK有5M,新版的有8M,更新的部分则可能只有3M左右(这里需要说明的是,得到的差分包大小并不是简单的相减,因为其实需要包含一些上下文相关的东西),使用差分升级的好处显而易见,那么你不需要下载完整的8M文件,只需要下载更新部分就可以,而更新部分可能只有3、4M,可以很大程度上减少流量的损失。

在用户下载了差分包之后,需要在手机端将他们组合起来。可以参考的做法是先将手机端的旧版本软件(多半在/data/下),复制到SD卡或者cache中,将它们和之前的差分patch进行组合,得到一个新版本的apk应用,如果不出意外的话,这个生成的apk和之前做差分的apk是一致的。

过程

生成差分包

这一步需要在服务器端来实现,一般来说,每当apk有新版本需要提示用户升级,都需要运营人员在后台管理端上传新apk,上传时就应该由程序生成之前所有旧版本们与最新版的差分包。

例如: 你的apk已经发布了3个版,V1.0、V2.0、V3.0,这时候你要在后台发布V4.0,那么,当你在服务器上传最新的V4.0包时,服务器端就应该立即生成以下差分包:

V1.0 ——> V4.0的差分包;

V2.0 ——> V4.0的差分包;

V3.0 ——> V4.0的差分包;
校验新合成的apk文件

新包和成之后,还需要对客户端合成的apk包与最新版本apk包进行MD5或SHA1校验,如果校验码不一致,说明合成过程有问题,新合成的包将不能被安装。

流程图:

待补充

解释:

1、把当前版本的versionName和versionCode发给服务器,服务器根据版本信息把后台的更新信息发给客户端;

2、判断是否有更新;

3、没有更新则结束,有更新则从服务器获取补丁包;

4、用补丁包和当前包合成新包;

5、验证新包签名是否成功;

6、验证成功则安装新包;

7、验证失败则提醒用户是否下载最新版本的整包;

8、直接下载整包的流程?

客户端代码:

主activity MainActivity.java:

package com.example.smartupdatedemo;

import com.example.smartupdatedemo.util.CheckUpdate;
import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

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

// 检查更新
CheckUpdate.checkUpdate(MainActivity.this);
}
}


PatchUtils.java

package com.cundong.utils;

/**
* 类说明:   APK Patch工具类
*
* @author  Cundong
* @date    2013-9-6
* @version 1.0
*/
public class PatchUtils {

/**
* native方法
* 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath
* @param oldApkPath
* @param newApkPath
* @param patchPath
* @return
*/
public static native int patch(String oldApkPath, String newApkPath,
String patchPath);
}


CheckUpdate.java

package com.example.smartupdatedemo.util;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import org.json.JSONException;
import org.json.JSONObject;

import com.cundong.utils.PatchUtils;
import com.example.test.util.ApkUtils;
import com.example.test.util.SignUtils;

import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

/**
* 检查更新
* @author huangy2
*
*/
public class CheckUpdate {
static {
System.loadLibrary("ApkPatchLibrary");
Log.d("CheckUpdate", "loadLibrary");
}

private static Context context;
private static ProgressDialog mProgressDialog;
public static String version; // 版本号
public static String versionNew;  // 新版本号
public static String newVersionUrl;  // 新版本Url
public static String newVersionSize;  // 新版本大小 MB

public static void checkUpdate(Context context){
CheckUpdate.context = context;
try {
// 获取当前程序版本号
version = getVersionName(context);
int versionCode = getVersionCode(context);
Log.d("smartupdatedemo", "version=" +version);
// 与服务器交互是否更新版本
Map<String, String> map = new HashMap<String, String>();
map.put("version", version);
map.put("versionCode", String.valueOf(versionCode));
// 启动检查更新请求线程
new PostThread("http://10.3.122.44/update.php",map).start();
} catch (Exception e) {
e.printStackTrace();
}
}

private static Handler mHandler = new Handler(){
long mBeginTime = 0;
long mEndTime = 0;

public void handleMessage(android.os.Message msg) {
switch (msg.what) {
// 下载整包完成,直接安装
case ConstantUtil.DOWNLOAD_FINISH_INSTALL:
File installFile = (File) msg.obj;
ApkUtils.installApk(CheckUpdate.context, installFile.getAbsolutePath());
break;
// 无法获取旧的源apk文件
case ConstantUtil.NO_OLD_APK:
Toast.makeText(CheckUpdate.context,"无法获取旧的源apk文件",Toast.LENGTH_LONG).show();
break;
// 合成成功
case ConstantUtil.COMPOSITE_SUCCESS:
String newApkPath = (String) msg.obj;
// 获取未安装Apk的签名
String signatureNew = SignUtils.getUnInstalledApkSignature(newApkPath);
// 获取已安装apk签名
String signatureSource = SignUtils.InstalledApkSignature(CheckUpdate.context,CheckUpdate.context.getPackageName());
// 只有我们合成后的新apk包的签名和服务端拆分前的原apk签名一致,才是正确的
if ( !TextUtils.isEmpty(signatureNew) && !TextUtils.isEmpty(signatureSource) && signatureNew.equals(signatureSource)) {
Toast.makeText(CheckUpdate.context,"新apk已合成成功:" + newApkPath,Toast.LENGTH_LONG).show();

mEndTime = System.currentTimeMillis();
Log.d("CheckUpdate", "耗时: " + (mEndTime - mBeginTime) + "ms");
// 安装新包
ApkUtils.installApk(CheckUpdate.context, newApkPath);
} else {
//					Toast.makeText(CheckUpdate.context, "新apk已合成失败,签名不一致",Toast.LENGTH_LONG).show();
Log.d("CheckUpdate", "新apk已合成失败,签名不一致");
// 补丁合成新包失败,从新下载整包安装
compositeFailAndInstallWhole();
}
break;
// 合成失败
case ConstantUtil.COMPOSITE_FAIL:
Toast.makeText(CheckUpdate.context, "新apk已合成失败", Toast.LENGTH_LONG).show();
break;
// 下载更新包完成,与旧版本合成新版本
case ConstantUtil.DOWNLOAD_FINISH:
// 合成apk进度循环框
mProgressDialog = new ProgressDialog(CheckUpdate.context);
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
mProgressDialog.setMessage("新版Apk合成中,请稍候...");
mProgressDialog.setCancelable(false);
mProgressDialog.show();

// 更新包
File file = (File) msg.obj;
// 启动apk合成线程
new PatchThread(file).start();
break;
// 检查更新
case ConstantUtil.CHECK_UPDATE:
String json = (String) msg.obj;
try {
final JSONObject obj = new JSONObject(json);
// 补丁更新
if ("patch".equals(obj.getString("isUpdate"))) {
// 更新文字信息
StringBuilder sb = new StringBuilder();
sb.append("更新简介:")
.append(obj.getString("introduction"))
.append("\n")
.append("更新包大小:")
.append(obj.getString("package_size"))
.append("MB")
.append("\n")
.append("新版版本号:")
.append(obj.getString("version"));
// 新版本号
versionNew = obj.getString("version");
// 新版本URL
newVersionUrl = obj.getString("newVersionUrl");
newVersionSize = obj.getString("newVersionSize");
// 弹出的更新提示对话框
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("版本更新提示");
builder.setMessage(sb.toString());
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
try {
// 更新包下载地址
String url = obj.getString("url");
//									version = obj.getString("version");
// 启动下载线程
new DownloadThread(url, version, ConstantUtil.PATCH_EXTENSION_NAME).start();
} catch (JSONException e) {
e.printStackTrace();
}
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {

}
});
builder.create().show();
// 整包更新
}else if ("yes".equals(obj.getString("isUpdate"))) {
// 更新文字信息
StringBuilder sb = new StringBuilder();
sb.append("更新简介:")
.append(obj.getString("introduction"))
.append("\n")
.append("包大小:")
.append(obj.getString("package_size"))
.append("MB")
.append("\n")
.append("新版版本号:")
.append(obj.getString("version"));
// 新版本号
versionNew = obj.getString("versionNew");
// 新版本URL
newVersionUrl = obj.getString("newVersionUrl");
newVersionSize = obj.getString("newVersionSize");
// 弹出的更新提示对话框
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle("版本更新提示");
builder.setMessage(sb.toString());
builder.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
try {
// 更新包下载地址
String url = obj.getString("url");
//										version = obj.getString("version");
// 启动下载线程
new DownloadThread(url, version, ConstantUtil.PATCH_EXTENSION_NAME).start();
} catch (JSONException e) {
e.printStackTrace();
}
}
});
builder.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {

}
});
builder.create().show();
}
} catch (JSONException e) {
e.printStackTrace();
}
break;

default:
break;
}
};
};

/**
* apk合成,线程
*/
public static class PatchThread extends Thread {
File file;

public PatchThread(File file){
this.file = file;
}

@Override
public void run() {
Looper.prepare();

// 旧spk包路径
String oldApkSource = ApkUtils.getSourceApkPath(CheckUpdate.context, CheckUpdate.context.getPackageName());

if (!TextUtils.isEmpty(oldApkSource)) {
// 合成的新apk包将要放在那里
String newApkPath = file.getParent();
File newApkFile = new File(newApkPath, CheckUpdate.versionNew + ".apk");
//				if (!newApkFile.exists()) {
//					try {
//						newApkFile.createNewFile();
//					} catch (IOException e) {
//						// TODO Auto-generated catch block
//						e.printStackTrace();
//					}
//				}
//				newApkFile.delete();
// 补丁包存放路径
String patchPath = file.getAbsolutePath();
Log.d("CheckUpdate", "newApkPath = " + newApkFile.getAbsolutePath() + "; patchPath = " + patchPath);
int patchResult = PatchUtils.patch(oldApkSource, newApkFile.getAbsolutePath(), patchPath);

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 隐藏合成进度框
mProgressDialog.dismiss();
Message msg = new Message();
if (patchResult == 0) {
msg.what = ConstantUtil.COMPOSITE_SUCCESS;
msg.obj = newApkFile.getAbsolutePath();
mHandler.sendMessage(msg);
} else {
msg.what = ConstantUtil.COMPOSITE_FAIL;
msg.obj = newApkPath;
mHandler.sendMessage(msg);
}
} else {
mHandler.sendEmptyMessage(ConstantUtil.NO_OLD_APK);
}

Looper.loop();
//			super.run();
}
}

/**
* 下载文件线程
* @author huangy2
*
*/
public static class DownloadThread extends Thread{
String url; // 下载URL
String version; // 版本号
String extensionName;
File cache; // 缓存文件夹

public DownloadThread(String url, String version, String extensionName){
this.url = url;
this.version = version;
this.extensionName = extensionName;
}

@Override
public void run() {

try {
cache = new File(Environment.getExternalStorageDirectory(), "cache");
// 缓存地址
Log.d("CheckUpdate", "file path = " + cache.getAbsolutePath());
if (!cache.exists()) {
cache.mkdirs();
if (!cache.exists()) {
Log.d("CheckUpdate", "create File fail.");
return;
}
}
// 清空文件夹
//				File[] childFiles = cache.listFiles();
//				for (File file : childFiles) {
//					file.delete();
//				}
// 目录 + 版本号 + 后缀
File file = new File(cache, version + extensionName);
// 文件已经存在
if (file.exists()) {
Log.d("CheckUpdate", "file already exist.");
// 文件已经存在旧删除,不做缓存
file.delete();
//					// 发送消息
//					Message msg = new Message();
//					// 直接下载,进入安装
//					if (version == CheckUpdate.versionNew) {
//						msg.what = ConstantUtil.DOWNLOAD_FINISH_INSTALL;
//						msg.obj = file;
//						mHandler.sendMessage(msg);
//						return;
//					}
//					// 下载的补丁包,进入合成流程
//					msg.what = ConstantUtil.DOWNLOAD_FINISH;
//					msg.obj = file;
//					mHandler.sendMessage(msg);
//					return;
}
// 补丁包存放地址
Log.d("CheckUpdate", "patch path = " + file.getAbsolutePath());
// 文件不存在则去服务端下载
// 创建链接
URL url = new URL(this.url);
// 打开连接
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
InputStream inputStream = conn.getInputStream();
OutputStream outputStream = new FileOutputStream(file);
//				byte[] buffer = new byte[1024];
byte[] buffer = new byte[1];
while (inputStream.read(buffer) > 0) {
outputStream.write(buffer);
}
// 关闭流
outputStream.flush();
outputStream.close();
inputStream.close();
// 发送消息
Message msg = new Message();
// 如果直接下载的整包,直接下载,进入安装
if (version == CheckUpdate.versionNew) {
msg.what = ConstantUtil.DOWNLOAD_FINISH_INSTALL;
msg.obj = file;
mHandler.sendMessage(msg);
return;
}
msg.what = ConstantUtil.DOWNLOAD_FINISH;
msg.obj = file;
mHandler.sendMessage(msg);
Log.d("CheckUpdate", "更新包下载完成");
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
super.run();
}
}

/**
* 检查更新请求线程
* @author huangy2
*
*/
public static class PostThread extends Thread{
// 请求参数
private Map<String, String> map;
private String url;

public PostThread(String url, Map<String, String> map){
this.url = url;
this.map = map;
}

@Override
public void run() {
// 检查更新POST请求
String result = HttpUtil.httpPost(url, map);
Message msg = new Message();
msg.what = ConstantUtil.CHECK_UPDATE;
msg.obj = result;
mHandler.sendMessage(msg);
Log.d("smartupdatedemo", result);
super.run();
}
}

/**
* 获取当前程序的版本号
*/
private static String getVersionName(Context context) throws Exception{
//获取packagemanager的实例
PackageManager packageManager = context.getPackageManager();
//getPackageName()是你当前类的包名,0代表是获取版本信息
PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
return packInfo.versionName;
}

/**
* 获取当前程序的版本号 编码
*/
private static int getVersionCode(Context context) throws Exception{
//获取packagemanager的实例
PackageManager packageManager = context.getPackageManager();
//getPackageName()是你当前类的包名,0代表是获取版本信息
PackageInfo packInfo = packageManager.getPackageInfo(context.getPackageName(), 0);
return packInfo.versionCode;
}

/**
* 补丁合成新包失败,从新下载整包安装
*/
public static void compositeFailAndInstallWhole(){
// 提示用户下载整包安装 TODO
StringBuilder sbNew = new StringBuilder();
sbNew.append("省流量升级失败,请下载整包安装")
.append("\n")
.append("整包大小:")
.append(newVersionSize)
.append("MB")
.append("\n")
.append("新版版本号:")
.append(versionNew);
// 弹出的更新提示对话框
AlertDialog.Builder builderNew = new AlertDialog.Builder(context);
builderNew.setTitle("提示");
builderNew.setMessage(sbNew.toString());
builderNew.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {
// 启动下载线程
new DownloadThread(newVersionUrl, versionNew, ConstantUtil.APK_EXTENSION_NAME).start();
}
});
builderNew.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface arg0, int arg1) {

}
});
builderNew.create().show();
}
}


ConstantUtil.java

package com.example.smartupdatedemo.util;

public class ConstantUtil {
public final static int CHECK_UPDATE = 10; // 检查更新
public final static int DOWNLOAD_FINISH = 11; // 下载完成
public final static int DOWNLOAD_FINISH_INSTALL = 12; // 下载完成,进入安装

public final static int NO_OLD_APK = -9999; // 无法获取旧的源apk文件
public final static int COMPOSITE_SUCCESS = 0; // 合成成功
public final static int COMPOSITE_FAIL = -1; // 合成失败

public final static String PATCH_EXTENSION_NAME = ".patch";  // 补丁包后缀名
public final static String APK_EXTENSION_NAME = ".apk";  // APK包后缀名
}


HttpUtil.java

package com.example.smartupdatedemo.util;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
* @memo http请求工具类
* */
public class HttpUtil {

/**
* @memo 发送http的post请求
* */
public static String httpPost(String url, final Map<String, String> params) {
try {
HttpClient httpclient = new DefaultHttpClient();

// 请求超时
httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 20000);
// 读取超时
httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 20000);
HttpPost httppost = new HttpPost(url);
List<NameValuePair> paramList = new ArrayList<NameValuePair>();
for (String key : params.keySet()) {
// 封装请求参数
paramList.add(new BasicNameValuePair(key, params.get(key)));
}
httppost.setEntity(new UrlEncodedFormEntity(paramList, HTTP.UTF_8));

HttpResponse response;
response = httpclient.execute(httppost);

// 检验状态码,如果成功接收数据
int code = response.getStatusLine().getStatusCode();

if (code == 200) {
String rev = EntityUtils.toString(response.getEntity());
System.out.println(rev);
return rev;
}
}catch (Exception e) {
e.printStackTrace();
return null;
}
return null;
}

/**
* 字符串转list
* @param str
*/
public static List<String> str2list(String str, String name){
List<String> list = new ArrayList<String>();
try {
JSONArray json = new JSONArray(str);
for (int i = 0; i < json.length(); i++) {
JSONObject row = new JSONObject(json.getString(i));
list.add(row.getString(name));
}
} catch (JSONException e) {
e.printStackTrace();
}
return list;
}

}


ApkUtils.java

package com.example.test.util;

import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.text.TextUtils;

/**
* apk 工具类
*
*/
public class ApkUtils {

public static boolean isInstalled(Context context, String packageName) {
PackageManager pm = context.getPackageManager();
boolean installed = false;
try {
pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES);
installed = true;
} catch (Exception e) {
e.printStackTrace();
}

return installed;
}

public static String getSourceApkPath(Context context, String packageName) {
if (TextUtils.isEmpty(packageName))
return null;

try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
return appInfo.sourceDir;
} catch (NameNotFoundException e) {
e.printStackTrace();
}

return null;
}

/**
* 安装APK
*
* @param context
* @param apkPath
*/
public static void installApk(Context context, String apkPath) {

Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + apkPath),
"application/vnd.android.package-archive");

context.startActivity(intent);
}
}


SignUtils.java

package com.example.test.util;

import java.io.File;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.List;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.util.DisplayMetrics;

/**
* apk 签名工具类
*
*/
public class SignUtils {

/**
* 获取未安装Apk的签名
*
* @param apkPath
* @return
*/
public static String getUnInstalledApkSignature(String apkPath) {
String PATH_PackageParser = "android.content.pm.PackageParser";
try {
// apk包的文件路径
// 这是一个Package 解释器, 是隐藏的
// 构造函数的参数只有一个, apk文件的路径
Class<?> pkgParserCls = Class.forName(PATH_PackageParser);
Class<?>[] typeArgs = new Class[1];
typeArgs[0] = String.class;
Constructor<?> pkgParserCt = pkgParserCls.getConstructor(typeArgs);
Object[] valueArgs = new Object[1];
valueArgs[0] = apkPath;
Object pkgParser = pkgParserCt.newInstance(valueArgs);

// 这个是与显示有关的, 里面涉及到一些像素显示等等, 我们使用默认的情况
DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
// PackageParser.Package mPkgInfo = packageParser.parsePackage(new
// File(apkPath), apkPath,
// metrics, 0);
typeArgs = new Class[4];
typeArgs[0] = File.class;
typeArgs[1] = String.class;
typeArgs[2] = DisplayMetrics.class;
typeArgs[3] = Integer.TYPE;
Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod(
"parsePackage", typeArgs);
valueArgs = new Object[4];
valueArgs[0] = new File(apkPath);
valueArgs[1] = apkPath;
valueArgs[2] = metrics;
valueArgs[3] = PackageManager.GET_SIGNATURES;
Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser,
valueArgs);

typeArgs = new Class[2];
typeArgs[0] = pkgParserPkg.getClass();
typeArgs[1] = Integer.TYPE;
Method pkgParser_collectCertificatesMtd = pkgParserCls
.getDeclaredMethod("collectCertificates", typeArgs);
valueArgs = new Object[2];
valueArgs[0] = pkgParserPkg;
valueArgs[1] = PackageManager.GET_SIGNATURES;
pkgParser_collectCertificatesMtd.invoke(pkgParser, valueArgs);
// 应用程序信息包, 这个公开的, 不过有些函数, 变量没公开
Field packageInfoFld = pkgParserPkg.getClass().getDeclaredField(
"mSignatures");
Signature[] info = (Signature[]) packageInfoFld.get(pkgParserPkg);
return info[0].toCharsString();
} catch (Exception e) {
e.printStackTrace();
}

return null;
}

/**
* 获取已安装apk签名
*
* @param context
* @param packageName
* @return
*/
public static String InstalledApkSignature(Context context,
String packageName) {
PackageManager pm = context.getPackageManager();
List<PackageInfo> apps = pm
.getInstalledPackages(PackageManager.GET_SIGNATURES);

Iterator<PackageInfo> iter = apps.iterator();
while (iter.hasNext()) {
PackageInfo packageinfo = iter.next();
String thisName = packageinfo.packageName;
if (thisName.equals(packageName)) {
return packageinfo.signatures[0].toCharsString();
}
}

return null;
}
}


服务端代码:

update.php:

<?php
require_once(dirname(__FILE__).'/config/config.php');  //配置
require_once(dirname(__FILE__).'/library/JSON.php');   // JSON

// 是否传递版本号过来
if(isset($_POST['version'])){
$newVersion; // 最新版本
$newVersionUrl; // 最新版本URL
$newVersionSize; // 最新版本大小
$minSmartUpdateVersionCode; // 最低增量版本编码
$version = $_POST['version'];
$versionCode = $_POST['versionCode'];

// 判断版本号是否为空
if($version != ""){
// 连接数据库
$conn = mysql_connect(DB_HOST,DB_USERNAME,DB_PASSWORD);
if(! $conn){
echo "connect database fail";
die();
}
// 选择数据库
mysql_select_db(DB_DATABASE,$conn);
/*
判断是否有更新
*/
$sql = "select * from smart_update where is_newest = '1'";
// 执行SQL语句
$result = mysql_query($sql,$conn);
while($row = mysql_fetch_array($result)){
$newVersion = $row['version'];
$newVersionUrl = $row['url'];
$newVersionSize = $row['package_size'];
$minSmartUpdateVersionCode = $row['min_smart_update_version_code'];
}
// 已是最新版本,无需更新
if($version == $newVersion){
//返回内容,JSON形式
$back = array();
$back['isUpdate'] = "no";
echo JSON($back);
die();
// 如果低于最低可增量版本,则下载整个包
}else if($versionCode < $minSmartUpdateVersionCode){
// 最新版本
//			$sql = "select * from smart_update where version = '".$result['version']."'";
// 执行SQL语句
//			$result = mysql_query($sql,$conn);

while($row = mysql_fetch_array($result)){
//返回内容,JSON形式
$back = array();
$back['isUpdate'] = "yes";
$back['versionNew'] = $newVersion;
$back['introduction'] = $row['introduction'];
$back['newVersionSize'] = $newVersionSize;
$back['newVersionUrl'] = $newVersionUrl;
echo JSON($back);
die();
}
}
// 不是最新版本,根据version下载对应的增量包
$sql = "select * from smart_update where version = '".$version."'";
// 执行SQL语句
$result = mysql_query($sql,$conn);
while($row = mysql_fetch_array($result)){
//返回内容,JSON形式
$back = array();
$back['isUpdate'] = "patch";
$back['version'] = $newVersion;
$back['introduction'] = $row['introduction'];
$back['package_size'] = $row['package_size'];
$back['url'] = $row['url'];
$back['newVersionUrl'] = $newVersionUrl;
$back['newVersionSize'] = $newVersionSize;
echo JSON($back);
die();
}
}else{
echo "version must not null";
die();
}
}else{
echo "miss version";
die();
}
?>


library/JSON.php:

<?php
/**************************************************************
*
*	使用特定function对数组中所有元素做处理
*	@param	string	&$array		要处理的字符串
*	@param	string	$function	要执行的函数
*	@return boolean	$apply_to_keys_also		是否也应用到key上
*	@access public
*
*************************************************************/
function arrayRecursive(&$array, $function, $apply_to_keys_also = false)
{
foreach ($array as $key => $value) {
if (is_array($value)) {
arrayRecursive($array[$key], $function, $apply_to_keys_also);
} else {
$array[$key] = $function($value);
}

if ($apply_to_keys_also && is_string($key)) {
$new_key = $function($key);
if ($new_key != $key) {
$array[$new_key] = $array[$key];
unset($array[$key]);
}
}
}
}
/**************************************************************
*
*	将数组转换为JSON字符串(兼容中文)
*	@param	array	$array		要转换的数组
*	@return string		转换得到的json字符串
*	@access public
*
*************************************************************/
function JSON($array) {
arrayRecursive($array, 'urlencode', true);
$json = json_encode($array);
return urldecode($json);
}

?>


数据库:

CREATE TABLE `smart_update` (
`id` INT(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY(`id`),
`version` VARCHAR(7) COMMENT '版本号',
`introduction` TEXT COMMENT '该版本简介,升级内容',
`min_smart_update_version` VARCHAR(7) COMMENT '该版本对应最低可增量更新版本',
`package_size` DOUBLE COMMENT '更新包大小',
`url` VARCHAR(100) COMMENT '更新包URL'
`is_newest` TINYINT DEFAULT 1 COMMENT '0-不是最新,1-最新'
)
COMMENT '增量更新表'
ENGINE=InnoDB DEFAULT CHARSET=utf8;
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: