標籤:重新整理 avr director ann pack sys 沒有 signature bundle
一、使用情境
apk升級,節省伺服器和使用者的流量
二、原理
自從 Android 4.1 開始, Google Play 引入了應用程式的累加式更新功能,App使用該升級方式,可節省約2/3的流量。
現在國內主流的應用市場也都支援應用的累加式更新了,最常見的應用寶省流量更新。
累加式更新的原理,就是將手機上已安裝apk與伺服器端最新apk進行二進位對比,得到差分包(即兩個版本的差異檔案),使用者更新程式時,只需要下載差分包,並在本地使用差分包與已安裝apk,合成新版apk。
例如,當前手機中已安裝微博V1,大小為12.8MB,現在微博發布了最新版V2,大小為15.4MB,我們對兩個版本的apk檔案差分比對之後,發現差異只有3M(可能更小,因為得到差異檔案後內部還會使用壓縮),那麼使用者就只需要要下載一個3M的差分包,使用舊版apk與這個差分包,合成得到一個新版本apk,提醒使用者安裝即可,不需要整包下載15.4M的微博V2版apk。
三、過程
此過程需要伺服器和用戶端寫作完成
伺服器:拿到最新版的apk,called new.apk,舊版本的apk,called old.apk,通過累加式更新技術得到差分檔案,called patch.patch。
用戶端:通過網路操作去伺服器下載已準備好的差分檔案patch.patch,找到data目錄下目前的版本的old.apk,通過累加式更新技術合并這兩個檔案,得到new.apk。
四、執行個體講解
上述過程需要用到伺服器得到差分檔案,但是尷尬了,我不會,所以我使用投機取巧的方式,建立一個工程通過NDK去得到差分檔案,然後通過在另外一個項目中通過NDK去合并,當然無論是伺服器還是通過類比我們最終是需要知道是怎麼的過程具體實現。
4.1 準備工作
1.NDK開發技術,還不會的,或者會的歡迎查看我NDK相關文章:點擊進入
2.我們自己需要準備兩個apk,一個old.apk,一個new.apk,用於類比伺服器進行差分,old.apk只是用了TextView顯示當前為1.0的舊版本,在old.apk的基礎上new.apk在目前的目錄下的res/drawable-hdpi目錄下增加了一些圖片以便類比容量擴從,並將版本號碼修改為2,TextView顯示為2.0新版本,這樣就簡單的得到了2個新舊版本。當然,這個操作非常簡單,因此我們提供了資源下載:old.apk與new.apk下載。
3.用於差分new.apk與old.apk以及合并old.apk與patch.patch的bsdiff檔案,又因為bsdiff依賴bzip2,所以我們還需要用到 bzip2
bsdiff中,bsdiff.c 用於產生差分包,bspatch.c 用於合成檔案。這些檔案都是C語言寫的,所以需要使用NDK技術: 本地下載
4.2 類比伺服器得到差分檔案patch.patch
注以下開發環境都是eclipse。
1.建立android項目,隨後new一個source folder 命名為jni,並做好此項目的NDK配置,這裡不講解,
2.然後將準備工作下載的bsdiff中的bsdiff.c,bsdiff.h,bzip2檔案夾拷貝到jni目錄下,
3.建立類,類名當然隨意,用於載入動態庫以及產生native方法,如下:
package com.example.serverpatch;public class PatchAPK { public native static int patchAPK(String oldFile,String newFile,String patchFile); static{ System.loadLibrary("server_patch"); }}
當然這裡的server_patch必須與後面Android.mk檔案中的so庫名字一致。並且patchAPK方法需要3個參數,分別為舊apk的路徑,新apk的路徑,差分檔案的路徑。
4.通過cmd定位到此項目的src目錄下,輸入javah +3步驟中建立的類包名,例如:
javah com.example.serverpatch.PatchAPK
然後重新整理項目,可以看到:
將其也複製到jni目錄下
5.將com.example.serverpatch.PatchAPK.h中需要實現的方法複製到bsdiff.c中實現:
當然這裡需要給這些JNIEnv,變數命名久不用說了吧。
然後我們看看具體怎麼實現:
JNIEXPORT jint JNICALL Java_com_example_serverpatch_PatchAPK_patchAPK(JNIEnv *env, jclass cls, jstring oldFile, jstring newFile, jstring patchFile){ int argc=4; char *argv[argc]; argv[0] = "bsdiff"; argv[1] = (char*) ((*env)->GetStringUTFChars(env, oldFile, 0)); argv[2] = (char*) ((*env)->GetStringUTFChars(env, newFile, 0)); argv[3] = (char*) ((*env)->GetStringUTFChars(env, patchFile, 0)); printf("old apk = %s \n", argv[1]); printf("new apk = %s \n", argv[2]); printf("patch = %s \n", argv[3]); int ret = diff_main(argc, argv); printf("diff_main result = %d ", ret); (*env)->ReleaseStringUTFChars(env, oldFile, argv[1]); (*env)->ReleaseStringUTFChars(env, newFile, argv[2]); (*env)->ReleaseStringUTFChars(env, patchFile, argv[3]); return ret;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
產生差分檔案我們需要用到是bsdiff.c中的main方法,因為我將其修改為diff_main,所以大家不必鬱悶哪裡的diff_main()函數,main函數需要兩個參數,第一個固定為4,第二個為 char* 資料,但是我們傳入的是Java中的string,所以我們首先通過NDK中的GetStringUTFChars將其轉為 char* ,因為java中有GC,但是c語言必須自己釋放通過ReleaseStringUTFChars釋放。
6.將bsdiff.c中的 < bzlib.h >修改為如下:
#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"
因為此時的bzlib在本地,所以使用雙引號。
6.編寫Android.mk以及Application.mk。這裡不貼出。只要記住產生的.so和載入的.so名稱一致。
最終的項目樣式如下:
首先在MainActivity不做任何事情,通過模擬器運行一把項目,我是通過模擬器。真機可以不用運行,因為我只是想將old.apk和new.apk放到模擬器的根目錄下,不行拿不到它的根目錄了啊,運行完成後將準備好的old.apk,與new.apk放到SDcard目錄下,如我的模擬器:
可以看到new.apk有5M,old.apk有1M,如果不使用累加式更新,現在我們需要使用5M的流量更新。
好的,現在在MainActivity中調用PatchAPK 類中的navtive方法:
package com.example.serverpatch;import java.io.File;import android.app.Activity;import android.os.Bundle;import android.os.Environment;public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getPatchAPK(); } private void getPatchAPK() { if (Environment.getExternalStorageState() != null) { File sdFile=Environment.getExternalStorageDirectory(); String sdString=sdFile.getAbsolutePath(); PatchAPK.patchAPK(sdString+"/old.apk", sdString+"/new.apk", sdString+"/patch.patch"); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
我們找到剛才 放置於SD根目錄下的new.apk與old.apk的目錄,並添加差分檔案目錄,當然調用
PatchAPK.patchAPK(sdString+”/old.apk”, sdString+”/new.apk”, sdString+”/patch.patch”);
需要在子線程中,這裡沒有示範,主要理解原理。最後運行項目。
可以看到SD的根目錄下多了一個patch.patch檔案:
可以看到只有3M多點,到了這裡我們類比伺服器拿到差分檔案已經完成。
4.3 用戶端合差分檔案
到了這裡離成功已經特別近了,所以我們不能氣餒!
1.建立用戶端項目,隨之後面的操作和剛才類比類比伺服器製作差分檔案的步驟幾乎是一致,所以我們不用擔心代碼量,可以一直複製,畢竟這是程式員必備技能。與類比伺服器中不同的是:
- 將bspatch.c,bspatch.h放到jni目錄下,而不是bsdiff系列,當然bzip2檔案
夾仍保留
- 建立類hebingAPK,當然我這裡命名不規範,別介意,關鍵看原理:
package com.example.patch;public class hebingAPK { public native static int hbAPK(String oldFile,String newFile,String patchFile); static{ System.loadLibrary("client_patch"); }}
通過javah命令產生標頭檔,在bspatch.c中實現具體合并,
JNIEXPORT jint JNICALL Java_com_example_patch_hebingAPK_hbAPK(JNIEnv *env, jclass cls,jstring old, jstring new, jstring patch){ int argc = 4; char * argv[argc]; argv[0] = "bspatch"; argv[1] = (char*) ((*env)->GetStringUTFChars(env, old, 0)); argv[2] = (char*) ((*env)->GetStringUTFChars(env, new, 0)); argv[3] = (char*) ((*env)->GetStringUTFChars(env, patch, 0)); printf("old apk = %s \n", argv[1]); printf("patch = %s \n", argv[3]); printf("new apk = %s \n", argv[2]); int ret = hb_main(argc, argv); printf("patch result = %d ", ret); (*env)->ReleaseStringUTFChars(env, old, argv[1]); (*env)->ReleaseStringUTFChars(env, new, argv[2]); (*env)->ReleaseStringUTFChars(env, patch, argv[3]); return ret;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
可以看到合并和差分的代碼NDK代碼幾乎一致,只是這裡使用的main函數為bspatch.c中的main,將其修改為hb_main函數,最後當然也需要將將bsdiff.c中的 include < bzlib.h >導包修改。
最終項目目錄樣式:
最後在MainActivity中調用native代碼:
public class MainActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_layout); getPatchAPK(); } private void getPatchAPK() { if (Environment.getExternalStorageState() != null) { File sdFile=Environment.getExternalStorageDirectory(); String sdString=sdFile.getAbsolutePath(); hebingAPK.hbAPK(sdString+"/old.apk", sdString+"/new2.apk", sdString+"/patch.patch"); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
當然,這裡合并和差分一樣也是需要在子線程中進行,這裡也不示範了,只要看原理。
等會我們就需要看產生的new2.apk是否可以安裝並且大小是否和new.apk是否一樣,如果有必要可以通過校正新合成的apk的MD5或SHA1是否正確,如正確,則引導使用者安裝,當然我們這裡不示範這個。
最後運行,模擬器根目錄如下:
可以看到new2.apk成功產生了,好的到此我們的差分和合并全部完成,覺得可以的點個贊好嗎,謝謝。
4.3 安裝new2.apk
當然這一部是最簡單的,通過adb命令去安裝。
首先將new2.apk pull到電腦中,開啟CMD,定位到new2.apk目錄下,通過adb install new2.apk將其安裝:
可以看到安裝成功,開啟模擬器也是可以使用,說明這一套的差分和合并是成功的。
轉Android開發之累加式更新