因?yàn)轫?xiàng)目需要前兩天研究了下增量更新的,如果項(xiàng)目沒(méi)有硬性規(guī)定的話(huà),本人推薦使用第三方的SDK.
比如:友盟的增量更新SDK 傳送門(mén)http://www.umeng.com/component_update分分鐘就能實(shí)現(xiàn)Android增量更新功能,友盟官方API相信大家都能看懂,不懂的沒(méi)關(guān)系之后我也會(huì)單獨(dú)寫(xiě)個(gè)用友盟的Demo
//TODO 友盟Demo傳送門(mén):
如果你跟我一樣,項(xiàng)目硬性規(guī)定必須自己寫(xiě)增量更新的代碼,請(qǐng)往下看下面的
●功能版本:
增量更新是Google 4.1增加的新功能
●官方說(shuō)明
[html]view plaincopy
Smart?app?updates?is?a?new?feature?of?Google?Play?that?introduces?a?better?way?of?delivering?app?updates?to?devices.?When?developers?publish?an?update,?Google?Play?now?delivers?only?the?bits?that?have?changed?to?devices,?rather?than?the?entire?APK.?This?makes?the?updates?much?lighter-weight?in?most?cases,?so?they?are?faster?to?download,?save?the?device’s?battery,?and?conserve?bandwidth?usage?on?users’?mobile?data?plan.?On?average,?a?smart?app?update?is?about?1/3?the?sizeof?a?full?APK?update.
http://developer.android.com/about/versions/jelly-bean.html
●功能背景:
現(xiàn)在的安卓Apk越來(lái)越大本冲,而在此之前如果用戶(hù)發(fā)現(xiàn)有新版本的話(huà)昵仅,需要重新把對(duì)應(yīng)程序的新版本下載下來(lái)轿曙,有時(shí)候并不是重大更新,僅僅只是優(yōu)化了一下,用戶(hù)就需要重新下載apk缩筛,不僅浪費(fèi)流量而且浪費(fèi)時(shí)間(等同于浪費(fèi)生命啊,親!!!),有這樣的問(wèn)題,便會(huì)出現(xiàn)更優(yōu)的功能迭代即增量更新或增量升級(jí)堡称,或者叫差異化更新瞎抛,目前很多應(yīng)用商店已經(jīng)對(duì)接了此功能:比如谷歌官方,小米等等却紧。桐臊。胎撤。
●實(shí)現(xiàn)原理:
客戶(hù)端與服務(wù)端對(duì)比,并生成版本之間的差異包,用戶(hù)不用下載整個(gè)apk文件,只用下載差異包就可以了豪硅,比如用戶(hù)微博2.0升級(jí)到微博3.0哩照,本來(lái)微博3.0版本應(yīng)該是10M,服務(wù)器通過(guò)生成差異包4M懒浮,用戶(hù)直接下載4M文件并在本地進(jìn)行合并生成微博3.0版本飘弧,安裝,對(duì)于網(wǎng)絡(luò)環(huán)境較差的用戶(hù)絕對(duì)提高用戶(hù)體驗(yàn),節(jié)省流量和時(shí)間
(服務(wù)端+客戶(hù)端)實(shí)現(xiàn)思路:
1.客戶(hù)端帶著VerisionCode發(fā)送請(qǐng)求給服務(wù)端
2.服務(wù)端判斷VersionCode是否是最新版本,如果不是檢測(cè)是否有此版本和最新版本的差異包,如果沒(méi)有則在后臺(tái)生成(舊VersionCode-新VersionCode).patch文件
3.客戶(hù)端收到返回?cái)?shù)據(jù),判斷是否最新,如果不是彈出升級(jí)的Dilog對(duì)話(huà)框
4.點(diǎn)擊立即升級(jí),再次發(fā)送請(qǐng)求
5.服務(wù)端返回給客戶(hù)端對(duì)應(yīng)版本差異包的Url地址
6.客戶(hù)端拿到URL地址下載到SD卡中,并從客戶(hù)端data/app 目錄下拷貝本程序的apk安裝包,
7.客戶(hù)端通過(guò)調(diào)用JNI編譯的.so動(dòng)態(tài)鏈接庫(kù)中的方法合并舊版本和差異包,生成新版本,調(diào)用Intent方法安裝最新的apk包
用到的知識(shí)點(diǎn):
1.JNI相關(guān)(重點(diǎn))
2.Http協(xié)議相關(guān)(次重點(diǎn))
3.工廠設(shè)計(jì)模式_調(diào)用接口實(shí)現(xiàn)類(lèi)(次重點(diǎn))
●實(shí)現(xiàn)
假設(shè)砚著,你的apk已經(jīng)發(fā)布了3個(gè)版次伶,1.0,2.0稽穆,3.0冠王,這時(shí)候你要在后臺(tái)發(fā)布4.0,在你上傳時(shí)舌镶,就應(yīng)該生成
1.0——>4.0的差異包柱彻;
2.0——>4.0的差異包;
3.0——>4.0的差異包餐胀;
選擇使用這個(gè)開(kāi)源二進(jìn)制比較工具來(lái)實(shí)現(xiàn):
http://www.daemonology.net/bsdiff/
下載后得到bsdiff-4.3.tar.gz哟楷。
其中bsdiff.c是二進(jìn)制文件比對(duì)的代碼;bspatch.c是二進(jìn)制文件合成的代碼否灾;
我們將使用這個(gè)bsdiff來(lái)生成兩個(gè)apk的patch包卖擅,并且使用bspatch.c來(lái)合成舊apk與patch包;
使用bsdiff墨技、bspatch時(shí)惩阶,還需用到bzip2: http://www.bzip.org/downloads.html
下載后得到:bzip2-1.0.6.tar.gz。
我們需要用到bzip2-1.0.6.tar.gz中以下13個(gè)文件(這里面可能有的是不需要的扣汪,我都拷貝過(guò)來(lái)了):
[plain]view plaincopy
01?blocksort.c
02?bzip2.c
03?bzip2recover.c
04?bzlib_private.h
05?bzlib.c
06?bzlib.h
07?compress.c
08?crctable.c
09?decompress.c
10?dlltest.c
11?huffman.c
12?randtable.c
13?spewG.c
將這13個(gè)文件拷貝至jni目錄下断楷,接下來(lái),我們就調(diào)用bsdiff生成差異包私痹,并且調(diào)用bspatch合成新包脐嫂。
客戶(hù)端解決問(wèn)題:
1)??????在客戶(hù)端把下載的.patch(差異包) 與舊版apk就行合并
一.??獲取舊版本apk
[java]view plaincopy
/**
*?備份data/app目錄下本程序的apk安裝文件到SD卡根目錄下
*?@param?packageName
*?@param?mActivity
*?@throws?IOException
*/
publicstaticvoidbackupApp(String?packageName,?Activity?mActivity)
throwsIOException?{
//存放位置
StringnewFile?=?Environment.getExternalStorageDirectory()
.getAbsolutePath()+?File.separator;
StringoldFile?=null;
try{
//原始位置
oldFile=?mActivity.getPackageManager().getApplicationInfo(
packageName,0).sourceDir;
}catch(NameNotFoundException?e)?{
e.printStackTrace();
}
System.out.println(newFile);
System.out.println(oldFile);
Filein?=newFile(oldFile);
Fileout?=newFile(newFile?+?packageName?+".apk");
if(!out.exists())?{
out.createNewFile();
Log.i(tag,"文件備份成功!"+"存放于"+?newFile?+"目錄下");
}else{
Log.i(tag,"文件備份成功紊遵!"+"存放于"+?newFile?+"目錄下");
}
FileInputStreamfis?=newFileInputStream(in);
FileOutputStreamfos?=newFileOutputStream(out);
intcount;
//文件太大的話(huà)账千,我覺(jué)得需要修改
byte[]buffer?=newbyte[256*1024];
while((count?=?fis.read(buffer))?>0)?{
fos.write(buffer,0,?count);
}
fis.close();
fos.flush();
fos.close();
}
二.??合并差異包和舊版本APK
[java]view plaincopy
/**
*?顯示更新對(duì)話(huà)框
*/
protectedvoid?showUpdateDialog()?{
AlertDialog.Builderbuilder?=newBuilder(this);
builder.setTitle("更新提醒");
builder.setMessage("1.增加數(shù)據(jù)庫(kù)...");
builder.setOnCancelListener(newOnCancelListener()?{
@Override
publicvoid?onCancel(DialogInterface?dialog)?{
}
});
builder.setPositiveButton("立刻升級(jí)",newDialogInterface.OnClickListener()?{
@Override
publicvoid?onClick(DialogInterface?dialog,intwhich)?{
Log.i(tag,"下載最新版本:"+",替換安裝");
finalProgressDialog?pd?=newProgressDialog(
MainActivity.this);
pd.setTitle("更新提醒:");
pd.setMessage("正在下載更新apk");
//顯示指定水平方向的進(jìn)度條
pd.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
pd.show();
if(Environment.getExternalStorageState().equals(
Environment.MEDIA_MOUNTED)){
newThread()?{
publicvoid?run()?{
Filef?=newFile(Environment
.getExternalStorageDirectory(),"temp.patch");
//獲取服務(wù)器上生成的差異包地址result
//??????????????????????????????????????????????????????????????PatcherEngineengine?=?BeanFactory.getInstance(PatcherEngine.class);
//??????????????????????????????????????????????????????????????Stringresult?=?engine.getUpGrade(MainActivity.this);
Filefile?=null;
//??????????????????????????????????????????????????????????????if(result!=null){
file=?DownloadManager.download(
urlPath,
f.getAbsolutePath(),pd);
//???????????????????????????????????????????????????????????????????????Log.i(tag,result);
//??????????????????????????????????????????????????????????????}
if(file?==null)?{
Log.i(tag,"下載的文件不存在");
}else{
Log.i(tag,"下載成功替換安裝");
try{
//這里的包名com.dodola.patcher就是你要從data/app目錄下復(fù)制出來(lái)的舊版apk包
ApkInfoTool.backupApp("com.dodola.patcher",MainActivity.this);
}catch(IOException?e)?{
//TODO?Auto-generated?catch?block
e.printStackTrace();
}
Stringpatch?=?rootPath?+?File.separator?+"temp.patch";
StringnewApk?=?rootPath?+?File.separator?+"new.apk";
//這里的包名com.dodola.patcher改成你自己的包名,不然肯定是不能合并的
StringoldApk?=?rootPath?+?File.separator?+"com.dodola.patcher.apk";
//合并差異文件和舊版APK包得到新版APk
patcher(oldApk,newApk,?patch);
//安裝新版APK
installApk(newApk);
}
pd.dismiss();
};
}.start();
}else{
Toast.makeText(getApplicationContext(),"sd卡不可用",0).show();
}
}
});
builder.setNegativeButton("下次再說(shuō)",newDialogInterface.OnClickListener()?{
@Override
publicvoid?onClick(DialogInterface?dialog,intwhich)?{
Toast.makeText(getApplicationContext(),"下次再說(shuō)",0).show();
}
});
builder.show();
}