在市场下我们经常见到省流量更新,特别是游戏类的apk,假如一个游戏apk有100M,那我下次版本有110M,我是不是要下载这110M的apk呢?这显然在用户的角度是不合理的,增量更新就是只要用户下载这10M的差分包就可以了。
原理就是:1:在服务器端通过old.apk与新的new.apk进行比较,生成一个.patch文件的差分包。来共用户下载。
2:用户收到版本升级的提示框,就从服务器下载这个.patch差分包文件,在与当前安装的apk文件进行合成。生成一个新的apk文件,即:old.apk+.patch
3:用户在来替换安装这个新的apk文件,即old.apk+.patch
注意:如果已经上线运营的apk有1.1 1.2 1.3 三个版本了。那么需要升级1.4的时候,在服务器分别要用1.4与1.1 1.2 1.3做比较,生成3个差分包。
这里提供一个生成差分包的测试工具:http://download.csdn.net/detail/qq_17387361/9663516
1:首先新建一个old工程。即ver:1.0
package com.demo;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.Toast;
import com.example.incrementalupdates.R;
import com.nothome.delta.Delta;
import com.nothome.delta.DiffWriter;
import com.nothome.delta.GDiffWriter;
/**
* 增量更新
*
*
* */
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btnCreate = (Button) findViewById(R.id.btnCreate);
Button btnMix = (Button) findViewById(R.id.btnMix);
Button btnInstall = (Button) findViewById(R.id.btnInstall);
btnCreate.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
createPatch();
}
});
btnMix.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mixPatch();
}
});
btnInstall.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
installAPK();
}
});
}
/**
* 生成差分包:old_new.patch = diff(old.apk, old.apk)
*
* */
private void createPatch() {
try {
String sd = Environment.getExternalStorageDirectory().getPath();
String oldFile = sd + "/henry/old.apk";
String newFile = sd + "/henry/new.apk";
String patchFile = sd + "/henry/old_new.patch";
DiffWriter output = null;
File sourceFile = null;
File targetFile = null;
sourceFile = new File(oldFile);
targetFile = new File(newFile);
output = new GDiffWriter(new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(new File(
patchFile)))));
if (sourceFile.length() > Integer.MAX_VALUE
|| targetFile.length() > Integer.MAX_VALUE) {
System.err
.println("source or target is too large, max length is "
+ Integer.MAX_VALUE);
System.err.println("aborting..");
}
Delta d = new Delta();
d.compute(sourceFile, targetFile, output);
Toast.makeText(getApplicationContext(), "生成完成!", Toast.LENGTH_LONG)
.show();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 合成差分包:new.apk = old.apk + old_new.patch
* */
private void mixPatch() {
try {
String sd = Environment.getExternalStorageDirectory()
.getAbsolutePath();
String serviceFile = sd + "/henry/new.apk";
String source = sd + "/henry/old.apk";
String patch = sd + "/henry/old_new.patch";
String target = sd + "/henry/mix.apk";
String newMD5 = DiffTool.getMD5(new File(serviceFile));
DiffTool.mergeApk(source, patch, target, newMD5);
Toast.makeText(getApplicationContext(), "合成完成!", Toast.LENGTH_LONG)
.show();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 安装apk。 这边路径已经写死,实际应用中,apk路径需要当参数传入
* */
private void installAPK() {
File apkfile = new File(Environment.getExternalStorageDirectory()
.getAbsolutePath() + "/henry/mix.apk");
if (!apkfile.exists()) {
return;
}
Intent i = new Intent(Intent.ACTION_VIEW);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
i.setDataAndType(Uri.parse("file://" + apkfile.toString()),
"application/vnd.android.package-archive");
MainActivity.this.startActivity(i);
}
}
下面是检验MD5的工具类
package com.demo;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
import com.nothome.delta.GDiffPatcher;
public class DiffTool {
private final static char[] hexChar = { '0', '1', '2', '3', '4', '5', '6',
'7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
private static String toHexString(byte[] b) {
StringBuilder sb = new StringBuilder(b.length * 2);
for (int i = 0; i < b.length; i++) {
sb.append(hexChar[((b[i] & 0xF0) >>> 4)]);
sb.append(hexChar[(b[i] & 0xF)]);
}
return sb.toString();
}
public static String getMD5(File file) {
InputStream fis = null;
String str = null;
try {
fis = new FileInputStream(file);
byte[] buffer = new byte[1024];
MessageDigest md5 = MessageDigest.getInstance("MD5");
int numRead = 0;
while ((numRead = fis.read(buffer)) > 0) {
md5.update(buffer, 0, numRead);
}
str = toHexString(md5.digest());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fis != null) {
try {
fis.close();
} catch (Exception e) {
}
}
}
return str;
}
private static File mergeFile(final String source, final String patch,
String target) throws Exception {
GDiffPatcher patcher = new GDiffPatcher();
File deffFile = new File(patch);
File updatedFile = new File(target);
patcher.patch(new File(source), deffFile, updatedFile);
return updatedFile;
}
public static File mergeApk(final String source, final String patch,
final String target, String newApkMd5) throws Exception {
File updateFile = mergeFile(source, patch, target);
String ufpMd5 = getMD5(updateFile);
System.out
.println("服务端下发的md5:" + newApkMd5 + ",新合并后的apk MD5:" + ufpMd5);
if (ufpMd5 == null || !newApkMd5.equalsIgnoreCase(ufpMd5)) {
if (updateFile.exists()) {
updateFile.delete();
}
throw new Exception("MD5错误,不能成功合并!");
}
return updateFile;
}
public static void main(String args[]) throws Exception {
try {
System.out.println("old Md5:"
+ DiffTool.getMD5(new File("d:/diff/appstore2024.apk")));
File sourceFile = new File("d:/diff/appstore2017.apk");
File patchFile = new File("d:/diff/appstore.patch");
File outputFile = new File("d:/diff/appstore2025.apk");
if (sourceFile.length() > Integer.MAX_VALUE
|| patchFile.length() > Integer.MAX_VALUE) {
System.err
.println("source or patch is too large, max length is "
+ Integer.MAX_VALUE);
System.err.println("aborting..");
return;
}
GDiffPatcher patcher = new GDiffPatcher();
patcher.patch(sourceFile, patchFile, outputFile);
System.out.println("finished patching file");
} catch (Exception ioe) { // gls031504a
System.err.println("error while patching: " + ioe);
}
System.out.println("new Md5:"
+ DiffTool.getMD5(new File("d:/diff/appstore2025.apk")));
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.incrementalupdates"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="8"
android:targetSdkVersion="18" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.demo.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>
2:由于这里是服务器的步骤省略了,都放在客户端进行的,所以,先在手机内存卡中建一个henry文件夹用于存放差分包和当前安装的apk文件
这是old.apk的界面:
然后我们在在上面工程文件中界面改改,当做升级的新版本new.apk 记住versioncode和versionname要改一下.界面如下:
同样的,运行之后把new.apk和old.apk和差分包.patch文件一同放入henry文件夹下面。现在我们可以开始做差分包了。
差分包工具博文开始处有下载地址。里面的使用写的很清楚了。
现在我们运行old.APK文件,这里可以点击生成差分包按钮,也可以不点击,因为我们用工具已经生成了差分包。
那个mix.apk就是差分包和old.apk合成的新的apk,最后直接替换安装即可。
最后供上我在网上找的一个jar包,此项目中所需要的:http://download.csdn.net/detail/qq_17387361/9663633