全民k歌怎样升级快里的升级套餐什么意思

全民k歌中的S是什么意思_百度知道全民K歌增量升级方案_QQ音乐技术团队_传送门
全民K歌增量升级方案
QQ音乐技术团队
  本文主要介绍一种增量升级方案。用户在升级版本时,不需要下载完整的安装包,只需下载增加的部分即可体验新版本完整功能,即节约用户流量,也减少服务器流量,并解决了多渠道问题,值得尝试。一、背景  随着全民K歌版本不断迭代,安装包大小也不断增大,现在每次版本更新,用户都需要下载最新版本安装包,如果使用增量更新的方式,用户每次更新只下载新版本和旧版本差异的部分,将会为用户和服务器节约大量流量。以全民K歌3.2和3.3版本为例:| 文件名
| 文件大小 ||———-
|| karaoke_3.2.apk
| 30.4M || karaoke_3.3.apk
| 27.6M || 3.2_3.3.patch
|  3.2_3.3.patch文件是3.2和3.3版本的差异部分,大小为7.3M,如果用户使用增量升级方案,相对于下载完整的3.3版本27.6M,用户将节约20.3M。下面我将介绍如何使用用户本地已安装的版本karaoke_3.2.apk + 差异包3.2_3.3.patch生成最新版本karaoke_3.3.apk。二、实现原理1、服务器端:2、客户端:  增量更新的原理是将旧版本的apk和新版本的apk进行二进制对比,得到差异包,用户升级更新时,根据本地版本从服务器下载需要的差分包,使用本地版本+差分包生成新版apk。而差异包需要提前由服务器生成,用户在升级时,服务器根据用户当前版本下发差异包。列如:用户从全民K歌3.2版本升级到3.3版本,需要从服务器下载差异包(3.2_3.3.patch),再使用用户正在使用的全民K歌3.2版本apk(karaoke_3.2.apk),即可生成全民K歌3.3版本(karaoke_3.3.apk)。三、实现步骤1、生成差异包  apk文件的差分和合并都是使用的开源的二进制比较工具 bsdiff 实现。下载的bsdiff-4.3版本中有几个文件,其中bsdiff.c用于生成差异包的源码,bspatch.c用于合成apk的源码,makefile是生成可执行文件的脚本。亲测在linux系统中,执行makefile文件,可生成一个bsdiff工具,使用该工具即可生成差异包。  在服务器端使用bsdiff工具生成差异包。其中karaoke_3.2.apk和karaoke_3.3.apk是我们的老版本和新版本安装包(都未写入渠道号)。在命令行执行./bsdiff karaoke_3.2.apk karaoke_3.3.apk 3.2_3.3.patch 命令即可生成差异包3.2_3.3.patch。2、解决多渠道问题(1)多渠道说明  多渠道是指根据不同的市场打不同的安装包包,比如应用宝,安卓市场,百度市场,Google市场,360市场等等。分渠道打包目的是为了针对不同市场做出不同的一些统计,数据分析,收集用户信息。多渠道的实现通常是在生成安装包的时候,把渠道号写入安装包的渠道文件中,用户在使用app时,读取安装包的渠道文件内容,并上传服务器。例如应用宝渠道,则在安装包中有一个qua.ini文件,里面内容是YYB_D,用户在使用APP时,读取qua.ini文件内容,把YYB_D上传服务器。  由实现原理可以看出,服务器端需要两个安装包对比,然后才能生成差异包。在生成安装包的时候,不同渠道的安装包内容是不一样的(文件md5值不一样),不同渠道的新老版本生成的差异包也不一样。解决这个问题比较粗暴的方案是:每个渠道都生成一个差异包,客户端合成的时候,根据用户使用的渠道安装包下载对应渠道的差异包,再合成对应渠道最新的安装包。这时如果有50个渠道,就需要50个差异包,这个方案实现复杂,不利于差异包的维护。  这里微信团队提出了另一种实现方案:把渠道号写入安装包的注释字段。该方案不会破坏安装包,经验证,android手机可以正常安装使用。Android apk安装包是zip格式文件,在zip文件的最后有一个记录说明。格式如下:  从表中可以看出,在文件的末尾有两个字段:Comment length和Comment,分别表示注释长度(2个字节)和注释内容(N个字节)。apk安装包打包完成后, Comment length默认为0,comment为空,我们可以把入渠道号写入comment字段。app启动后,读取Comment内容即可获取渠道号。(2)安装包未写入渠道号时:  从图中可以看出,Comment length=0,说明这个安装包未写入任何注释。在使用gradle编译打包生成的apk默认是没有写入任何注释信息的。(3)安装包写入应用宝(YYB_D)渠道号时:  从图中可以看出,Comment length=12,说明这个安装包的注释长度为12个字节。(为了方便定位渠道号,除了渠道号,在文件末尾多写了7个字节内容)12 = 5 + 2 + 5 { 5个字节渠道号(YYB_D)+ 2个字节的MAGIC字符长度说明 + 5个字节的MAGIC(!ZXK!)}。(4)写渠道号关键代码:// ZIP文件注释长度字段和MAGIC的字节数
static final int SHORT_LENGTH = 2;
//注释字符编码
static final String UTF_8 = "UTF-8";
// 文件最后用于定位的MAGIC字节
static final byte[] MAGIC = new byte[]{0x21, 0x5a, 0x58, 0x4b, 0x21}; //!ZXK!
//写入渠道号
public static void writeQUA(File file, String comment) throws IOException {
byte[] data = comment.getBytes(UTF_8);
final RandomAccessFile raf = new RandomAccessFile(file, "rw");
//定位到文件有效内容的末尾(文件长度-注释长度)
raf.seek(file.length() - SHORT_LENGTH);
//写入注释字节数{注释字节数+2(MAGIC长度说明)+MAGIC长度}
writeShort(data.length + SHORT_LENGTH + MAGIC.length, raf);
//写入注释内容
writeBytes(data, raf);
//写入MAGIC字节数
writeShort(data.length, raf);
//写入MAGIC
writeBytes(MAGIC, raf);
raf.close();
private static void writeBytes(byte[] data, DataOutput out) throws IOException {
out.write(data);
private static void writeShort(int i, DataOutput out) throws IOException {
ByteBuffer bb = ByteBuffer.allocate(SHORT_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
bb.putShort((short) i);
out.write(bb.array());
}  在安装包Comment字段写入渠道号的方式,经过测试,并没有修改安装包的内容,用户能成功安装并且使用。(5)读渠道号关键代码://读取源apk的路径
public static String getSourceApkPath(Context context, String packageName) {
if (TextUtils.isEmpty(packageName))
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(packageName, 0);
return appInfo.sourceD
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}//读取渠道号
public static String readQUA(File file) throws IOException {
RandomAccessFile raf =
raf = new RandomAccessFile(file, "r");
long index = raf.length();
byte[] buffer = new byte[MAGIC.length];
index -= MAGIC.
//定位到MAGIC处
raf.seek(index);
//读取MAGIC
raf.readFully(buffer);
//判断文件末尾是否存在MAGIC字符
if (isMagicMatched(buffer)) {
index -= SHORT_LENGTH;
raf.seek(index);
//读取渠道号长度
int length = readShort(raf);
if (length > 0) {
raf.seek(index);
//读取渠道号
byte[] bytesComment = new byte[length];
raf.readFully(bytesComment);
return new String(bytesComment, UTF_8);
} finally {
if (raf != null) {
raf.close();
//判断是否存在渠道号
private static boolean isMagicMatched(byte[] buffer) {
if (buffer.length != MAGIC.length) {
for (int i = 0; i < MAGIC. ++i) {
if (buffer[i] != MAGIC[i]) {
}读取渠道号有两个步骤:  1、获取安装包的绝对路径。Android系统在用户安装app时,会把用户安装的apk拷贝一份到/data/apk/路径下,通过getSourceApkPath 可以获取该apk的绝对路径。  2、读取渠道号。先定位到文件末尾,判断该文件是否存在写入的MAGIC字符,如果存在再读取渠道号。这时,我们多渠道的问题也解决了。我们把代码渠道号写入apk的comment字段,也通过代码成功读取到了渠道号。3、合成新安装包(1)删除原APK的渠道号  由于我们在生成差异包的时候,两个新旧版本的安装包都是没有渠道号的,而用户在应用市场下载的安装包是我们写入渠道号的安装包,所以我们要把用户正在使用的版本删除渠道号。由于/data/apk/路径,我们只有读取的权限,所以需要把删除渠道号的安装包临时保存起来。  删除渠道号的关键代码://删除渠道号
public static int deleteQua(File src, File dest) throws IOException{
if(!src.exists()){
return DELETE_QUA_FAILE_SOURCE_FILE_NOT_EXIST;
long contenLength = getContentLength(src);
if(contenLength < 0){
return DELETE_QUA_FAILE_QUA_NOT_EXIST;
FileInputStream in = new FileInputStream(src.getAbsolutePath());
File file =
if(!file.exists())
file.createNewFile();
FileOutputStream out = new FileOutputStream(file);
long copyed = contenL
byte buffer[] = new byte[1024];
while ((c = in.read(buffer)) != -1) {
if(copyed != c && c == buffer.length){
copyed = copyed -
out.write(buffer, 0, c);
//还原源文件,需要把最后两个字节置为0
表示apk没有注释
buffer[(int) (copyed - 1)] = 0;
buffer[(int) (copyed - 2)] = 0;
out.write(buffer, 0, (int)copyed);
close(in);
close(out);
return SUCCESS;
}(2)合成新APK  通过上面步骤,我们可以得到没有渠道号的临时本地安装包,并且和服务器的原始包一致,我们可以使用这个没有渠道号的安装包和已下载的差异包合成新版安装包。由于用户使用的版本可能是破解版,或者下载差异包下载不完整,所以在合成的前需要做文件一致性检验。可以比较文件的md5值,如果MD5值一致,才能进行合成,否则合成失败,直接下载完整的安装包升级。流程如下图:  我们需要把bsdiff中的bspatch.c整合到我们C代码中,并将其编译生so供Android手机使用,其中bspatch依赖bzip2,需要自己下载依赖的c文件。C关键代码:#include
#include "bzip2/bzlib.c"
#include "bzip2/crctable.c"
#include "bzip2/compress.c"
#include "bzip2/decompress.c"
#include "bzip2/randtable.c"
#include "bzip2/blocksort.c"
#include "bzip2/huffman.c"
#include "com_tencent_smartpatch_utils_PatchUtils.h"
static off_t offtin(u_char *buf) {
y = buf[7] & 0x7F;
y = y * 256;
y += buf[6];
y = y * 256;
y += buf[5];
y = y * 256;
y += buf[4];
y = y * 256;
y += buf[3];
y = y * 256;
y += buf[2];
y = y * 256;
y += buf[1];
y = y * 256;
y += buf[0];
if (buf[7] & 0x80)
int applypatch(int argc, char * argv[]) {
FILE * f, *cpf, *dpf, *
BZFILE * cpfbz2, *dpfbz2, *epfbz2;
int cbz2err, dbz2err, ebz2
ssize_t oldsize,
ssize_t bzctrllen,
u_char essay-header[32], buf[8];
u_char *old, *
off_t oldpos,
off_t ctrl[3];
if (argc != 4)
errx(1, "usage: %s oldfile newfile patchfile\n", argv[0]);
/* Open patch file */
if ((f = fopen(argv[3], "r")) == NULL)
err(1, "fopen(%s)", argv[3]);
File format:
"BSDIFF40"
sizeof(newfile)
bzip2(control block)
bzip2(diff block)
bzip2(extra block)
with control block a set of triples (x,y,z) meaning "add x bytes
from oldfile to x bytes copy y bytes from the
seek forwards in oldfile by z bytes".
/* Read essay-header */
if (fread(essay-header, 1, 32, f) < 32) {
if (feof(f))
errx(1, "Corrupt patch\n");
err(1, "fread(%s)", argv[3]);
/* Check for appropriate magic */
if (memcmp(essay-header, "BSDIFF40", 8) != 0)
errx(1, "Corrupt patch\n");
/* Read lengths from essay-header */
bzctrllen = offtin(essay-header + 8);
bzdatalen = offtin(essay-header + 16);
newsize = offtin(essay-header + 24);
if ((bzctrllen < 0) || (bzdatalen < 0) || (newsize < 0))
errx(1, "Corrupt patch\n");
/* Close patch file and re-open it via libbzip2 at the right places */
if (fclose(f))
err(1, "fclose(%s)", argv[3]);
if ((cpf = fopen(argv[3], "r")) == NULL)
err(1, "fopen(%s)", argv[3]);
if (fseeko(cpf, 32, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3], (long long) 32);
if ((cpfbz2 = BZ2_bzReadOpen(&cbz2, cpf, 0, 0, NULL, 0)) == NULL)
errx(1, "BZ2_bzReadOpen, bz2err = %d", cbz2err);
if ((dpf = fopen(argv[3], "r")) == NULL)
err(1, "fopen(%s)", argv[3]);
if (fseeko(dpf, 32 + bzctrllen, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3], (long long) (32 + bzctrllen));
if ((dpfbz2 = BZ2_bzReadOpen(&dbz2, dpf, 0, 0, NULL, 0)) == NULL)
errx(1, "BZ2_bzReadOpen, bz2err = %d", dbz2err);
if ((epf = fopen(argv[3], "r")) == NULL)
err(1, "fopen(%s)", argv[3]);
if (fseeko(epf, 32 + bzctrllen + bzdatalen, SEEK_SET))
err(1, "fseeko(%s, %lld)", argv[3],
(long long) (32 + bzctrllen + bzdatalen));
if ((epfbz2 = BZ2_bzReadOpen(&ebz2, epf, 0, 0, NULL, 0)) == NULL)
errx(1, "BZ2_bzReadOpen, bz2err = %d", ebz2err);
if (((fd = open(argv[1], O_RDONLY, 0)) < 0)
|| ((oldsize = lseek(fd, 0, SEEK_END)) == -1)
|| ((old = malloc(oldsize + 1)) == NULL)
|| (lseek(fd, 0, SEEK_SET) != 0)
|| (read(fd, old, oldsize) != oldsize) || (close(fd) == -1))
err(1, "%s", argv[1]);
if ((new = malloc(newsize + 1)) == NULL)
err(1, NULL);
oldpos = 0;
newpos = 0;
while (newpos < newsize) {
/* Read control data */
for (i = 0; i <= 2; i++) {
lenread = BZ2_bzRead(&cbz2, cpfbz2, buf, 8);
if ((lenread
errx(1, "Corrupt patch\n");
/* Read diff string */
lenread = BZ2_bzRead(&dbz2, dpfbz2, new + newpos, ctrl[0]);
if ((lenread < ctrl[0])
|| ((dbz2err != BZ_OK) && (dbz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
/* Add old data to diff string */
for (i = 0; i = 0) && (oldpos + i
errx(1, "Corrupt patch\n");
/* Read extra string */
lenread = BZ2_bzRead(&ebz2, epfbz2, new + newpos, ctrl[1]);
if ((lenread < ctrl[1])
|| ((ebz2err != BZ_OK) && (ebz2err != BZ_STREAM_END)))
errx(1, "Corrupt patch\n");
/* Adjust pointers */
newpos += ctrl[1];
oldpos += ctrl[2];
/* Clean up the bzip2 reads */
BZ2_bzReadClose(&cbz2, cpfbz2);
BZ2_bzReadClose(&dbz2, dpfbz2);
BZ2_bzReadClose(&ebz2, epfbz2);
if (fclose(cpf) || fclose(dpf) || fclose(epf))
err(1, "fclose(%s)", argv[3]);
/* Write the new file */
if (((fd = open(argv[2], O_CREAT | O_TRUNC | O_WRONLY, 0666)) GetStringUTFChars(env, old_apk, 0));
ch[2] = (char*) ((*env)->GetStringUTFChars(env, new_apk, 0));
ch[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0));
int ret = applypatch(4, ch);
(*env)->ReleaseStringUTFChars(env, old_apk, ch[1]);
(*env)->ReleaseStringUTFChars(env, new_apk, ch[2]);
(*env)->ReleaseStringUTFChars(env, patch, ch[3]);
}  java关键代码:/**
* apk 合成类
public class PatchUtils {
public static final String TAG = "PatchUtils";
System.loadLibrary("apksmartpatchlibrary");
* native方法 使用路径为oldApkPath的apk与路径为patchPath的补丁包,合成新的apk,并存储于newApkPath
* 返回:0,说明操作成功
* @param oldApkPath 示例:/sdcard/old.apk
* @param newApkPath 示例:/sdcard/new.apk
* @param patchPath
示例:/sdcard/xx.patch
public static native int patch(String oldApkPath, String newApkPath, String patchPath);
}  至此,我们调用 PatchUtile.patch方法即可生成最新的安装包。三、小结  再重复一下完整过程:  1、编译打包APK(未写入渠道号)  2、服务器用新旧APK(未写入渠道号)生成差异包  3、APK写入渠道号,供用户下载使用  4、用户本地APK删除渠道号  5、根据用户使用版本下载差异包  6、用本地删除渠道号的APK+下载的差异包生成最新版本APK四、参考资料  1、http://www.daemonology.net/bsdiff  2、https://en.wikipedia.org/wiki/Zip_(file_format)  3、/cundong/SmartAppUpdates  4、/mcxiaoke/packer-ng-plugin
觉得不错,分享给更多人看到
QQ音乐技术团队 微信二维码
分享这篇文章
QQ音乐技术团队 最新文章
QQ音乐技术团队 热门文章全民k歌里的洪荒之力是什么意思_百度知道14:00~18:00下午场欢唱套餐+啤酒套餐,免开机费!免费WiFi!
价值&&#165;350
小包,下午场4小时
中包,下午场4小时
大包,下午场4小时
小包,下午场4小时
中包,下午场4小时
大包,下午场4小时
小包,下午场4小时
中包,下午场4小时
大包,下午场4小时
小包,下午场4小时
中包,下午场4小时
大包,下午场4小时
小包,下午场4小时
中包,下午场4小时
大包,下午场4小时
小包,下午场4小时
中包,下午场4小时
大包,下午场4小时
小包,下午场4小时
中包,下午场4小时
大包,下午场4小时
包厢适用人数
下午场啤酒套餐内容
小吃(9选3,不可重复选)
元旦、春节、情人节、圣诞节不可用
无需预约,如遇消费高峰时段您可能需要排队
商家根据使用人数安排包厢类型,详询商户
同时段内,每个包厢最多用1张
同一房间按最早到店者的消费时间起计算,时间无法叠加累计
请会员合理安排入场时间,到点清场
谢绝自带酒水、饮料、食品
不再与其他优惠同享
提供免费WiFi
提供免费停车位
如需团购券发票,请您在消费时向商户咨询
为了保障您的权益,建议使用美团、点评网线上支付。若使用其他支付方式导致纠纷,美团、点评网不承担任何责任,感谢您的理解和支持!
套餐详情介绍
萨拉齐的一家包内拥有舞动大地的KTV,也是享有众多娱乐人称赞的一家休闲活动场所。求大神修改全民k歌老版本,去掉升级提示!_全民k歌吧_百度贴吧
&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&签到排名:今日本吧第个签到,本吧因你更精彩,明天继续来努力!
本吧签到人数:0成为超级会员,使用一键签到本月漏签0次!成为超级会员,赠送8张补签卡连续签到:天&&累计签到:天超级会员单次开通12个月以上,赠送连续签到卡3张
关注:201,065贴子:
求大神修改全民k歌老版本,去掉升级提示!收藏
个人觉得还是老版本高大上,腾讯所有应用软件都是一身赘肉而且越来越多胖得离谱,有没有和我一样,喜欢简单直接的老版本的,吧友,大神?求一个1.5.6版本的全民k歌去广告去升级提示版的。联系方式
HUAWEI nova 我的手机,我漂亮,震撼上市!
难道就这样沉了?
喂,有人么?
登录百度帐号推荐应用
为兴趣而生,贴吧更懂你。或}

我要回帖

更多关于 全民k歌怎么升级 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信