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;
相关文章推荐
- Adapter模式实战-重构鸿洋的Android建行圆形菜单
- “煎蛋”Android版的高仿GitHub路径
- ExpandableListView的使用
- Android统计图表MPAndroidChart
- Android 数据库管理— — —升级数据库
- Android 把图片进行压缩
- android支持多行的radiogroup
- Android学习之Timer和TimerTask
- 注册谷歌帐号以及用其他镜像解决android sdk的下载问题(已解决)
- Android消息推送解决方案
- 41.Android之图片放大缩小学习
- Android开发中java.lang.RuntimeException: Unable to start activity ComponentInfo{xxx}: java.lang.NullPoi
- android开机启动Service
- Android-SDK-Windows的sdk manager闪退
- Android 数据库管理— — —创建数据库
- android开发利器--站在巨人肩膀上前行
- 基于Android的可视化自动化脚本编辑和维护功能
- Android中使用Handler造成内存泄露的分析和解决
- android内存泄漏分析的一种方式
- Android学习笔记——4种Activity之间的数据传递方式的实现